From 7119e04a6504628e96738d8422d1c9a4579f8ef6 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 24 Oct 2026 16:34:02 +0800 Subject: [PATCH] ++ --- .../2026-10-24-profile-startup.md | 14 ++ docs/openapi.json | 34 ++++ packages/client/src/api/hermes/chat.ts | 9 +- packages/client/src/api/hermes/profiles.ts | 5 + .../components/hermes/chat/MessageList.vue | 18 ++ .../hermes/profiles/ProfileAvatar.vue | 22 ++- packages/client/src/i18n/locales/en.ts | 1 + packages/client/src/i18n/locales/zh.ts | 1 + packages/client/src/stores/hermes/chat.ts | 75 ++++++-- packages/client/src/stores/hermes/profiles.ts | 10 +- packages/client/src/stores/hermes/settings.ts | 9 +- packages/client/src/views/hermes/ChatView.vue | 12 +- .../server/src/controllers/hermes/profiles.ts | 24 ++- packages/server/src/routes/hermes/profiles.ts | 1 + .../server/src/services/hermes/hermes-cli.ts | 10 ++ tests/client/chat-run-reconnect.test.ts | 16 ++ .../client/chat-store-session-command.test.ts | 2 +- .../client/chat-store-session-loading.test.ts | 162 ++++++++++++++++++ tests/client/chat-view-tab-title.test.ts | 11 ++ tests/client/profile-avatar-loading.test.ts | 34 ++++ tests/client/profiles-store.test.ts | 14 ++ tests/e2e/chat-startup-recovery.spec.ts | 41 +++++ tests/server/profile-list-snapshot.test.ts | 44 +++++ tests/server/profiles-routes.test.ts | 23 +++ 24 files changed, 553 insertions(+), 39 deletions(-) create mode 100644 docs/chat-chain-changes/2026-10-24-profile-startup.md create mode 100644 tests/client/chat-store-session-loading.test.ts create mode 100644 tests/client/profile-avatar-loading.test.ts create mode 100644 tests/server/profile-list-snapshot.test.ts diff --git a/docs/chat-chain-changes/2026-10-24-profile-startup.md b/docs/chat-chain-changes/2026-10-24-profile-startup.md new file mode 100644 index 0000000..01f1c12 --- /dev/null +++ b/docs/chat-chain-changes/2026-10-24-profile-startup.md @@ -0,0 +1,14 @@ +--- +date: 2026-10-24 +pr: pending +feature: Conversation startup and profile loading +impact: Load conversations independently of metadata and recover failed resume requests via HTTP. +--- + +# 2026-10-24: Conversation startup and profile loading + +- PR/commit: pending local review. +- Touched feature: client chat startup, session switching, resume listener lifecycle, and profile list metadata. +- Behavior impact: conversation loading no longer waits for profile/settings/model metadata. Session switches ignore obsolete replies (including A/B/A), cancel obsolete resume listeners, and fall back to paginated HTTP history after a three-second resume timeout. Failed history loads show a retry action. Profile inventory uses disk metadata instead of spawning Hermes CLI; uploaded avatar bodies are fetched on demand through an access-checked endpoint. +- The `/chat-run` event names, fields and server emission timing remain unchanged. Only the client's wait/fallback behavior changes. +- Validation: focused store/view/profile tests, Playwright startup/retry/multi-tab/profile tests, full Vitest coverage, harness and production build. See task results for actual outcomes. diff --git a/docs/openapi.json b/docs/openapi.json index 0f458c5..24fdae5 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -7375,6 +7375,40 @@ } }, "/api/hermes/profiles/{name}/avatar": { + "get": { + "tags": [ + "Profiles" + ], + "summary": "Get avatar", + "description": "GET /api/hermes/profiles/:name/avatar", + "operationId": "getAvatar", + "security": [ + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, "put": { "tags": [ "Profiles" diff --git a/packages/client/src/api/hermes/chat.ts b/packages/client/src/api/hermes/chat.ts index f48df7d..701d9ae 100644 --- a/packages/client/src/api/hermes/chat.ts +++ b/packages/client/src/api/hermes/chat.ts @@ -788,14 +788,21 @@ export function resumeSession( onResumed: (data: ResumeSessionPayload) => void, profile?: string | null, transport: ChatRunTransport = 'chat-run', + signal?: AbortSignal, ): Socket { const socket = connectChatRun(profile, transport) const handleResumed = (data: ResumeSessionPayload) => { if (data?.session_id !== sessionId) return - removeSocketListener(socket, 'resumed', handleResumed) + cleanup() onResumed(data) } + const cleanup = () => { + removeSocketListener(socket, 'resumed', handleResumed) + signal?.removeEventListener('abort', cleanup) + } + if (signal?.aborted) return socket + signal?.addEventListener('abort', cleanup, { once: true }) socket.on('resumed', handleResumed) socket.emit('resume', { session_id: sessionId, ...(profile ? { profile } : {}) }) diff --git a/packages/client/src/api/hermes/profiles.ts b/packages/client/src/api/hermes/profiles.ts index e8fbe00..c7d877f 100644 --- a/packages/client/src/api/hermes/profiles.ts +++ b/packages/client/src/api/hermes/profiles.ts @@ -242,3 +242,8 @@ export async function importProfile(file: File): Promise { return false } } + +export async function fetchProfileAvatar(name: string): Promise { + const res = await request<{ avatar: ProfileAvatar | null }>(`/api/hermes/profiles/${encodeURIComponent(name)}/avatar`) + return res.avatar +} diff --git a/packages/client/src/components/hermes/chat/MessageList.vue b/packages/client/src/components/hermes/chat/MessageList.vue index 299d9f8..e55a8a5 100644 --- a/packages/client/src/components/hermes/chat/MessageList.vue +++ b/packages/client/src/components/hermes/chat/MessageList.vue @@ -450,6 +450,11 @@ defineExpose({