From 691bc643cf498b82d4c36db2d3a517ace3dba98e Mon Sep 17 00:00:00 2001 From: zerob13 Date: Mon, 9 Mar 2026 20:41:53 +0800 Subject: [PATCH 1/6] refactor(hooks): migrate hooksNotifications to new agent system - Update HooksNotificationsDeps type to use new agent methods - Replace getConversation with getSession, remove resolveWorkspaceContext - Update buildPayload to extract workdir from session.projectDir directly - Refactor extractPromptPreview to handle JSON serialized content - Adjust presenter initialization order for dependency injection --- .../presenter/hooksNotifications/index.ts | 88 ++++++++++--------- src/main/presenter/index.ts | 13 ++- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/main/presenter/hooksNotifications/index.ts b/src/main/presenter/hooksNotifications/index.ts index 8c7061688..b3f92dc35 100644 --- a/src/main/presenter/hooksNotifications/index.ts +++ b/src/main/presenter/hooksNotifications/index.ts @@ -50,10 +50,9 @@ export type HookDispatchContext = { } type HookConversationLookup = { - settings: { - providerId?: string - modelId?: string - } + providerId?: string + modelId?: string + projectDir?: string | null } type HookMessageLookup = { @@ -61,12 +60,8 @@ type HookMessageLookup = { } type HooksNotificationsDeps = { - getConversation?: (conversationId: string) => Promise - getMessage?: (messageId: string) => Promise - resolveWorkspaceContext?: ( - conversationId?: string, - modelId?: string - ) => Promise<{ agentWorkspacePath: string | null }> + getSession?: (sessionId: string) => Promise + getMessage?: (messageId: string) => Promise } class SerialQueue { @@ -113,16 +108,36 @@ export const parseRetryAfterMs = (response: Response, body?: unknown): number | } const extractPromptPreview = (content: unknown): string => { - if (typeof content === 'string') return content - if (!content || typeof content !== 'object') return '' - const candidate = content as { - text?: string - content?: Array<{ content?: string }> + // Handle string content (JSON serialized from new agent system) + if (typeof content === 'string') { + try { + const parsed = JSON.parse(content) as unknown + return extractFromParsed(parsed) + } catch { + // Not valid JSON, return as-is + return content + } } - if (typeof candidate.text === 'string') return candidate.text - if (Array.isArray(candidate.content)) { - return candidate.content.map((block) => block.content || '').join('') + // Handle object content (already parsed) + return extractFromParsed(content) +} + +const extractFromParsed = (content: unknown): string => { + if (!content || typeof content !== 'object') return '' + + // Handle UserMessageContent format: { text: string, files: [], ... } + const userCandidate = content as { text?: string } + if (typeof userCandidate.text === 'string') return userCandidate.text + + // Handle AssistantMessageBlock[] format: array of blocks + if (Array.isArray(content)) { + const blocks = content as Array<{ type?: string; content?: string }> + return blocks + .filter((block) => block.type === 'content') + .map((block) => block.content || '') + .join('') } + return '' } @@ -264,31 +279,22 @@ export class HooksNotificationsService { let agentId = context.agentId let workdir = context.workdir - if (conversationId && (!providerId || !modelId)) { - const getConversation = this.deps.getConversation + if (conversationId && (!providerId || !modelId || !workdir)) { + const getSession = this.deps.getSession try { - if (getConversation) { - const conversation = await getConversation(conversationId) - providerId = providerId ?? conversation.settings.providerId - modelId = modelId ?? conversation.settings.modelId - if (!agentId && conversation.settings.providerId === 'acp') { - agentId = conversation.settings.modelId + if (getSession) { + const session = await getSession(conversationId) + if (session) { + providerId = providerId ?? session.providerId + modelId = modelId ?? session.modelId + workdir = workdir ?? session.projectDir + if (!agentId && session.providerId === 'acp') { + agentId = session.modelId + } } } } catch (error) { - log.warn('[HooksNotifications] Failed to load conversation info:', error) - } - } - - if (conversationId && !workdir) { - const resolveWorkspaceContext = this.deps.resolveWorkspaceContext - try { - if (resolveWorkspaceContext) { - const resolved = await resolveWorkspaceContext(conversationId, modelId) - workdir = resolved.agentWorkspacePath ?? null - } - } catch (error) { - log.warn('[HooksNotifications] Failed to resolve workdir:', error) + log.warn('[HooksNotifications] Failed to load session info:', error) } } @@ -298,7 +304,9 @@ export class HooksNotificationsService { try { if (getMessage) { const message = await getMessage(context.messageId) - promptPreview = extractPromptPreview(message.content) + if (message) { + promptPreview = extractPromptPreview(message.content) + } } } catch (error) { log.warn('[HooksNotifications] Failed to read message for preview:', error) diff --git a/src/main/presenter/index.ts b/src/main/presenter/index.ts index 2600202a7..87c266966 100644 --- a/src/main/presenter/index.ts +++ b/src/main/presenter/index.ts @@ -203,11 +203,10 @@ export class Presenter implements IPresenter { // Initialize Skill Sync presenter this.skillSyncPresenter = new SkillSyncPresenter(this.skillPresenter, this.configPresenter) - // Initialize Hooks & Notifications service + // Initialize new agent architecture presenters first (needed by hooksNotifications) this.hooksNotifications = new HooksNotificationsService(this.configPresenter, { - getConversation: this.sessionPresenter.getConversation.bind(this.sessionPresenter), - getMessage: this.sessionPresenter.getMessage.bind(this.sessionPresenter), - resolveWorkspaceContext: this.sessionManager.resolveWorkspaceContext.bind(this.sessionManager) + getSession: async () => null, + getMessage: async () => null }) const newSessionHooksBridge = new NewSessionHooksBridge(this.hooksNotifications) @@ -231,6 +230,12 @@ export class Presenter implements IPresenter { this.devicePresenter ) + // Update hooksNotifications with actual dependencies now that newAgentPresenter is ready + this.hooksNotifications = new HooksNotificationsService(this.configPresenter, { + getSession: this.newAgentPresenter.getSession.bind(this.newAgentPresenter), + getMessage: this.newAgentPresenter.getMessage.bind(this.newAgentPresenter) + }) + this.setupEventBus() // 设置事件总线监听 } From 72a3a92f0b084cea49354becf826d8032aa611ea Mon Sep 17 00:00:00 2001 From: zerob13 Date: Mon, 9 Mar 2026 20:59:54 +0800 Subject: [PATCH 2/6] fix: i18n input @ --- P0_DESIGN_DECISIONS.md => docs/P0_DESIGN_DECISIONS.md | 0 .../P0_IMPLEMENTATION_SUMMARY.md | 0 src/renderer/src/i18n/da-DK/chat.json | 2 +- src/renderer/src/i18n/en-US/chat.json | 2 +- src/renderer/src/i18n/fa-IR/chat.json | 2 +- src/renderer/src/i18n/fr-FR/chat.json | 2 +- src/renderer/src/i18n/he-IL/chat.json | 2 +- src/renderer/src/i18n/ja-JP/chat.json | 2 +- src/renderer/src/i18n/ko-KR/chat.json | 2 +- src/renderer/src/i18n/pt-BR/chat.json | 2 +- src/renderer/src/i18n/ru-RU/chat.json | 2 +- src/renderer/src/i18n/zh-CN/chat.json | 2 +- src/renderer/src/i18n/zh-HK/chat.json | 2 +- src/renderer/src/i18n/zh-TW/chat.json | 2 +- 14 files changed, 12 insertions(+), 12 deletions(-) rename P0_DESIGN_DECISIONS.md => docs/P0_DESIGN_DECISIONS.md (100%) rename P0_IMPLEMENTATION_SUMMARY.md => docs/P0_IMPLEMENTATION_SUMMARY.md (100%) diff --git a/P0_DESIGN_DECISIONS.md b/docs/P0_DESIGN_DECISIONS.md similarity index 100% rename from P0_DESIGN_DECISIONS.md rename to docs/P0_DESIGN_DECISIONS.md diff --git a/P0_IMPLEMENTATION_SUMMARY.md b/docs/P0_IMPLEMENTATION_SUMMARY.md similarity index 100% rename from P0_IMPLEMENTATION_SUMMARY.md rename to docs/P0_IMPLEMENTATION_SUMMARY.md diff --git a/src/renderer/src/i18n/da-DK/chat.json b/src/renderer/src/i18n/da-DK/chat.json index 5c298e734..ba329a06c 100644 --- a/src/renderer/src/i18n/da-DK/chat.json +++ b/src/renderer/src/i18n/da-DK/chat.json @@ -19,7 +19,7 @@ "historyPlaceholder": "(Udfyld med Tab)", "inputArea": "Indtastningsområde", "pasteFiles": "Understøtter kopiering og indsættelse af filer", - "placeholder": "Spørg DeepChat om hvad som helst, @ for at nævne filer, / for kommandoer", + "placeholder": "Spørg DeepChat om hvad som helst, {'@'} for at nævne filer, / for kommandoer", "promptFilesAdded": "Prompt-filen er blevet tilføjet", "promptFilesAddedDesc": "{count} filer er blevet tilføjet successfully", "promptFilesError": "Filbehandlingsfejl", diff --git a/src/renderer/src/i18n/en-US/chat.json b/src/renderer/src/i18n/en-US/chat.json index f5e42647a..174ef50a8 100644 --- a/src/renderer/src/i18n/en-US/chat.json +++ b/src/renderer/src/i18n/en-US/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "Ask DeepChat anything, @ to mention files, / for commands", + "placeholder": "Ask DeepChat anything, {'@'} to mention files, / for commands", "fileArea": "File Area", "inputArea": "Input Area", "functionSwitch": "Function Switch", diff --git a/src/renderer/src/i18n/fa-IR/chat.json b/src/renderer/src/i18n/fa-IR/chat.json index 9d52ee743..94046270a 100644 --- a/src/renderer/src/i18n/fa-IR/chat.json +++ b/src/renderer/src/i18n/fa-IR/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "هر چیزی از DeepChat بپرسید، با @ به فایل‌ها اشاره کنید و با / از دستورها استفاده کنید", + "placeholder": "هر چیزی از DeepChat بپرسید، با {'@'} به فایل‌ها اشاره کنید و با / از دستورها استفاده کنید", "fileArea": "ناحیه پرونده", "inputArea": "ناحیه ورودی", "functionSwitch": "کلید کارکرد", diff --git a/src/renderer/src/i18n/fr-FR/chat.json b/src/renderer/src/i18n/fr-FR/chat.json index 358c1f584..7148d7792 100644 --- a/src/renderer/src/i18n/fr-FR/chat.json +++ b/src/renderer/src/i18n/fr-FR/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "Demandez n'importe quoi à DeepChat, @ pour mentionner des fichiers, / pour les commandes", + "placeholder": "Demandez n'importe quoi à DeepChat, {'@'} pour mentionner des fichiers, / pour les commandes", "fileArea": "Zone de fichiers", "inputArea": "Zone de saisie", "functionSwitch": "Commutateur de fonction", diff --git a/src/renderer/src/i18n/he-IL/chat.json b/src/renderer/src/i18n/he-IL/chat.json index 44d0ca47a..2d546699c 100644 --- a/src/renderer/src/i18n/he-IL/chat.json +++ b/src/renderer/src/i18n/he-IL/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "שאלו את DeepChat כל דבר, השתמשו ב-@ כדי להזכיר קבצים וב-/ לפקודות", + "placeholder": "שאלו את DeepChat כל דבר, השתמשו ב-{'@'} כדי להזכיר קבצים וב-/ לפקודות", "fileArea": "אזור קבצים", "inputArea": "אזור קלט", "functionSwitch": "מתג פונקציות", diff --git a/src/renderer/src/i18n/ja-JP/chat.json b/src/renderer/src/i18n/ja-JP/chat.json index 9ab4b195a..8d228bfde 100644 --- a/src/renderer/src/i18n/ja-JP/chat.json +++ b/src/renderer/src/i18n/ja-JP/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "DeepChat に何でも聞いてください。@ でファイルをメンション、/ でコマンドを使用できます", + "placeholder": "DeepChat に何でも聞いてください。{'@'} でファイルをメンション、/ でコマンドを使用できます", "fileArea": "ファイルエリア", "inputArea": "入力エリア", "functionSwitch": "機能スイッチ", diff --git a/src/renderer/src/i18n/ko-KR/chat.json b/src/renderer/src/i18n/ko-KR/chat.json index 73cb91d40..c229f4164 100644 --- a/src/renderer/src/i18n/ko-KR/chat.json +++ b/src/renderer/src/i18n/ko-KR/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "DeepChat에 무엇이든 물어보세요. @로 파일을 언급하고 /로 명령을 사용할 수 있습니다", + "placeholder": "DeepChat에 무엇이든 물어보세요. {'@'}로 파일을 언급하고 /로 명령을 사용할 수 있습니다", "fileArea": "파일 영역", "inputArea": "입력 영역", "functionSwitch": "기능 전환", diff --git a/src/renderer/src/i18n/pt-BR/chat.json b/src/renderer/src/i18n/pt-BR/chat.json index 4324384b5..fd5672014 100644 --- a/src/renderer/src/i18n/pt-BR/chat.json +++ b/src/renderer/src/i18n/pt-BR/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "Pergunte qualquer coisa ao DeepChat, @ para mencionar arquivos, / para comandos", + "placeholder": "Pergunte qualquer coisa ao DeepChat, {'@'} para mencionar arquivos, / para comandos", "fileArea": "Área de Arquivos", "inputArea": "Área de Entrada", "functionSwitch": "Alternar Função", diff --git a/src/renderer/src/i18n/ru-RU/chat.json b/src/renderer/src/i18n/ru-RU/chat.json index b1eef0135..b5f2ea474 100644 --- a/src/renderer/src/i18n/ru-RU/chat.json +++ b/src/renderer/src/i18n/ru-RU/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "Спросите DeepChat о чем угодно, @ чтобы упомянуть файлы, / для команд", + "placeholder": "Спросите DeepChat о чем угодно, {'@'} чтобы упомянуть файлы, / для команд", "fileArea": "Область файлов", "inputArea": "Область ввода", "functionSwitch": "Переключатель функций", diff --git a/src/renderer/src/i18n/zh-CN/chat.json b/src/renderer/src/i18n/zh-CN/chat.json index df3e78f9f..1c1fa68dc 100644 --- a/src/renderer/src/i18n/zh-CN/chat.json +++ b/src/renderer/src/i18n/zh-CN/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "向 DeepChat 发送消息,@ 可引用文件,/ 可使用命令", + "placeholder": "向 DeepChat 发送消息,{'@'} 可引用文件,/ 可使用命令", "fileArea": "文件区域", "inputArea": "输入区域", "functionSwitch": "功能开关", diff --git a/src/renderer/src/i18n/zh-HK/chat.json b/src/renderer/src/i18n/zh-HK/chat.json index 14bd44c5c..57ae185a9 100644 --- a/src/renderer/src/i18n/zh-HK/chat.json +++ b/src/renderer/src/i18n/zh-HK/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "向 DeepChat 發送訊息,@ 可引用檔案,/ 可使用指令", + "placeholder": "向 DeepChat 發送訊息,{'@'} 可引用檔案,/ 可使用指令", "fileArea": "文件區域", "inputArea": "輸入區域", "functionSwitch": "功能開關", diff --git a/src/renderer/src/i18n/zh-TW/chat.json b/src/renderer/src/i18n/zh-TW/chat.json index 84561a3c1..e3dbfd571 100644 --- a/src/renderer/src/i18n/zh-TW/chat.json +++ b/src/renderer/src/i18n/zh-TW/chat.json @@ -1,6 +1,6 @@ { "input": { - "placeholder": "向 DeepChat 傳送訊息,@ 可引用檔案,/ 可使用指令", + "placeholder": "向 DeepChat 傳送訊息,{'@'} 可引用檔案,/ 可使用指令", "fileArea": "檔案區域", "inputArea": "輸入區域", "functionSwitch": "功能切換", From 8af7f2f3c137d2c66d1fcd3c00ef2130e5639ed2 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Mon, 9 Mar 2026 22:04:05 +0800 Subject: [PATCH 3/6] docs: update docs for new arch --- GAP_ANALYSIS_SUMMARY.md | 138 -- docs/ARCHITECTURE.md | 2 + docs/FLOWS.md | 2 + docs/P0_DESIGN_DECISIONS.md | 285 ---- docs/P0_IMPLEMENTATION_SUMMARY.md | 369 ------ docs/architecture/agent-system.md | 1152 +++++------------ .../new-ui-implementation-plan.md | 9 + docs/specs/p0-implementation/README.md | 205 --- .../feature-01-generating-session-ids/plan.md | 246 ---- .../feature-01-generating-session-ids/spec.md | 246 ---- .../tasks.md | 458 ------- .../feature-02-input-disable-stop/plan.md | 347 ----- .../feature-02-input-disable-stop/spec.md | 210 --- .../feature-02-input-disable-stop/tasks.md | 531 -------- .../feature-03-cancel-generating/plan.md | 109 -- .../feature-03-cancel-generating/spec.md | 204 --- .../feature-03-cancel-generating/tasks.md | 128 -- .../feature-04-permission-approval/plan.md | 119 -- .../feature-04-permission-approval/spec.md | 180 --- .../feature-04-permission-approval/tasks.md | 202 --- .../feature-05-session-list-refresh/plan.md | 108 -- .../feature-05-session-list-refresh/spec.md | 179 --- .../feature-05-session-list-refresh/tasks.md | 147 --- .../feature-06-optimistic-messages/plan.md | 121 -- .../feature-06-optimistic-messages/spec.md | 193 --- .../feature-06-optimistic-messages/tasks.md | 188 --- .../feature-07-cache-versioning/plan.md | 114 -- .../feature-07-cache-versioning/spec.md | 179 --- .../feature-07-cache-versioning/tasks.md | 203 --- 29 files changed, 310 insertions(+), 6264 deletions(-) delete mode 100644 GAP_ANALYSIS_SUMMARY.md delete mode 100644 docs/P0_DESIGN_DECISIONS.md delete mode 100644 docs/P0_IMPLEMENTATION_SUMMARY.md delete mode 100644 docs/specs/p0-implementation/README.md delete mode 100644 docs/specs/p0-implementation/feature-01-generating-session-ids/plan.md delete mode 100644 docs/specs/p0-implementation/feature-01-generating-session-ids/spec.md delete mode 100644 docs/specs/p0-implementation/feature-01-generating-session-ids/tasks.md delete mode 100644 docs/specs/p0-implementation/feature-02-input-disable-stop/plan.md delete mode 100644 docs/specs/p0-implementation/feature-02-input-disable-stop/spec.md delete mode 100644 docs/specs/p0-implementation/feature-02-input-disable-stop/tasks.md delete mode 100644 docs/specs/p0-implementation/feature-03-cancel-generating/plan.md delete mode 100644 docs/specs/p0-implementation/feature-03-cancel-generating/spec.md delete mode 100644 docs/specs/p0-implementation/feature-03-cancel-generating/tasks.md delete mode 100644 docs/specs/p0-implementation/feature-04-permission-approval/plan.md delete mode 100644 docs/specs/p0-implementation/feature-04-permission-approval/spec.md delete mode 100644 docs/specs/p0-implementation/feature-04-permission-approval/tasks.md delete mode 100644 docs/specs/p0-implementation/feature-05-session-list-refresh/plan.md delete mode 100644 docs/specs/p0-implementation/feature-05-session-list-refresh/spec.md delete mode 100644 docs/specs/p0-implementation/feature-05-session-list-refresh/tasks.md delete mode 100644 docs/specs/p0-implementation/feature-06-optimistic-messages/plan.md delete mode 100644 docs/specs/p0-implementation/feature-06-optimistic-messages/spec.md delete mode 100644 docs/specs/p0-implementation/feature-06-optimistic-messages/tasks.md delete mode 100644 docs/specs/p0-implementation/feature-07-cache-versioning/plan.md delete mode 100644 docs/specs/p0-implementation/feature-07-cache-versioning/spec.md delete mode 100644 docs/specs/p0-implementation/feature-07-cache-versioning/tasks.md diff --git a/GAP_ANALYSIS_SUMMARY.md b/GAP_ANALYSIS_SUMMARY.md deleted file mode 100644 index 571f6f60c..000000000 --- a/GAP_ANALYSIS_SUMMARY.md +++ /dev/null @@ -1,138 +0,0 @@ -# Architecture Gap Analysis - Executive Summary - -**Date**: 2026-02-28 -**Author**: Subagent (document-arch-gap-analysis) -**Status**: ✅ COMPLETE - Ready for implementation - ---- - -## TL;DR - -The new `deepchatAgentPresenter` architecture has **excellent streaming and message persistence** infrastructure, but is **missing the entire permission flow**. This is a **P0 MVP blocker** that requires immediate attention. - ---- - -## What's Working ✅ - -1. **Session Management** - `newAgentPresenter` creates/activates/deletes sessions correctly -2. **Message Streaming** - `processStream()` handles LLM streaming with tool calls -3. **Message Persistence** - `deepchat_messages` table stores messages correctly -4. **Event System** - `SESSION_EVENTS` and `STREAM_EVENTS` work end-to-end -5. **Frontend Stores** - `sessionStore` and `messageStore` handle state correctly - ---- - -## What's Broken 🔴 - -### Critical: Permission Flow Missing - -**Problem**: `executeTools()` in `dispatch.ts` calls tools **without any permission checks**. - -```typescript -// Current code (BROKEN): -for (const tc of state.completedToolCalls) { - const { rawData } = await toolPresenter.callTool(toolCall) // ← NO PERMISSION CHECK! - // ... -} -``` - -**Required Fix**: -1. Create `PermissionChecker` class -2. Check permissions BEFORE calling tools -3. Create permission request blocks -4. Pause stream and wait for user approval -5. Resume after approval - -**Files to Modify**: -- `src/main/presenter/deepchatAgentPresenter/dispatch.ts` - add permission check -- `src/main/presenter/deepchatAgentPresenter/permissionChecker.ts` - CREATE NEW -- `src/main/presenter/newAgentPresenter/index.ts` - add `handlePermissionResponse()` -- `src/renderer/src/components/chat/ChatStatusBar.vue` - convert to dropdown - -**Database Changes**: -- Add `permission_mode TEXT DEFAULT 'default'` to `new_sessions` table -- Create `permission_whitelists` table - ---- - -## Other Missing Features 🟡 - -### Message Operations (P1 - High Priority) - -1. **Edit User Message** - Not implemented -2. **Retry/Regenerate** - Not implemented (no variants, just append) -3. **Fork Session** - Not implemented - -### Session Configuration 🟢 (P2 - Medium) - -- Currently only stores: `providerId`, `modelId` -- Missing: `temperature`, `contextLength`, `maxTokens`, `systemPrompt` -- Can use defaults for MVP - ---- - -## Implementation Priority - -### P0: Critical (MVP Blockers) - 1-2 weeks - -1. ✅ Add `permission_mode` to `new_sessions` table -2. 🔴 Create `PermissionChecker` class -3. 🔴 Integrate permission check in `executeTools()` -4. 🔴 Add `handlePermissionResponse()` IPC method -5. 🔴 Update `ChatStatusBar` with permission dropdown -6. 🔴 Implement whitelist storage and matching -7. 🔴 Enforce `projectDir` boundary in full access mode - -### P1: High (Core Functionality) - 1 week - -1. Implement `editUserMessage()` -2. Implement `retryMessage()` (no variants) -3. Implement `forkSessionFromMessage()` -4. Frontend UI for message actions - -### P2: Medium (Nice to Have) - 3-5 days - -1. Extend session configuration (temperature, etc.) -2. Add 'paused' status for permission wait state -3. Error recovery improvements - ---- - -## Detailed Documentation - -See `docs/specs/agentpresenter-mvp-replacement/gap-analysis.md` for: -- Complete functional comparison (old vs new) -- Architecture diagrams -- Implementation details -- File reference map - ---- - -## Next Steps - -1. **Review gap-analysis.md** with Claude -2. **Start with P0 permission flow** implementation -3. **Create database migration** for `permission_mode` and whitelists -4. **Implement PermissionChecker** class -5. **Test end-to-end permission flow** - ---- - -## Files Created/Modified - -**Created**: -- `docs/specs/agentpresenter-mvp-replacement/gap-analysis.md` (29KB) - -**Modified**: -- `docs/specs/agentpresenter-mvp-replacement/spec.md` - Added implementation notes -- `docs/specs/agentpresenter-mvp-replacement/plan.md` - Updated phases with status -- `docs/specs/agentpresenter-mvp-replacement/tasks.md` - Added detailed tasks - -**Committed**: ✅ Yes (commit fc17e245) -**Pushed**: ❌ No (authentication required) - ---- - -## Contact - -Questions? Review the full gap analysis or reach out to the development team. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index af7340618..067d55648 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,6 +2,8 @@ 本文档从高层视角介绍 DeepChat 的系统架构,帮助开发者快速理解项目结构和组件关系。 +> **Note (2026-03-09):** 本文档描述的是原始 AgentPresenter 架构。新架构(P0 实现)使用 `newAgentPresenter` + `deepchatAgentPresenter` 作为主要入口,详见 [P0 Implementation Summary](./P0_IMPLEMENTATION_SUMMARY.md)。 + ## 🏗️ 核心组件关系 ```mermaid diff --git a/docs/FLOWS.md b/docs/FLOWS.md index b9a458f9c..11f80e95b 100644 --- a/docs/FLOWS.md +++ b/docs/FLOWS.md @@ -2,6 +2,8 @@ 本文档使用时序图详细描述 DeepChat 的关键业务流程,帮助开发者理解运行时行为。 +> **Note (2026-03-09):** 本文档描述的是原始 AgentPresenter 流程。新架构流程(newAgentPresenter + deepchatAgentPresenter)已实现,核心流程类似但入口不同。详见 [P0 Implementation Summary](./P0_IMPLEMENTATION_SUMMARY.md)。 + ## 1. 发送消息完整流程 ```mermaid diff --git a/docs/P0_DESIGN_DECISIONS.md b/docs/P0_DESIGN_DECISIONS.md deleted file mode 100644 index c8514d520..000000000 --- a/docs/P0_DESIGN_DECISIONS.md +++ /dev/null @@ -1,285 +0,0 @@ -# P0 Design Decisions - -**Date:** 2026-02-28 -**Branch:** `feat/new-arch-complete` -**Status:** ✅ Finalized - Ready for Implementation - ---- - -## Context - -This document captures all design decisions made for P0 critical features. These decisions were reviewed and finalized based on spec-driven development methodology. - ---- - -## Decision 1: generatingSessionIds Update Timing - -**Status:** ✅ DECIDED - -### Problem -When should we add/remove session IDs from the `generatingSessionIds` Set? - -### Decision: **Option A - Frontend Control** - -```typescript -// Add immediately when sending message -sessionStore.generatingSessionIds.add(sessionId) - -// Remove on END/ERROR events -sessionStore.generatingSessionIds.delete(sessionId) -``` - -### Rationale - -After analysis, **Frontend Control** provides the best UX: -- **Immediate feedback**: UI responds instantly when user sends message -- **Simpler implementation**: No need to wait for backend STATUS_CHANGED event -- **Consistent with optimistic UI**: Matches Feature 6 (Optimistic Messages) pattern -- **Backend events as backup**: END/ERROR events ensure cleanup even if frontend logic fails - -### Implementation - -See: `docs/specs/p0-implementation/feature-01-generating-session-ids/` - ---- - -## Decision 2: cancelGenerating Message State - -**Status:** ✅ DECIDED - -### Decision - -- **Keep partial content** ✅ -- **Message status**: `'cancelled'` (new status type) -- **User can manually send new message** ✅ -- **No automatic retry** - -### Rationale - -- **Partial content**: Users should see what was generated before cancel (better UX) -- **Cancelled status**: More accurate than 'completed', helps with analytics and debugging -- **Manual retry**: Gives user control, avoids accidental regeneration - -### Implementation - -See: `docs/specs/p0-implementation/feature-03-cancel-generating/` - ---- - -## Decision 3: Permission Mode Change Behavior - -**Status:** ✅ DECIDED - -### Decision: **Option B - Only Affects New Requests** - -- Pending requests stay pending -- New tool calls use new mode -- User must manually approve/deny pending requests - -### Rationale - -**Safety first**: Permission changes should be explicit. If user switches from Default to Full, they should consciously approve pending requests rather than having them auto-approved. - -### Implementation - -See: `docs/specs/p0-implementation/feature-04-permission-approval/` - ---- - -## Decision 4: Input Box Disable Strategy - -**Status:** ✅ DECIDED - -### Decision - -- Input box disabled when `isGenerating(sessionId) === true` -- Disabled state applied via ChatInputBox `disabled` prop -- Stop button shown when generating -- Stop button click triggers cancelGenerating - -### Rationale - -**Clear visual feedback**: Users should immediately understand when system is busy and have control to stop. - -### Implementation - -See: `docs/specs/p0-implementation/feature-02-input-disable-stop/` - ---- - -## Decision 5: Session List Auto-Refresh - -**Status:** ✅ DECIDED - -### Decision - -- Listen to `CONVERSATION_EVENTS.LIST_UPDATED` -- Automatic refresh on create/delete/rename -- Cross-tab synchronization via EventBus -- No manual refresh button needed (fallback only) - -### Rationale - -**Always in sync**: Users should never need to manually refresh. Event-driven architecture ensures all tabs stay synchronized. - -### Implementation - -See: `docs/specs/p0-implementation/feature-05-session-list-refresh/` - ---- - -## Decision 6: Optimistic User Messages - -**Status:** ✅ DECIDED - -### Decision - -- Show user message immediately with temp ID -- Mark as `pending: true` -- Merge with real message when backend returns -- Remove on error with user notification - -### Rationale - -**Instant feedback**: Zero perceived latency. Users see their message immediately, making the app feel responsive. - -### Implementation - -See: `docs/specs/p0-implementation/feature-06-optimistic-messages/` - ---- - -## Decision 7: Message Cache Version Bumping - -**Status:** ✅ DECIDED - -### Decision - -- Cache key includes version: `messages-v{VERSION}-{sessionId}` -- Version constant defined in code -- Automatic invalidation on version mismatch -- Document version bump process - -### Rationale - -**Future-proof**: Prevents stale cache issues when schema changes. Makes migrations safe and predictable. - -### Implementation - -See: `docs/specs/p0-implementation/feature-07-cache-versioning/` - ---- - -## Decision 8: Implementation Strategy - -**Status:** ✅ DECIDED - -### Approach - -**Incremental with Testing:** -- Each feature = separate commit -- Test immediately after each commit -- Roll back individual features if issues found -- Integration test after all features implemented - -### Testing Checklist - -``` -Feature 1 (generatingSessionIds): - [ ] Send message → Set contains session ID - [ ] Receive END → Set doesn't contain session ID - -Feature 2 (cancelGenerating): - [ ] Click Stop → generation cancels - [ ] After cancel → Set doesn't contain session ID - -Feature 3 (Input disable): - [ ] Send message → input disabled - [ ] Click Stop → input re-enabled - -Feature 4 (Permission flow): - [ ] Tool call → permission dialog shown - [ ] Approve → tool executes - [ ] Deny → error shown - -Feature 5 (Session list): - [ ] Create session → list updates - [ ] Delete session → list updates - [ ] Cross-tab sync works - -Feature 6 (Optimistic messages): - [ ] Send message → appears immediately - [ ] Backend returns → seamless merge - -Feature 7 (Cache versioning): - [ ] Cache works with version - [ ] Version bump → cache invalidated -``` - ---- - -## Implementation Order - -**Phase 1: Core UI Bindings (Features 1-3)** -1. Feature 1: generatingSessionIds tracking -2. Feature 2: Input box disable + stop button -3. Feature 3: cancelGenerating implementation - -**Phase 2: Backend Integration (Feature 4)** -4. Feature 4: Permission approval flow - -**Phase 3: UX Polish (Features 5-7)** -5. Feature 5: Session list auto-refresh -6. Feature 6: Optimistic user messages -7. Feature 7: Message cache version bumping - ---- - -## Architecture References - -All P0 features integrate with: - -- **New Agent Presenter**: `src/main/presenter/newAgentPresenter/` -- **DeepChat Agent Presenter**: `src/main/presenter/deepchatAgentPresenter/` -- **Session Store**: `src/renderer/src/stores/session.ts` -- **Message Store**: `src/renderer/src/stores/message.ts` -- **Event System**: `src/main/eventbus.ts` and `src/main/events.ts` - -### Related Documentation - -- [P0 Implementation README](./docs/specs/p0-implementation/README.md) -- [Event System](./docs/architecture/event-system.md) -- [Agent System](./docs/architecture/agent-system.md) -- [AgentPresenter MVP Replacement](./docs/specs/agentpresenter-mvp-replacement/) - ---- - -## Quality Gates - -Before marking any feature as complete: - -- [ ] All spec requirements implemented -- [ ] Unit tests passing -- [ ] Integration tests passing -- [ ] Type check passing (`pnpm run typecheck`) -- [ ] Lint passing (`pnpm run lint`) -- [ ] Format passing (`pnpm run format`) -- [ ] Manual testing completed -- [ ] Edge cases validated - ---- - -## Next Steps - -1. ✅ All design decisions finalized -2. ✅ All specifications written -3. ✅ All implementation plans created -4. ✅ All task lists defined -5. ⏳ Review documentation with team -6. ⏳ Begin Phase 1 implementation - ---- - -**Status:** ✅ Finalized -**Last Updated:** 2026-02-28 -**Ready for:** Implementation diff --git a/docs/P0_IMPLEMENTATION_SUMMARY.md b/docs/P0_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 436be1b7b..000000000 --- a/docs/P0_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,369 +0,0 @@ -# P0 Implementation Summary - -**Date:** 2026-02-28 -**Branch:** `feat/new-arch-complete` -**Status:** 📝 Documentation Complete - Ready for Implementation - ---- - -## Executive Summary - -Comprehensive spec-driven documentation has been created for all 7 P0 critical features. This documentation follows the project's established spec-driven development pattern and provides complete, actionable guidance for implementation. - -### Documentation Deliverables - -✅ **1 README** - Overview of all P0 features -✅ **7 Spec Documents** - Detailed specifications for each feature -✅ **7 Plan Documents** - Implementation plans with phases -✅ **7 Tasks Documents** - Granular implementation tasks with code examples -✅ **1 Design Decisions** - All user decisions documented and finalized -✅ **1 Implementation Summary** - This document - -**Total Documents:** 25 files -**Total Lines of Code:** ~3,500+ lines of documentation -**Estimated Implementation Time:** 2-3 days - ---- - -## P0 Features Overview - -### Feature 1: Generating Session IDs Tracking -**Priority:** P0 | **Estimate:** 2-3 hours | **Risk:** Low - -- Frontend-managed Set tracking generating sessions -- Add on send, remove on END/ERROR -- Enables reactive UI updates -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-01-generating-session-ids/` - ---- - -### Feature 2: Input Box Disable + Stop Button -**Priority:** P0 | **Estimate:** 2-3 hours | **Risk:** Low - -- ChatInputBox disabled prop -- StopButton component with show/hide logic -- Visual feedback during generation -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-02-input-disable-stop/` - ---- - -### Feature 3: CancelGenerating Implementation -**Priority:** P0 | **Estimate:** 1-2 days | **Risk:** Medium - -- Backend cancelGeneration IPC method -- Stream abort controller integration -- Message marked as 'cancelled' with partial content -- User can resend after cancel -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-03-cancel-generating/` - ---- - -### Feature 4: Permission Approval Flow -**Priority:** P0 | **Estimate:** 2-3 days | **Risk:** Medium - -- PermissionChecker class for tool calls -- Permission dialog UI -- Whitelist storage for remembered decisions -- Backend pauses on permission request -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-04-permission-approval/` - ---- - -### Feature 5: Session List Auto-Refresh -**Priority:** P0 | **Estimate:** 2-3 hours | **Risk:** Low - -- Listen to CONVERSATION_EVENTS.LIST_UPDATED -- Automatic refresh on create/delete/rename -- Cross-tab synchronization -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-05-session-list-refresh/` - ---- - -### Feature 6: Optimistic User Messages -**Priority:** P0 | **Estimate:** 3-4 hours | **Risk:** Low - -- Show user message immediately with temp ID -- Merge with real message on backend response -- Error handling with rollback -- Zero perceived latency -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-06-optimistic-messages/` - ---- - -### Feature 7: Message Cache Version Bumping -**Priority:** P0 | **Estimate:** 1-2 hours | **Risk:** Low - -- Versioned cache keys -- Automatic invalidation on version mismatch -- Virtual scroll compatibility -- Future-proof cache management -- **Status:** ✅ Spec Complete - -📁 Location: `docs/specs/p0-implementation/feature-07-cache-versioning/` - ---- - -## Implementation Roadmap - -### Phase 1: Core UI Bindings (Day 1) -**Features:** 1, 2, 3 -**Focus:** Generation state tracking and user controls - -``` -Morning: - ✅ Feature 1: generatingSessionIds tracking (2-3h) - ✅ Feature 2: Input box disable + stop button (2-3h) - -Afternoon: - ✅ Feature 3: cancelGenerating implementation (1-2d) - -End of Day 1: - ✅ Test end-to-end generation flow - ✅ Validate cancel behavior -``` - -### Phase 2: Backend Integration (Day 2) -**Features:** 4 -**Focus:** Permission flow and security - -``` -Morning: - ✅ PermissionChecker class - ✅ Integrate with tool execution - -Afternoon: - ✅ Permission dialog UI - ✅ handlePermissionResponse IPC - -End of Day 2: - ✅ Test permission boundaries - ✅ Test whitelist persistence -``` - -### Phase 3: UX Polish (Day 3) -**Features:** 5, 6, 7 -**Focus:** Performance and user experience - -``` -Morning: - ✅ Feature 5: Session list auto-refresh (2-3h) - ✅ Feature 6: Optimistic user messages (3-4h) - -Afternoon: - ✅ Feature 7: Message cache versioning (1-2h) - ✅ Integration testing - -End of Day 3: - ✅ Full regression testing - ✅ Performance validation - ✅ Documentation review -``` - ---- - -## Quality Assurance - -### Testing Requirements - -Each feature includes: - -1. **Unit Tests** - Component/function level -2. **Integration Tests** - End-to-end flows -3. **Manual Testing** - User experience validation -4. **Edge Case Testing** - Boundary conditions -5. **Accessibility Testing** - WCAG compliance (where applicable) - -### Quality Gates - -Before merging any feature: - -- [ ] All spec requirements implemented -- [ ] Unit tests passing (≥90% coverage) -- [ ] Integration tests passing -- [ ] Type check passing (`pnpm run typecheck`) -- [ ] Lint passing (`pnpm run lint`) -- [ ] Format passing (`pnpm run format`) -- [ ] Manual testing completed -- [ ] Edge cases validated -- [ ] No console errors or warnings -- [ ] Documentation updated - ---- - -## Risk Assessment - -### Low Risk Features -- Feature 1: Generating Session IDs (frontend-only) -- Feature 2: Input Box Disable (frontend-only) -- Feature 5: Session List Auto-Refresh (event-driven) -- Feature 7: Cache Versioning (frontend-only) - -### Medium Risk Features -- Feature 3: CancelGenerating (backend stream abort) -- Feature 4: Permission Approval (security-critical) -- Feature 6: Optimistic Messages (merge logic) - -### Mitigation Strategies - -1. **Incremental Implementation** - Each feature independently testable -2. **Rollback Plan** - Each feature can be reverted individually -3. **Feature Flags** - Can disable features if issues found -4. **Comprehensive Testing** - Multiple test layers catch issues early - ---- - -## Dependencies Map - -``` -Feature 1 (generatingSessionIds) - └─> No dependencies - └─> Used by: Feature 2, 3, 6 - -Feature 2 (Input Disable) - └─> Depends on: Feature 1 - └─> Used by: Feature 3 - -Feature 3 (cancelGenerating) - └─> Depends on: Feature 1, 2 - └─> Used by: None - -Feature 4 (Permission Flow) - └─> Depends on: None - └─> Used by: None - -Feature 5 (Session List) - └─> Depends on: None - └─> Used by: None - -Feature 6 (Optimistic Messages) - └─> Depends on: Feature 1 - └─> Used by: None - -Feature 7 (Cache Versioning) - └─> Depends on: None - └─> Used by: None -``` - ---- - -## Technical Debt - -### Known Limitations - -1. **Feature 3 (CancelGenerating):** - - Tool execution abort may not be immediate for all tools - - Future enhancement: Add timeout mechanism - -2. **Feature 4 (Permission Flow):** - - Whitelist is session-scoped (not global) - - Future enhancement: Add global whitelist option - -3. **Feature 6 (Optimistic Messages):** - - No offline support yet - - Future enhancement: Queue messages for retry - -### Future Enhancements - -- Add message retry mechanism (Feature 6) -- Add global permission whitelist (Feature 4) -- Add cancel timeout fallback (Feature 3) -- Add optimistic UI for assistant messages (future P1 feature) - ---- - -## Success Metrics - -### Implementation Success - -- [ ] All 7 features implemented according to spec -- [ ] All tests passing -- [ ] No critical bugs -- [ ] Performance metrics met (<100ms UI response) -- [ ] User feedback positive - -### User Experience Goals - -- **Zero perceived latency** for user messages (Feature 6) -- **Clear visual feedback** during generation (Feature 1, 2) -- **User control** over long-running operations (Feature 3) -- **Security boundaries** respected (Feature 4) -- **Always in sync** across tabs (Feature 5) -- **No stale data** issues (Feature 7) - ---- - -## Team Alignment - -### Roles and Responsibilities - -- **Developer:** Implement features according to specs -- **Tester:** Validate against acceptance criteria -- **Reviewer:** Code review and quality gate enforcement -- **Architect:** Technical guidance and edge case resolution - -### Communication Plan - -- **Daily Standup:** Progress updates, blockers -- **Code Review:** PR comments, feedback loops -- **Testing Reports:** Test results, bug reports -- **Final Demo:** Feature walkthrough, user acceptance - ---- - -## Next Steps - -1. ✅ **Documentation Complete** - All specs written -2. ⏳ **Team Review** - Review docs with team (1 day) -3. ⏳ **Implementation Phase 1** - Features 1-3 (1-2 days) -4. ⏳ **Implementation Phase 2** - Feature 4 (1 day) -5. ⏳ **Implementation Phase 3** - Features 5-7 (1 day) -6. ⏳ **Integration Testing** - Full regression (1 day) -7. ⏳ **User Acceptance** - Demo and approval - ---- - -## Appendix: Document Locations - -### Root Documentation -- `P0_DESIGN_DECISIONS.md` - All design decisions -- `P0_IMPLEMENTATION_SUMMARY.md` - This document - -### Feature Documentation -- `docs/specs/p0-implementation/README.md` - Overview -- `docs/specs/p0-implementation/feature-01-generating-session-ids/` - Spec, Plan, Tasks -- `docs/specs/p0-implementation/feature-02-input-disable-stop/` - Spec, Plan, Tasks -- `docs/specs/p0-implementation/feature-03-cancel-generating/` - Spec, Plan, Tasks -- `docs/specs/p0-implementation/feature-04-permission-approval/` - Spec, Plan, Tasks -- `docs/specs/p0-implementation/feature-05-session-list-refresh/` - Spec, Plan, Tasks -- `docs/specs/p0-implementation/feature-06-optimistic-messages/` - Spec, Plan, Tasks -- `docs/specs/p0-implementation/feature-07-cache-versioning/` - Spec, Plan, Tasks - -### Reference Documentation -- `docs/architecture/event-system.md` - Event system details -- `docs/architecture/agent-system.md` - Agent system details -- `docs/specs/agentpresenter-mvp-replacement/` - MVP replacement spec - ---- - -**Documentation Status:** ✅ Complete -**Implementation Status:** ⏳ Ready to Start -**Estimated Completion:** 3-5 days -**Confidence Level:** High (comprehensive specs, clear tasks) - ---- - -**Last Updated:** 2026-02-28 -**Maintained By:** Development Team -**Next Review:** After implementation phase diff --git a/docs/architecture/agent-system.md b/docs/architecture/agent-system.md index 729445019..1acf0abcf 100644 --- a/docs/architecture/agent-system.md +++ b/docs/architecture/agent-system.md @@ -1,934 +1,376 @@ # Agent 系统架构详解 -本文档详细介绍 Agent 系统的设计和实现,包括 Agent Loop、流生成、事件处理和权限协调。 +本文档详细介绍 Agent 系统的设计和实现,包括 Session 管理、Agent Loop、流生成、事件处理和权限协调。 -## 📋 核心组件概览 +## 架构概览 -| 组件 | 文件位置 | 职责 | -|------|---------|------| -| **AgentPresenter** | `src/main/presenter/agentPresenter/index.ts` | Agent 编排主入口,实现 IAgentPresenter 接口 | -| **agentLoopHandler** | `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts` | Agent Loop 主循环(while 循环) | -| **streamGenerationHandler** | `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts` | 流生成协调,准备上下文、启动 Loop | -| **loopOrchestrator** | `src/main/presenter/agentPresenter/loop/loopOrchestrator.ts` | Loop 状态管理器 | -| **toolCallProcessor** | `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` | 工具调用执行和结果处理 | -| **llmEventHandler** | `src/main/presenter/agentPresenter/streaming/llmEventHandler.ts` | 标准化 LLM 事件 | -| **permissionHandler** | `src/main/presenter/agentPresenter/permission/permissionHandler.ts` | 权限请求响应协调 | -| **messageBuilder** | `src/main/presenter/agentPresenter/message/messageBuilder.ts` | 提示词构建 | -| **contentBufferHandler** | `src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts` | 流式内容缓冲优化 | -| **toolCallHandler** | `src/main/presenter/agentPresenter/loop/toolCallHandler.ts` | 工具调用 UI 块管理 | - -## 🏗️ 架构关系 - -```mermaid -graph TB - subgraph "AgentPresenter 主入口" - AgentP[AgentPresenter] - end +DeepChat 采用两层 Agent 架构: - subgraph "Agent Loop 执行层" - StreamGen[streamGenerationHandler] - AgentLoop[agentLoopHandler] - LoopOrch[loopOrchestrator] - ToolCallProc[toolCallProcessor] - end +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Renderer (IPC) │ +└───────────────────────────────┬─────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ NewAgentPresenter │ +│ (Session Manager - IPC-facing, routing, orchestration) │ +│ │ +│ - Owns AgentRegistry (maps agentId -> implementation) │ +│ - Owns NewSessionManager (session records + window bindings) │ +│ - Routes calls to appropriate agent implementation │ +│ - Handles multi-agent support (deepchat + ACP agents) │ +└───────────────────────────────┬─────────────────────────────────┘ + │ resolves via AgentRegistry + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ DeepChatAgentPresenter │ +│ (Agent Loop - IAgentImplementation for "deepchat" agent) │ +│ │ +│ - Owns SessionStore (runtime state per session) │ +│ - Owns MessageStore (message persistence) │ +│ - Owns CompactionManager (context summarization) │ +│ - Implements processMessage() -> processStream() loop │ +│ - Handles tool execution via ToolPresenter │ +└─────────────────────────────────────────────────────────────────┘ +``` - subgraph "事件处理层" - LLMEvent[llmEventHandler] - ToolCall[toolCallHandler] - BufHandler[contentBufferHandler] - end +## 核心组件 - subgraph "辅助组件" - MessageBuilder[messageBuilder] - PermHandler[permissionHandler] - Utility[utilityHandler] - end +### NewAgentPresenter - Session Manager Layer - AgentP --> StreamGen - AgentP --> PermHandler - AgentP --> Utility +**文件位置**: `src/main/presenter/newAgentPresenter/` - StreamGen --> AgentLoop - StreamGen --> LLMEvent - StreamGen --> MessageBuilder +``` +newAgentPresenter/ +├── index.ts # Main presenter - orchestrates sessions +├── sessionManager.ts # Session CRUD operations +├── messageManager.ts # Message lookup across agents +├── agentRegistry.ts # Agent registration and resolution +└── legacyImportService.ts # Legacy chat import functionality +``` - AgentLoop --> LoopOrch - AgentLoop --> ToolCallProc - AgentLoop --> ToolCall +#### 主要方法 - ToolCallProc --> AgentP - ToolCall --> LLMEvent - LLMEvent --> BufHandler +| 方法 | 用途 | +|------|------| +| `createSession(input, webContentsId)` | 创建新会话并发送第一条消息 | +| `sendMessage(sessionId, content)` | 向现有会话发送消息 | +| `retryMessage(sessionId, messageId)` | 从某条消息重试生成 | +| `editUserMessage(sessionId, messageId, text)` | 编辑用户消息并重新生成 | +| `forkSession(sourceSessionId, targetMessageId)` | 从某条消息分叉会话 | +| `getSessionList(filters)` | 获取会话列表 | +| `activateSession(webContentsId, sessionId)` | 绑定会话到窗口 | +| `deleteSession(sessionId)` | 删除会话并清理资源 | +| `cancelGeneration(sessionId)` | 取消正在进行的生成 | +| `respondToolInteraction(...)` | 响应权限/问题提示 | +| `setSessionModel(sessionId, providerId, modelId)` | 更换模型 | +| `getAgents()` | 获取可用的 agent 列表 | - LoopOrch --> LLMEvent +### DeepChatAgentPresenter - Agent Loop Implementation - classDef entry fill:#e3f2fd - classDef loop fill:#fff3e0 - classDef event fill:#f3e5f5 - classDef util fill:#e8f5e9 +**文件位置**: `src/main/presenter/deepchatAgentPresenter/` - class AgentP entry - class StreamGen,AgentLoop,LoopOrch,ToolCallProc loop - class LLMEvent,ToolCall,BufHandler event - class MessageBuilder,PermHandler,Utility util ``` - -## 🎯 AgentPresenter 主入口 - -### 核心方法 - -```typescript -class AgentPresenter implements IAgentPresenter { - // 1. 发送消息(启动新的 Agent Loop) - async sendMessage( - agentId: string, - content: string, - tabId?: number, - selectedVariantsMap?: Record - ): Promise - - // 2. 继续生成(从断点恢复) - async continueLoop( - agentId: string, - messageId: string, - selectedVariantsMap?: Record - ): Promise - - // 3. 取消生成 - async cancelLoop(messageId: string): Promise - - // 4. 重试消息 - async retryMessage( - messageId: string, - selectedVariantsMap?: Record - ): Promise - - // 5. 从用户消息重新生成 - async regenerateFromUserMessage( - agentId: string, - userMessageId: string, - selectedVariantsMap?: Record - ): Promise - - // 6. 翻译文本 - async translateText(text: string, tabId: number): Promise - - // 7. AI 问答 - async askAI(text: string, tabId: number): Promise - - // 8. 权限响应 - async handlePermissionResponse( - messageId: string, - toolCallId: string, - granted: boolean, - permissionType: 'read' | 'write' | 'all' | 'command', - remember?: boolean - ): Promise - - // 9. 获取请求预览 - async getMessageRequestPreview(agentId: string, messageId?: string): Promise -} +deepchatAgentPresenter/ +├── index.ts # Agent implementation (IAgentImplementation) +├── process.ts # Core stream processing loop +├── dispatch.ts # Tool execution and finalization +├── types.ts # Stream state, process params, results +├── accumulator.ts # Stream event -> block accumulator +├── contextBuilder.ts # Build LLM context from history +├── messageStore.ts # Message persistence (SQLite wrapper) +├── sessionStore.ts # Session runtime state +├── echo.ts # Real-time block streaming to renderer +├── toolOutputGuard.ts # Tool output validation/truncation +├── compactionManager.ts # Context compaction via summarization +└── pendingInteractions.ts # Manage paused tool interactions ``` -**文件位置**:`src/main/presenter/agentPresenter/index.ts:139-365` - -### sendMessage 流程详解 - -```typescript -async sendMessage(agentId, content, tabId, selectedVariantsMap) { - // 1. 创建用户消息 - const userMessage = await messageManager.sendMessage( - agentId, - content, - 'user', - '', - false, - this.buildMessageMetadata(conversation) - ) - - // 2. 创建助手消息(初始为空) - const assistantMessage = await streamGenerationHandler.generateAIResponse( - agentId, - userMessage.id - ) - - // 3. 跟踪生成状态 - this.trackGeneratingMessage(assistantMessage, agentId) - - // 4. 更新会话状态 - await this.updateConversationAfterUserMessage(agentId) - - // 5. 启动 Agent Loop - await sessionManager.startLoop(agentId, assistantMessage.id) - - // 6. 启动流生成 - void StreamGenerationHandler.startStreamCompletion( - agentId, - assistantMessage.id, - selectedVariantsMap - ) - - return assistantMessage -} -``` +#### 主要方法 + +| 方法 | 用途 | +|------|------| +| `initSession(sessionId, config)` | 初始化会话运行时状态 | +| `destroySession(sessionId)` | 清理会话资源 | +| `processMessage(sessionId, input, options)` | 处理用户消息(生成主入口) | +| `getMessages(sessionId)` | 获取会话所有消息 | +| `cancelGeneration(sessionId)` | 中止当前生成 | +| `respondToolInteraction(...)` | 恢复暂停的交互(权限批准/回答问题) | +| `setSessionModel(...)` | 切换 provider/model | +| `setPermissionMode(...)` | 更改权限模式 | + +#### 内部模块职责 + +| 模块 | 职责 | +|------|------| +| `processStream()` | 核心 LLM 循环: stream -> accumulate -> tool_use loop -> finalize | +| `executeTools()` | 执行工具调用,处理权限,构建工具消息 | +| `finalize/finalizeError/finalizePaused` | 消息完成状态处理 | +| `StreamState` | 流式过程中的可变状态(blocks, metadata, tool calls) | +| `accumulate()` | 纯函数: LLM events -> assistant message blocks | +| `DeepChatMessageStore` | 持久化消息到 SQLite | +| `DeepChatSessionStore` | 持久化会话运行时状态 | +| `buildContext()` | 构建 LLM 上下文,包含 token 预算和历史选择 | +| `startEcho()` | 实时流式传输 blocks 到 renderer | +| `ToolOutputGuard` | 验证/截断工具输出 | +| `CompactionManager` | 当上下文过长时总结旧消息 | + +## 核心流程 + +### 发送消息流程 -**文件位置**:`src/main/presenter/agentPresenter/index.ts:139-176` - -### 生成状态跟踪 - -```typescript -private trackGeneratingMessage(message: AssistantMessage, conversationId: string) { - this.generatingMessages.set(message.id, { - message, - conversationId, - startTime: Date.now(), - firstTokenTime: null, - promptTokens: 0, - reasoningStartTime: null, - reasoningEndTime: null, - lastReasoningTime: null - }) -} ``` - -## 🔄 agentLoopHandler - Agent Loop 主循环 - -### 核心结构 - -```typescript -async *startStreamCompletion( - providerId: string, - initialMessages: ChatMessage[], - modelId: string, - eventId: string, - temperature: number, - maxTokens: number, - enabledMcpTools?: string[], - thinkingBudget?: number, - reasoningEffort?: 'minimal'|'low'|'medium'|'high', - verbosity?: 'low'|'medium'|'high', - enableSearch?: boolean, - forcedSearch?: boolean, - searchStrategy?: 'turbo'|'max', - conversationId?: string -): AsyncGenerator +Renderer IPC -> NewAgentPresenter.sendMessage() + -> resolveAgentImplementation(session.agentId) // via AgentRegistry + -> agent.processMessage(sessionId, input) // DeepChatAgentPresenter + -> buildContext() // history -> ChatMessage[] + -> processStream() // LLM loop + -> accumulate() // events -> blocks + -> executeTools() // MCP tool calls + -> finalize() // persist to DB ``` -**文件位置**:`src/main/presenter/agentPresenter/loop/agentLoopHandler.ts:145-668` - -### Agent Loop 主循环逻辑 +### Agent Loop 主循环 ```mermaid flowchart TD - Start([Agent Loop 开始]) --> InitLoop[初始化循环变量
conversationMessages, needContinue, toolCallCount] - InitLoop --> CheckAbort{用户是否中断?} - CheckAbort -->|是| EndLoop([Loop 结束]) - CheckAbort -->|否| CheckMaxCalls{toolCallCount >= MAX?} - - CheckMaxCalls -->|是| SendMax[发送 maximum_tool_calls_reached 事件] - SendMax --> EndLoop - CheckMaxCalls -->|否| ResetNeedContinue[needContinue = false] - - ResetNeedContinue --> GetTools[获取工具定义
getAllToolDefinitions] - GetTools --> CallLLM[调用 provider.coreStream
带 filteredToolDefs] - + Start([processStream 开始]) --> InitState[初始化 StreamState] + InitState --> BuildContext[构建上下文 buildContext] + BuildContext --> CallLLM[调用 provider.coreStream] + CallLLM --> LoopEvents{遍历 LLM Stream 事件} - - LoopEvents --> EventText{text 事件
累积 currentContent} + + LoopEvents --> EventText{text 事件 +累积 content block} EventText --> LoopEvents - - LoopEvents --> EventReasoning{reasoning 事件
累积 currentReasoning} + + LoopEvents --> EventReasoning{reasoning 事件 +累积 reasoning block} EventReasoning --> LoopEvents - - LoopEvents --> EventToolStart{tool_call_start
初始化 currentToolChunks} + + LoopEvents --> EventToolStart{tool_call_start +初始化 tool block} EventToolStart --> LoopEvents - - LoopEvents --> EventToolChunk{tool_call_chunk
累积参数增量} - EventToolChunk --> LoopEvents - + LoopEvents --> EventToolEnd{tool_call_end} - EventToolEnd --> IsACP{providerId == 'acp'?} - - IsACP -->|是| SendACPResult[发送 tool_call: end 事件
ACP 已执行] - IsACP -->|否| PushToolCall[将工具调用加入 currentToolCalls] - PushToolCall --> LoopEvents - - LoopEvents --> EventPermission{permission 事件
发送权限请求} - EventPermission --> ExitLoop[退出循环
等待用户响应] - ExitLoop --> EndLoop - + EventToolEnd --> CheckPermission{需要权限?} + + CheckPermission -->|是| PauseStream[暂停流 +发送 permission block] + PauseStream --> WaitUser[等待用户响应] + WaitUser --> UserResponse{用户批准?} + UserResponse -->|是| ExecuteTool + UserResponse -->|否| DenyTool[记录拒绝] + DenyTool --> LoopEvents + + CheckPermission -->|否| ExecuteTool[执行工具] + ExecuteTool --> AddResult[添加工具结果到上下文] + AddResult --> LoopEvents + LoopEvents --> EventStop{stop 事件} - EventStop --> CheckReason{stop_reason == 'tool_use'?} - - CheckReason -->|是| SetContinue[needContinue = true] - CheckReason -->|否| SetStop[needContinue = false] - SetContinue --> LoopEvents - SetStop --> LoopEvents - - LoopEvents -->|所有事件处理完| AddAssistant[添加 assistant 消息到上下文] - AddAssistant --> CheckNeedTool{needContinue && 有工具调用?} - - CheckNeedTool -->|是| ExecuteTools[执行工具调用
ToolCallProcessor] - ExecuteTools --> ProcessToolLoop{遍历 toolCalls 执行} - ProcessToolLoop --> SendToolEvents[发送工具执行事件] - SendToolEvents --> AddToolResult[添加工具结果到上下文] - AddToolResult --> IncrementCount[toolCallCount++] - IncrementCount --> CheckContinue2{needContinue?} - - CheckContinue2 -->|是| InitLoop - CheckContinue2 -->|否| EndLoop - - CheckNeedTool -->|否| EndLoop - - EndLoop --> SendFinalUsage[发送最终 usage 事件] - SendFinalUsage --> EndStream([发送 END 事件]) + EventStop --> CheckReason{stop_reason?} + + CheckReason -->|tool_use| SetContinue[needContinue = true] + CheckReason -->|end| SetStop[needContinue = false] + + SetContinue --> CheckToolCalls{有待执行工具?} + CheckToolCalls -->|是| ExecuteTools[executeTools] + ExecuteTools --> BuildContext + + CheckToolCalls -->|否| Finalize[finalize] + SetStop --> Finalize + + Finalize --> PersistDB[持久化到 SQLite] + PersistDB --> SendEnd([发送 END 事件]) ``` -**文件位置**:`src/main/presenter/agentPresenter/loop/agentLoopHandler.ts:222-627` - -### 关键代码片段 - -```typescript -// 主循环 -while (needContinueConversation) { - if (abortController.signal.aborted) break - - if (toolCallCount >= MAX_TOOL_CALLS) { - yield { type: 'response', data: { maximum_tool_calls_reached: true } } - break - } - - needContinueConversation = false - let currentContent = '' - let currentToolCalls = [] - - // 获取工具定义 - const toolDefs = await this.getToolPresenter().getAllToolDefinitions({ - enabledMcpTools, - chatMode, - supportsVision: this.currentSupportsVision, - agentWorkspacePath - }) - const filteredToolDefs = await this.filterToolsForChatMode(toolDefs, chatMode, modelId) - - // 调用 LLM - const stream = provider.coreStream( - conversationMessages, - modelId, - modelConfig, - temperature, - maxTokens, - filteredToolDefs - ) - - // 处理流事件 - for await (const chunk of stream) { - switch (chunk.type) { - case 'text': - currentContent += chunk.content - yield { type: 'response', data: { eventId, content: chunk.content } } - break - - case 'tool_call_end': - if (providerId === 'acp') { - // ACP Provider 直接返回执行结果 - yield { type: 'response', data: { eventId, tool_call: 'end', ...completeArgs } } - } else { - // 非 ACP 需要本地执行 - currentToolCalls.push({ id: chunk.tool_call_id, name, arguments: completeArgs }) - } - break - - case 'stop': - needContinueConversation = chunk.stop_reason === 'tool_use' - break - } - } - - // 添加 assistant 消息 - conversationMessages.push({ role: 'assistant', content: currentContent }) - - // 执行工具调用 - if (needContinueConversation && currentToolCalls.length > 0) { - const processor = this.toolCallProcessor.process({...}) - while (true) { - const { value, done } = await processor.next() - if (done) { - toolCallCount = value.toolCallCount - needContinueConversation = value.needContinueConversation - break - } - yield value - } - } -} -``` - -## 🌊 streamGenerationHandler - 流生成协调 - -### 主要职责 - -1. **准备对话上下文** - 获取用户消息、历史消息、处理变体选择 -2. **处理用户消息内容** - 提取 URL、图片等 -3. **执行搜索**(如启用) -4. **构建提示词** - 使用 messageBuilder -5. **启动 LLM Stream** - 调用 llmProviderPresenter -6. **消费流** - 通过 loopOrchestrator - -### startStreamCompletion 流程 - -```typescript -async startStreamCompletion(conversationId: string, queryMsgId?: string, selectedVariantsMap?) { - // 1. 获取生成状态 - const state = this.findGeneratingState(conversationId) - - // 2. 启动 Loop - await sessionManager.startLoop(conversationId, state.message.id) - - // 3. 准备会话上下文 - const { conversation, userMessage, contextMessages } = await this.prepareConversationContext( - conversationId, - queryMsgId, - selectedVariantsMap - ) - - // 4. 解析 workspace context - const { chatMode, agentWorkspacePath } = await sessionManager.resolveWorkspaceContext(conversationId, modelId) - - // 5. 处理用户消息内容(URL、图片) - const { userContent, urlResults, imageFiles } = await this.processUserMessageContent(userMessage) - - // 6. 执行搜索(如果启用) - let searchResults = null - if (userMessage.content.search) { - searchResults = await this.searchHandler.startStreamSearch(conversationId, state.message.id, userContent) - } - - // 7. 构建提示词 - const { finalContent, promptTokens } = await preparePromptContent({ - conversation, - userContent, - contextMessages, - searchResults, - urlResults, - userMessage, - vision: modelConfig?.vision, - imageFiles, - supportsFunctionCall: modelConfig.functionCall, - modelType: modelConfig.type - }) - - // 8. 启动 LLM Stream - const stream = llmProviderPresenter.startStreamCompletion( - providerId, - finalContent, - modelId, - eventId, - temperature, - maxTokens, - enabledMcpTools, - thinkingBudget, - reasoningEffort, - verbosity, - enableSearch, - forcedSearch, - searchStrategy, - conversationId - ) - - // 9. 消费流 - await this.loopOrchestrator.consume(stream) -} -``` - -**文件位置**:`src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts:54-179` - -### continueStreamCompletion - 继续生成 - -```typescript -async continueStreamCompletion(conversationId: string, queryMsgId: string, selectedVariantsMap?) { - // 1. 检查待执行的工具调用(maximum_tool_calls_reached) - const queryMessage = await this.ctx.messageManager.getMessage(queryMsgId) - const content = queryMessage.content as AssistantMessageBlock[] - const lastActionBlock = content.filter((block) => block.type === 'action').pop() - - if (lastActionBlock?.action_type === 'maximum_tool_calls_reached' && lastActionBlock.tool_call) { - // 2. 执行工具调用 - const toolCallResponse = await presenter.mcpPresenter.callTool({ - id: lastActionBlock.tool_call.id, - type: 'function', - function: { - name: lastActionBlock.tool_call.name, - arguments: lastActionBlock.tool_call.params - }, - server: { - name: lastActionBlock.tool_call.server_name, - icons: lastActionBlock.tool_call.server_icons, - description: lastActionBlock.tool_call.server_description - } - }) - - // 3. 发送工具执行事件 - eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, SendTarget.ALL_WINDOWS, { - eventId: state.message.id, - tool_call: 'start', - tool_call_id: toolCall.id, - tool_call_name: toolCall.name, - tool_call_params: toolCall.params, - tool_call_response: toolCallResponse.content - }) - // ... running, end 事件 - } - - // 4. 准备上下文并继续 - const { conversation, contextMessages, userMessage } = await this.prepareConversationContext(...) - const { finalContent } = await preparePromptContent({ - conversation, - userContent: 'continue', // 特殊标记继续 - contextMessages, - searchResults: null, - ... - }) - - // 5. 继续流式生成 - const stream = llmProviderPresenter.startStreamCompletion(...) - await this.loopOrchestrator.consume(stream) -} -``` - -**文件位置**:`src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts:181-350` - -## 🔄 loopOrchestrator - 循环编排 - -```typescript -class LoopOrchestrator { - constructor(private llmEventHandler: LLMEventHandler) {} - - async consume(stream: AsyncGenerator) { - for await (const event of stream) { - if (event.type === 'response') { - await this.llmEventHandler.handleResponse(event.data) - } else if (event.type === 'error') { - await this.llmEventHandler.handleError(event.data) - } else if (event.type === 'end') { - await this.llmEventHandler.handleEnd(event.data) - break - } - } - } -} -``` - -**文件位置**:`src/main/presenter/agentPresenter/loop/loopOrchestrator.ts` - -## 🔧 toolCallProcessor - 工具调用处理 - -### 组件职责 - -```typescript -class ToolCallProcessor { - // 处理工具调用(异步生成器) - async *process(context: { - eventId: string - toolCalls: Array<{id, name, arguments}> - conversationMessages: ChatMessage[] - modelConfig: any - abortSignal: AbortSignal - currentToolCallCount: number - maxToolCalls: number - conversationId: string - }): AsyncGenerator -} -``` - -**文件位置**:`src/main/presenter/agentPresenter/loop/toolCallProcessor.ts:1-445` - -### 处理流程 +### 权限流程 ```mermaid sequenceDiagram participant L as Agent Loop - participant T as toolCallProcessor - participant P as ToolPresenter + participant D as dispatch.ts + participant P as PermissionService participant E as EventBus - - L->>T: process(toolCalls) - - T->>T: 检查工具列表 + participant R as Renderer + + L->>D: executeTools(toolCalls) + loop 遍历每个 toolCall - T->>P: callTool(toolCall) - P->>P: ToolMapper 路由 - - alt MCP 工具 - P->>P: mcpPresenter.callTool() - else Agent 工具 - P->>P: agentToolManager.callTool() + D->>P: checkPermission(toolCall) + + alt 需要权限 + P-->>D: {needsPermission: true} + D->>E: 发送 permission block + E->>R: 显示权限对话框 + R-->>D: respondToolInteraction() + + alt 用户批准 + D->>P: grantPermission() + D->>D: 执行工具 + else 用户拒绝 + D->>P: denyPermission() + D->>D: 记录拒绝 + end + else 已有权限/全权限模式 + D->>D: 直接执行工具 end + end + + D-->>L: 返回工具结果 +``` - P-->>T: toolResponse +## 权限模式 - T->>E: send(tool_call running) - T->>E: send(tool_call end) +### 三种权限模式 - T->>T: 添加 tool result 到上下文 - T-->>L: return tool_call end +| 模式 | 行为 | +|------|------| +| `default` | 每次工具调用都需要用户批准 | +| `ask` | 首次询问,之后记住决策 | +| `full` | 自动批准所有工具调用(受 projectDir 限制) | - T->>T: incrementToolCallCount() - end +### 权限类型 - alt 用户中断 - T->>T: needContinueConversation = false - else 工具调用达上限 - T->>T: needContinueConversation = false - T-->>L: return maximum_tool_calls_reached - end +| 类型 | 说明 | +|------|------| +| `read` | 读取文件权限 | +| `write` | 写入文件权限 | +| `all` | 完全访问权限 | +| `command` | 执行命令权限 | - T-->>L: return metadata -``` +## P0 功能实现状态 -**文件位置**:`src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` +| 功能 | 状态 | 说明 | +|------|------|------| +| Session 状态跟踪 | ✅ 完成 | 通过 `session.status` + `messageStore.isStreaming` | +| 输入禁用 + Stop | ✅ 完成 | Stop UX 在 `ChatInputToolbar.vue` | +| 取消生成 | ✅ 完成 | Abort controller 集成 | +| 权限审批流程 | 🟡 部分 | 核心流程已实现,remember 持久化待完成 | +| Session 列表刷新 | ✅ 完成 | 事件驱动 `SESSION_EVENTS.LIST_UPDATED` | +| 乐观消息 | ✅ 完成 | `addOptimisticUserMessage()` | +| 缓存版本 | ⚪ 延迟 | 内存缓存足够 P0 | -## 📡 llmEventHandler - 事件处理 +详见 [P0 Implementation Summary](../P0_IMPLEMENTATION_SUMMARY.md) -### 标准化事件处理 +## 关键文件位置 -```typescript -class LLMEventHandler { - async handleResponse(data: LLMAgentEvent['data']) { - const { content, tool_call, tool_call_id, tool_call_name, tool_call_params } = data +### 新架构 - if (content) { - await this.contentBufferHandler.accumulate(eventId, content) - } +| 组件 | 位置 | +|------|------| +| NewAgentPresenter | `src/main/presenter/newAgentPresenter/index.ts` | +| DeepChatAgentPresenter | `src/main/presenter/deepchatAgentPresenter/index.ts` | +| processStream | `src/main/presenter/deepchatAgentPresenter/process.ts` | +| executeTools | `src/main/presenter/deepchatAgentPresenter/dispatch.ts` | +| buildContext | `src/main/presenter/deepchatAgentPresenter/contextBuilder.ts` | +| accumulate | `src/main/presenter/deepchatAgentPresenter/accumulator.ts` | +| MessageStore | `src/main/presenter/deepchatAgentPresenter/messageStore.ts` | +| SessionStore | `src/main/presenter/deepchatAgentPresenter/sessionStore.ts` | - if (tool_call) { - await this.toolCallHandler.handleToolCallEvent(data) - } - } +### 前端组件 - async handleError(data: {eventId, error}) { - await this.messageManager.handleMessageError(eventId, error) - } +| 组件 | 位置 | +|------|------| +| ChatPage | `src/renderer/src/pages/ChatPage.vue` | +| ChatInputToolbar | `src/renderer/src/components/chat/ChatInputToolbar.vue` | +| ChatToolInteractionOverlay | `src/renderer/src/components/chat/ChatToolInteractionOverlay.vue` | +| sessionStore | `src/renderer/src/stores/ui/session.ts` | +| messageStore | `src/renderer/src/stores/ui/message.ts` | - async handleEnd(data: {eventId, userStop}) { - await this.contentBufferHandler.flush(eventId) - await this.conversationUpdates(state) - } -} -``` +--- -**文件位置**:`src/main/presenter/agentPresenter/streaming/llmEventHandler.ts` - -## 🔐 permissionHandler - 权限协调 - -### 核心特性 - -1. **Batch-level Permission**: 同一 tool call 的多个权限块可以批量更新 -2. **Resume Lock**: messageId 级别的锁,确保权限恢复的原子性 -3. **Synchronous Flush**: 工具执行前同步刷新,确保 UI 状态已持久化 -4. **Pending Permissions Queue**: 支持多个待处理权限的队列管理 - -### 权限响应处理流程 - -```typescript -async handlePermissionResponse( - messageId: string, - toolCallId: string, - granted: boolean, - permissionType: 'read' | 'write' | 'all' | 'command', - remember: boolean = true -): Promise { - // Step 1: 批量更新权限块状态 - const { updatedCount, targetPermissionBlock } = await this.updatePermissionBlocks( - messageId, - toolCallId, - granted, - permissionType - ) - - // Step 2: 从待处理队列移除 - presenter.sessionManager.removePendingPermission(conversationId, messageId, toolCallId) - - // Step 3: 处理特殊权限类型(ACP、command、agent-filesystem、deepchat-settings) - if (isAcpPermission) { - await this.handleAcpPermissionFlow(messageId, targetPermissionBlock, granted) - return - } - - // Step 4: 批准对应类型的权限 - if (permissionType === 'command') { - this.commandPermissionHandler.approve(conversationId, signature, remember) - } else if (serverName === 'agent-filesystem') { - presenter.filePermissionService?.approve(conversationId, paths, remember) - } else if (serverName === 'deepchat-settings') { - presenter.settingsPermissionService?.approve(conversationId, toolName, remember) - } else { - await this.getMcpPresenter().grantPermission(serverName, permissionType, remember, conversationId) - } - - // Step 5: 检查是否还有更多待处理权限,恢复工具执行 - await this.checkAndResumeToolExecution(conversationId, messageId, granted, toolCallId) -} -``` +## Legacy Architecture (旧架构) -### 批量更新权限块 - -```typescript -// Batch update: only update blocks that can be safely batched -for (const block of content) { - if (canBatchUpdate(block, targetPermissionBlock, permissionType)) { - block.status = granted ? 'granted' : 'denied' - if (block.extra) { - block.extra.needsUserAction = false - if (granted && ['read', 'write', 'all'].includes(permissionType)) { - block.extra.grantedPermissions = permissionType - } - } - updatedCount++ - } -} - -// canBatchUpdate 条件: -// 1. 必须是 pending 状态 -// 2. 必须是 tool_call_permission 类型 -// 3. 必须是相同的 server -// 4. CRITICAL: 必须是相同的 tool_call.id(防止误批准其他工具) -// 5. 权限层级必须满足(all > write > read) -``` +以下内容描述旧的 AgentPresenter 架构,保留作为历史参考。新开发应使用上述新架构。 -### Resume Lock 机制 - -```typescript -/** - * Resume tool execution after permission is granted - * CRITICAL SECTION: Lock is held throughout the entire resume flow - * - Early-exit checks prevent stale execution - * - Synchronous flush before executing tools - * - Lock released only at single exit point - * - All tools executed atomically (no lock release between tools) - */ -private async resumeToolExecutionAfterPermissions( - messageId: string, - grantedToolCallId?: string -): Promise { - // CRITICAL SECTION: Lock must be held throughout this entire method - const session = presenter.sessionManager.getSessionSync(conversationId) - - // Verify the lock is still valid (same message) - const currentLock = presenter.sessionManager.getPermissionResumeLock(conversationId) - if (!currentLock || currentLock.messageId !== messageId) { - // Lock mismatch or expired, skip resume - presenter.sessionManager.releasePermissionResumeLock(conversationId) - return - } - - try { - // Step 1: Re-check session status - // Step 2: Refresh generating state from DB - // Step 3: Collect tool calls to execute - // Step 4: Validate tool calls with latest DB state - - // Step 5: SYNCHRONOUS FLUSH before executing tools - // This ensures all pending UI updates are persisted to DB before tool execution - await this.llmEventHandler.flushStreamUpdates(messageId) - - // Step 6: Execute tools sequentially (lock held throughout - NO RELEASE BETWEEN TOOLS) - for (const toolCall of toolCallsToExecute) { - const canContinue = await this.executeSingleToolCall(state, toolCall, conversationId) - if (!canContinue) { - hasNewPermissionRequest = true - break - } - } - - // Ensure tool_call end/error updates are persisted before rebuilding next-turn context - await this.llmEventHandler.flushStreamUpdates(messageId) - - // Step 7: Check if there are still pending permissions - const stillHasPending = await this.hasPendingPermissionsInMessage(messageId) - if (stillHasPending || hasNewPermissionRequest) { - // SINGLE EXIT POINT: Release lock - presenter.sessionManager.releasePermissionResumeLock(conversationId) - return - } - - // Step 8: All permissions resolved, continue with stream completion - await this.continueAfterToolsExecuted(state, conversationId, messageId) - // SINGLE EXIT POINT: Release lock after successful completion - presenter.sessionManager.releasePermissionResumeLock(conversationId) - } catch (error) { - // SINGLE EXIT POINT: Ensure lock is released on error - presenter.sessionManager.releasePermissionResumeLock(conversationId) - throw error - } -} -``` +### 旧架构组件概览 -### Pending Permissions Queue - -```typescript -// SessionManager 中的队列管理 -interface PendingPermission { - messageId: string - toolCallId: string - permissionType: string - serverName: string - timestamp: number -} - -// 添加到队列 -addPendingPermission(conversationId: string, permission: PendingPermission): void { - const runtime = this.getRuntime(agentId) - if (!runtime.pendingPermissions) { - runtime.pendingPermissions = [] - } - - const existingIndex = runtime.pendingPermissions.findIndex( - p => p.toolCallId === permission.toolCallId && p.messageId === permission.messageId - ) - if (existingIndex >= 0) { - runtime.pendingPermissions[existingIndex] = permission - } else { - runtime.pendingPermissions.push(permission) - } - runtime.pendingPermission = runtime.pendingPermissions[0] -} - -// 从队列移除 -removePendingPermission(conversationId: string, messageId: string, toolCallId: string): void { - const runtime = this.getRuntime(agentId) - if (runtime.pendingPermissions) { - runtime.pendingPermissions = runtime.pendingPermissions.filter( - p => !(p.toolCallId === toolCallId && p.messageId === messageId) - ) - runtime.pendingPermission = runtime.pendingPermissions[0] - } -} -``` +| 组件 | 文件位置 | 职责 | +|------|---------|------| +| **AgentPresenter** | `src/main/presenter/agentPresenter/index.ts` | Agent 编排主入口,实现 IAgentPresenter 接口 | +| **agentLoopHandler** | `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts` | Agent Loop 主循环(while 循环) | +| **streamGenerationHandler** | `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts` | 流生成协调 | +| **loopOrchestrator** | `src/main/presenter/agentPresenter/loop/loopOrchestrator.ts` | Loop 状态管理器 | +| **toolCallProcessor** | `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` | 工具调用执行 | +| **llmEventHandler** | `src/main/presenter/agentPresenter/streaming/llmEventHandler.ts` | 标准化 LLM 事件 | +| **permissionHandler** | `src/main/presenter/agentPresenter/permission/permissionHandler.ts` | 权限请求响应协调 | -**文件位置**:`src/main/presenter/agentPresenter/permission/permissionHandler.ts` - -## 🛠️ messageBuilder - 提示词构建 - -### 主要方法 - -```typescript -async function preparePromptContent(context: { - conversation: CONVERSATION - userContent: string - contextMessages: Message[] - searchResults?: SearchResult[] - urlResults?: SearchResult[] - userMessage: Message - vision?: boolean - imageFiles?: MessageFile[] - supportsFunctionCall?: boolean - modelType?: 'chat' | 'image' | 'audio' -}): Promise<{ finalContent: ChatMessage[], promptTokens: number }> -``` +### 旧架构关系图 -**文件位置**:`src/main/presenter/agentPresenter/message/messageBuilder.ts` +```mermaid +graph TB + subgraph "AgentPresenter 主入口" + AgentP[AgentPresenter] + end -### 构建流程 + subgraph "Agent Loop 执行层" + StreamGen[streamGenerationHandler] + AgentLoop[agentLoopHandler] + LoopOrch[loopOrchestrator] + ToolCallProc[toolCallProcessor] + end -```typescript -async function preparePromptContent(context) { - const { conversation, userContent, contextMessages, searchResults, urlResults } = context + subgraph "事件处理层" + LLMEvent[llmEventHandler] + ToolCall[toolCallHandler] + BufHandler[contentBufferHandler] + end - // 1. 基础消息列表 - let messages = contextMessages.map(msg => ({ - role: msg.role, - content: buildUserMessageContext(msg.content) - })) + subgraph "辅助组件" + MessageBuilder[messageBuilder] + PermHandler[permissionHandler] + Utility[utilityHandler] + end - // 2. 添加系统提示词 - const systemPrompt = buildSystemPrompt(conversation) - messages.unshift({ role: 'system', content: systemPrompt }) + AgentP --> StreamGen + AgentP --> PermHandler + AgentP --> Utility - // 3. 添加用户消息 - const userMessage = { - role: 'user', - content: buildUserMessageWithContext(userMessage.content, searchResults, urlResults) - } - messages.push(userMessage) + StreamGen --> AgentLoop + StreamGen --> LLMEvent + StreamGen --> MessageBuilder - // 4. 处理图片(vision) - if (conversation.settings.vision && context.imageFiles.length > 0) { - userMessage.content = combineTextAndImages(userMessage.content, context.imageFiles) - } + AgentLoop --> LoopOrch + AgentLoop --> ToolCallProc + AgentLoop --> ToolCall - // 5. 压缩上下文(如超过限制) - messages = await MessageCompressor.compress(messages, conversation.settings.contextLength) + ToolCallProc --> AgentP + ToolCall --> LLMEvent + LLMEvent --> BufHandler - // 6. 格式化为 OpenAI 格式 - const finalContent = toOpenAIMessages(messages) + LoopOrch --> LLMEvent - return { finalContent, promptTokens: calculateTokens(messages) } -} -``` + classDef entry fill:#e3f2fd + classDef loop fill:#fff3e0 + classDef event fill:#f3e5f5 + classDef util fill:#e8f5e9 -**文件位置**:`src/main/presenter/agentPresenter/message/messageBuilder.ts` - -## 📊 contentBufferHandler - 内容缓冲 - -### 优化策略 - -```typescript -class ContentBufferHandler { - // 累积内容 - async accumulate(eventId: string, content: string) { - const state = this.generatingMessages.get(eventId) - if (!state) return - - // 累积到 adaptiveBuffer - if (!state.adaptiveBuffer) { - state.adaptiveBuffer = [] - } - state.adaptiveBuffer.push({ - content, - timestamp: Date.now(), - size: content.length - }) - - // 自适应刷新 - const totalSize = state.adaptiveBuffer.reduce((sum, item) => sum + item.size, 0) - if (totalSize >= this.threshold) { - await this.flushAdaptiveBuffer(eventId) - } - } - - // 刷新到前端 - async flushAdaptiveBuffer(eventId: string) { - const state = this.generatingMessages.get(eventId) - if (!state?.adaptiveBuffer) return - - const combined = state.adaptiveBuffer.map(item => item.content).join('') - state.adaptiveBuffer = [] - - await this.messageManager.editMessage(eventId, JSON.stringify(combined)) - eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, { eventId, content: combined }) - } -} + class AgentP entry + class StreamGen,AgentLoop,LoopOrch,ToolCallProc loop + class LLMEvent,ToolCall,BufHandler event + class MessageBuilder,PermHandler,Utility util ``` -**文件位置**:`src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts` +### 旧架构关键文件位置 -## 📁 关键文件位置汇总 - -- **AgentPresenter**: `src/main/presenter/agentPresenter/index.ts:1-472` -- **agentLoopHandler**: `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts:145-668` -- **streamGenerationHandler**: `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts:54-645` +- **AgentPresenter**: `src/main/presenter/agentPresenter/index.ts` +- **agentLoopHandler**: `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts` +- **streamGenerationHandler**: `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts` - **loopOrchestrator**: `src/main/presenter/agentPresenter/loop/loopOrchestrator.ts` -- **toolCallProcessor**: `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts:1-445` +- **toolCallProcessor**: `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` - **llmEventHandler**: `src/main/presenter/agentPresenter/streaming/llmEventHandler.ts` - **permissionHandler**: `src/main/presenter/agentPresenter/permission/permissionHandler.ts` -- **messageBuilder**: `src/main/presenter/agentPresenter/message/messageBuilder.ts:1-285` +- **messageBuilder**: `src/main/presenter/agentPresenter/message/messageBuilder.ts` - **contentBufferHandler**: `src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts` -- **toolCallHandler**: `src/main/presenter/agentPresenter/loop/toolCallHandler.ts` -## 📚 相关阅读 +--- + +## 相关阅读 -- [整体架构概览](../ARCHITECTURE.md#agent-编排器层) -- [工具系统详解](../architecture/tool-system.md) -- [核心流程](../FLOWS.md#发送消息完整流程) +- [整体架构概览](../ARCHITECTURE.md) +- [工具系统详解](./tool-system.md) +- [核心流程](../FLOWS.md) - [会话管理详解](./session-management.md) +- [事件系统](./event-system.md) \ No newline at end of file diff --git a/docs/architecture/new-ui-implementation-plan.md b/docs/architecture/new-ui-implementation-plan.md index a2f77ba7a..595b4cfa3 100644 --- a/docs/architecture/new-ui-implementation-plan.md +++ b/docs/architecture/new-ui-implementation-plan.md @@ -1,5 +1,14 @@ # New UI Feature Implementation Plan +**Status:** ✅ **IMPLEMENTATION COMPLETE** +**Completion Date:** 2026-03-09 + +--- + +> **Note:** This plan has been fully implemented. The new UI architecture is now the primary interface. This document is retained for historical reference. + +--- + This document defines the technical plan for implementing complete functionality on the new UI architecture, without considering legacy compatibility migration, based on entirely new code implementation. --- diff --git a/docs/specs/p0-implementation/README.md b/docs/specs/p0-implementation/README.md deleted file mode 100644 index 42984ec6e..000000000 --- a/docs/specs/p0-implementation/README.md +++ /dev/null @@ -1,205 +0,0 @@ -# P0 Implementation Master Plan - -**Date:** 2026-03-04 -**Branch:** `feat/new-arch-complete` -**Status:** In Progress (Synced with current codebase state on 2026-03-04) - ---- - -## 1) Goal - -This P0 plan defines the **minimum complete refactor slice** to make the new agent architecture the stable mainline for daily chat usage: - -1. New UI main flow uses new presenters/stores consistently. -2. Tool permission flow is safe and resumable (no silent auto-run risk). -3. Generation UX is usable (disable/stop/cancel/optimistic/list refresh/cache correctness). -4. Default model behavior is consistent across new-session entry points. - -This is the implementation baseline for the larger replacement plan, not final cleanup. - ---- - -## 2) Current Snapshot (as of 2026-03-04) - -### 2.1 Feature-01 ~ Feature-07 Progress Matrix - -| Feature | Current State | Notes | -|---|---|---| -| feature-01-generating-session-ids | 🟡 Partial | Generation state is tracked via `session status + messageStore.isStreaming`, not via a dedicated `generatingSessionIds` Set. | -| feature-02-input-disable-stop | ✅ Functional Complete (Implementation differs) | Input disable and stop button are closed in `ChatPage + ChatInputBox + ChatInputToolbar`; no standalone `StopButton.vue`. | -| feature-03-cancel-generating | 🟡 Partial | Cancel flow and abort controller are live; aborted message currently lands as error-style stream termination, not explicit `cancelled` status model. | -| feature-04-permission-approval | 🟡 Partial | Approval/deny + resume loop are live; remember-decision persistence and strict full-access boundary closure are still not fully closed in this P0 spec line. | -| feature-05-session-list-refresh | ✅ Mostly Complete | `SESSION_EVENTS.LIST_UPDATED` is primary and cross-window refresh works; compatibility listener retained, but old-link fallback still exists in `pageRouter`. | -| feature-06-optimistic-messages | 🟡 Partial | Optimistic user message insertion is live; temp-id → backend-id merge contract is not fully implemented (currently refresh-based reconciliation). | -| feature-07-cache-versioning | ⚪ Not Started | No explicit cache version bump/invalidation scheme per this feature spec yet. | - -### 2.2 Additional Progress Already Landed (outside feature-01~07 docs) - -1. Chat top bar Share/More menus are now functional in new architecture. -2. Session actions from top bar are live: pin/unpin, rename, clear messages, delete. -3. Session export from top bar is live: markdown/html/txt/nowledge-mem(json download only). -4. Sidebar now supports pinned sessions in a no-title top area outside normal groups. - -### 2.3 Historical Baseline Notes (kept for context) - -### 已有基础 - -1. `newAgentPresenter + deepchatAgentPresenter(v3 process loop)` 已存在并可跑通基本消息流。 -2. 新 UI 页面与 stores 已接入主路径(`sessionStore/messageStore/agentStore/projectStore`)。 -3. P0 的 7 个功能子规格(feature-01 ~ feature-07)文档已拆分完成。 - -### 仍未闭环的关键缺口(P0 blockers, refreshed) - -1. `new_sessions` 仍无 `permission_mode` 字段(仅 `deepchat_sessions.permission_mode` 已存在)。 -2. 新链路会话状态仍未对外覆盖 `paused / waiting_permission`(当前 `SessionStatus` 仍是 `idle/generating/error`)。 -3. 权限流 remember 决策持久化与 full-access 工作区边界约束仍需按 P0 标准补齐。 -4. `pageRouter` 仍保留 legacy `sessionPresenter` fallback;`ChatStatusBar` 仍有旧 `chatStore` 依赖。 -5. optimistic merge 协议(temp id → real id)与 cache versioning 仍未完全落地。 - ---- - -## 3) P0 Scope (Must Have) - -### A. Permission & Safety Closure (Highest Priority) - -来源: -- [feature-04-permission-approval](./feature-04-permission-approval/) -- [agentpresenter-mvp-replacement/spec.md](../agentpresenter-mvp-replacement/spec.md) -- [permission-flow-stabilization/spec.md](../permission-flow-stabilization/spec.md) - -必须完成: - -1. `new_sessions` 增加 `permission_mode`(`default | full`)。 -2. `deepchatAgentPresenter` 工具执行前权限检查,未批准必须暂停,不得直接执行。 -3. 支持 message 级恢复幂等(resume lock),避免重复恢复/重复 loop。 -4. 恢复继续生成前,工具结果与 permission block 必须先落库可见(避免重复 tool_use)。 -5. 增加 `handlePermissionResponse` 新链路 IPC(approve/deny/remember)。 -6. Full access 必须受 `projectDir` 边界约束。 - -### B. Generation UX Baseline - -来源: -- [feature-01-generating-session-ids](./feature-01-generating-session-ids/) -- [feature-02-input-disable-stop](./feature-02-input-disable-stop/) -- [feature-03-cancel-generating](./feature-03-cancel-generating/) -- [feature-06-optimistic-messages](./feature-06-optimistic-messages/) - -必须完成: - -1. 输入禁用与 stop 按钮在新 UI 主路径闭环。 -2. cancel 行为保持“保留部分内容 + 可继续后续对话”。 -3. 生成态追踪与 session status 对齐(避免双状态源冲突)。 -4. optimistic message 与最终落库消息可正确合并。 - -### C. Session/List/Event Consistency - -来源: -- [feature-05-session-list-refresh](./feature-05-session-list-refresh/) -- [new-ui-page-state/spec.md](../new-ui-page-state/spec.md) -- [new-ui-session-store/spec.md](../new-ui-session-store/spec.md) - -必须完成: - -1. 会话列表刷新以 `SESSION_EVENTS` 为主(保留兼容监听但明确退场计划)。 -2. `pageRouter` 活跃会话查询切到新链路(`newAgentPresenter`)。 -3. session 状态映射覆盖等待态(至少 `paused`)。 - -### D. Cache/Performance Correctness - -来源: -- [feature-07-cache-versioning](./feature-07-cache-versioning/) -- [permission-flow-stabilization/spec.md](../permission-flow-stabilization/spec.md) - -必须完成: - -1. message cache 版本控制与失效策略落地。 -2. 流式 flush 策略在“权限恢复后继续生成”场景无可见性竞态。 - -### E. Default Model Settings Integration - -来源: -- [default-model-settings/spec.md](../default-model-settings/spec.md) -- [default-model-settings/plan.md](../default-model-settings/plan.md) - -必须完成: - -1. `defaultModel` 作为新建会话默认模型规则的统一入口(非 ACP)。 -2. `defaultVisionModel` 仅允许 vision 模型。 -3. `imageServer` 移除 args 模型依赖,统一读取全局 `defaultVisionModel`。 - ---- - -## 4) Execution Order (Dependency Driven) - -### Phase 0: Contract Freeze - -1. 冻结新链路事件契约(`SESSION_EVENTS`/`STREAM_EVENTS` payload)与状态枚举。 -2. 明确 permission batch + resume lock + flush-before-continue 的统一约束。 - -### Phase 1: Permission Core (A) - -1. DB 字段与运行时状态扩展(`permission_mode`、`paused`)。 -2. 后端 permission check / pause / resume / remember 全链路打通。 -3. 前端权限响应入口接入新 presenter。 - -### Phase 2: Generation UX (B) - -1. 输入禁用、stop、cancel、optimistic 在新页面闭环验证。 -2. 补齐失败与中断路径(END/ERROR/CANCEL)一致性。 - -### Phase 3: Session/Event Alignment (C) - -1. `pageRouter` 与 session activation 全部切到新链路。 -2. 列表刷新/状态更新跨窗口一致性验证。 - -### Phase 4: Cache & Flush Safety (D) - -1. cache versioning 落地。 -2. 权限恢复后续跑场景做可见性回归测试。 - -### Phase 5: Default Model / Vision (E) - -1. default model & vision model 完整接入。 -2. imageServer 模型来源统一。 - ---- - -## 5) Explicitly Out of P0 - -以下内容不纳入本轮 P0,放入后续阶段: - -1. 全量移除 chat mode 与搜索系统(见 [remove-chat-mode/spec.md](../remove-chat-mode/spec.md))。 -2. edit/retry/regenerate/fork 的完整产品化能力(见 [agentpresenter-mvp-replacement/spec.md](../agentpresenter-mvp-replacement/spec.md))。 -3. 大规模旧数据迁移脚本(先保证双链路兼容可运行)。 - ---- - -## 6) Quality Gates (Release Blockers) - -P0 完成前必须全部满足: - -1. `pnpm run format` -2. `pnpm run lint` -3. `pnpm run typecheck` -4. 新链路权限流集成测试(approve/deny/remember/full-access-boundary) -5. 新链路生成流集成测试(send/stop/cancel/error/retry-send) -6. 跨窗口 session 刷新与激活一致性测试 -7. 旧链路回归冒烟(避免一次性回归爆炸) - ---- - -## 7) Source of Truth - -1. [P0 design decisions](../../../P0_DESIGN_DECISIONS.md) -2. [Architecture overview](../../ARCHITECTURE.md) -3. [new-agent/spec.md](../new-agent/spec.md) -4. [new-agent/v3-spec.md](../new-agent/v3-spec.md) -5. [new-ui-implementation/todo.md](../new-ui-implementation/todo.md) -6. [default-model-settings/spec.md](../default-model-settings/spec.md) -7. [agentpresenter-mvp-replacement/gap-analysis.md](../agentpresenter-mvp-replacement/gap-analysis.md) - ---- - -**Last Updated:** 2026-03-04 -**Maintained By:** Development Team -**Review Status:** Active Implementation Sync diff --git a/docs/specs/p0-implementation/feature-01-generating-session-ids/plan.md b/docs/specs/p0-implementation/feature-01-generating-session-ids/plan.md deleted file mode 100644 index 96b6ae8b5..000000000 --- a/docs/specs/p0-implementation/feature-01-generating-session-ids/plan.md +++ /dev/null @@ -1,246 +0,0 @@ -# Generating Session IDs Tracking - Implementation Plan - -## Implementation Status Sync (2026-03-04) - -**Status:** 🟡 Partial -Current new UI implementation uses `session status + streaming state` instead of the dedicated Set strategy in this plan. - -## Current State - -**What exists today:** - -1. No centralized tracking of generating sessions in frontend -2. Input box disable logic is ad-hoc and inconsistent -3. Stop button visibility not properly tied to generation state -4. Backend emits `STREAM_EVENTS.END` and `STREAM_EVENTS.ERROR` ✅ -5. Session store exists but lacks generation tracking state - -**Current Code Flow:** -```typescript -// Current: No tracking -async function sendMessage() { - await agentPresenter.sendMessage(sessionId, content) - // No state management -} -``` - -## Target State - -**What we want after implementation:** - -1. Frontend maintains reactive Set of generating session IDs -2. Immediate UI feedback when generation starts -3. Consistent input box disable behavior -4. Stop button visibility tied to generation state -5. Multi-session generation tracking support - -**Target Code Flow:** -```typescript -// Target: With tracking -async function sendMessage() { - sessionStore.addGeneratingSession(sessionId) - try { - await agentPresenter.sendMessage(sessionId, content) - } catch (error) { - sessionStore.removeGeneratingSession(sessionId) - throw error - } -} - -// Cleanup on stream end -window.api.on(STREAM_EVENTS.END, (data) => { - sessionStore.removeGeneratingSession(data.sessionId) -}) -``` - -## Implementation Phases - -### Phase 1: Session Store Updates - -1. Add `generatingSessionIds` state to session store -2. Add helper functions (add, remove, isGenerating) -3. Ensure reactivity with Vue 3 `ref>` -4. Export state and functions for use in components - -**Files:** -- `src/renderer/src/stores/session.ts` - -### Phase 2: ChatPage Integration - -1. Import `addGeneratingSession` and `removeGeneratingSession` -2. Modify `sendMessage()` to add session ID before sending -3. Add error handling to remove on failure -4. Set up event listeners for END and ERROR events -5. Clean up listeners on component unmount - -**Files:** -- `src/renderer/src/views/ChatPage.vue` - -### Phase 3: UI Component Binding - -1. Update `ChatInputBox.vue` to check `isGenerating(sessionId)` -2. Update `StopButton.vue` to show when generating -3. Test reactive updates across components -4. Verify multi-session tracking works correctly - -**Files:** -- `src/renderer/src/components/chat/ChatInputBox.vue` -- `src/renderer/src/components/chat/StopButton.vue` - -## File Changes - -| File | Change Type | Description | -|------|-------------|-------------| -| `src/renderer/src/stores/session.ts` | Modify | Add `generatingSessionIds` state and helper functions | -| `src/renderer/src/views/ChatPage.vue` | Modify | Add session to Set on send, remove on END/ERROR | -| `src/renderer/src/components/chat/ChatInputBox.vue` | Modify | Check `isGenerating()` for disabled prop | -| `src/renderer/src/components/chat/StopButton.vue` | Modify | Show/hide based on generating state | - -## Testing Strategy - -### Unit Tests - -**File:** `src/renderer/src/stores/__tests__/session.test.ts` - -```typescript -describe('generatingSessionIds', () => { - it('should add session ID to Set', () => { - addGeneratingSession('session-1') - expect(isGenerating('session-1')).toBe(true) - }) - - it('should remove session ID from Set', () => { - addGeneratingSession('session-1') - removeGeneratingSession('session-1') - expect(isGenerating('session-1')).toBe(false) - }) - - it('should track multiple sessions independently', () => { - addGeneratingSession('session-1') - addGeneratingSession('session-2') - expect(isGenerating('session-1')).toBe(true) - expect(isGenerating('session-2')).toBe(true) - removeGeneratingSession('session-1') - expect(isGenerating('session-1')).toBe(false) - expect(isGenerating('session-2')).toBe(true) - }) - - it('should be reactive', async () => { - const wrapper = mount({ template: '
{{ isGenerating("s1") }}
' }) - expect(wrapper.text()).toBe('false') - addGeneratingSession('s1') - await nextTick() - expect(wrapper.text()).toBe('true') - }) -}) -``` - -### Integration Tests - -**File:** `tests/integration/generation-tracking.test.ts` - -```typescript -describe('Generation Tracking Integration', () => { - it('should track session during message send', async () => { - const sessionId = 'test-session' - - // Send message - await chatPage.sendMessage(sessionId, 'Hello') - - // Verify tracking - expect(sessionStore.isGenerating(sessionId)).toBe(true) - }) - - it('should stop tracking on END event', async () => { - const sessionId = 'test-session' - - // Send message - await chatPage.sendMessage(sessionId, 'Hello') - - // Simulate END event - eventBus.sendToRenderer(STREAM_EVENTS.END, { sessionId }) - await nextTick() - - // Verify tracking removed - expect(sessionStore.isGenerating(sessionId)).toBe(false) - }) - - it('should stop tracking on ERROR event', async () => { - const sessionId = 'test-session' - - // Send message - await chatPage.sendMessage(sessionId, 'Hello') - - // Simulate ERROR event - eventBus.sendToRenderer(STREAM_EVENTS.ERROR, { sessionId, error: 'Test error' }) - await nextTick() - - // Verify tracking removed - expect(sessionStore.isGenerating(sessionId)).toBe(false) - }) -}) -``` - -### Manual Testing - -1. Open app and navigate to a session -2. Send a message -3. Verify input box is disabled immediately -4. Verify stop button appears -5. Wait for generation to complete -6. Verify input box is re-enabled -7. Verify stop button disappears -8. Repeat with multiple sessions open in different tabs - -## Rollback Plan - -**If issues are found:** - -1. **Revert session store changes:** - ```bash - git checkout HEAD -- src/renderer/src/stores/session.ts - ``` - -2. **Revert ChatPage changes:** - ```bash - git checkout HEAD -- src/renderer/src/views/ChatPage.vue - ``` - -3. **Revert component changes:** - ```bash - git checkout HEAD -- src/renderer/src/components/chat/ChatInputBox.vue - git checkout HEAD -- src/renderer/src/components/chat/StopButton.vue - ``` - -**Fallback Behavior:** -- Input box disable logic reverts to previous ad-hoc implementation -- Stop button visibility uses old logic -- No breaking changes to existing functionality - -## Success Criteria - -- [ ] All unit tests passing -- [ ] All integration tests passing -- [ ] Manual testing completed successfully -- [ ] No console errors or warnings -- [ ] Reactive updates work correctly -- [ ] Multi-session tracking works -- [ ] Edge cases handled properly -- [ ] Type check passing -- [ ] Lint passing -- [ ] Format passing - -## Estimated Timeline - -- **Phase 1 (Store):** 30 minutes -- **Phase 2 (ChatPage):** 1 hour -- **Phase 3 (Components):** 30 minutes -- **Testing:** 1 hour -- **Total:** ~3 hours - ---- - -**Status:** 📝 Plan Complete -**Priority:** P0 -**Complexity:** Low -**Risk:** Low diff --git a/docs/specs/p0-implementation/feature-01-generating-session-ids/spec.md b/docs/specs/p0-implementation/feature-01-generating-session-ids/spec.md deleted file mode 100644 index 1d4b6d382..000000000 --- a/docs/specs/p0-implementation/feature-01-generating-session-ids/spec.md +++ /dev/null @@ -1,246 +0,0 @@ -# Generating Session IDs Tracking - Specification - -## Implementation Status Sync (2026-03-04) - -**Status:** 🟡 Partial -**Note:** Business outcome is mostly achieved via `session status + messageStore.isStreaming`; this spec's dedicated `generatingSessionIds Set` design is not implemented as-is. - -## Overview - -Track which sessions are currently generating responses using a frontend-managed Set (`generatingSessionIds`). This provides immediate UI feedback for generation state, enabling proper input box disabling and stop button visibility. - -## User Stories - -- As a user, I want to know when a session is actively generating a response -- As a user, I need visual feedback that my message is being processed -- As a user, I want to see which conversations are busy in a multi-session environment - -## Acceptance Criteria - -### Functional Requirements - -- [ ] Frontend maintains a reactive Set of generating session IDs -- [ ] Session ID is added to the Set immediately when user sends a message -- [ ] Session ID is removed from the Set when END or ERROR event is received -- [ ] UI components can reactively query if a session is generating -- [ ] Multiple sessions can be tracked simultaneously (multi-tab support) - -### Technical Requirements - -- [ ] Frontend adds session ID to Set on message send -- [ ] Frontend listens to `STREAM_EVENTS.END` event -- [ ] Frontend listens to `STREAM_EVENTS.ERROR` event -- [ ] Session ID removed on both END and ERROR events -- [ ] Set is reactive (Vue 3 `ref>`) -- [ ] No backend changes required (frontend-only state) - -## Architecture - -### Backend Changes - -**None** - This is a frontend-only tracking mechanism. - -Backend already emits the required events: -- `STREAM_EVENTS.END` - when generation completes -- `STREAM_EVENTS.ERROR` - when generation fails - -### Frontend Changes - -**New State:** -```typescript -// src/renderer/src/stores/session.ts -export const generatingSessionIds = ref>(new Set()) -``` - -**Modified Components:** -1. `src/renderer/src/stores/session.ts` - Add generatingSessionIds state -2. `src/renderer/src/views/ChatPage.vue` - Add session ID on send -3. `src/renderer/src/components/chat/ChatInputBox.vue` - Check generating state -4. `src/renderer/src/components/chat/StopButton.vue` - Show when generating - -### State Management - -```typescript -// Session Store (src/renderer/src/stores/session.ts) -import { ref } from 'vue' - -export const generatingSessionIds = ref>(new Set()) - -export function addGeneratingSession(sessionId: string) { - generatingSessionIds.value.add(sessionId) -} - -export function removeGeneratingSession(sessionId: string) { - generatingSessionIds.value.delete(sessionId) -} - -export function isGenerating(sessionId: string): boolean { - return generatingSessionIds.value.has(sessionId) -} -``` - -## Event Flow - -### Message Send Flow - -``` -User clicks Send - ↓ -ChatPage.sendMessage() - ↓ -sessionStore.addGeneratingSession(sessionId) ← Add to Set - ↓ -messageStore.addOptimisticMessage() ← Show optimistic UI - ↓ -agentPresenter.sendMessage() - ↓ -[Backend processes message] - ↓ -[Backend emits STREAM_EVENTS.RESPONSE (streaming content)] - ↓ -[Backend emits STREAM_EVENTS.END or STREAM_EVENTS.ERROR] - ↓ -Frontend receives END/ERROR event - ↓ -sessionStore.removeGeneratingSession(sessionId) ← Remove from Set - ↓ -UI updates (input re-enabled, stop button hidden) -``` - -### Implementation Example - -```typescript -// src/renderer/src/views/ChatPage.vue -async function sendMessage(content: string) { - const sessionId = sessionStore.activeSession?.id - - if (!sessionId) return - - // Add to generating set IMMEDIATELY - sessionStore.addGeneratingSession(sessionId) - - try { - await agentPresenter.sendMessage(sessionId, content) - // Note: Don't remove here - wait for END/ERROR event - } catch (error) { - // On error, remove from set - sessionStore.removeGeneratingSession(sessionId) - throw error - } -} - -// Listen to stream events -onMounted(() => { - window.api.on(STREAM_EVENTS.END, (data) => { - sessionStore.removeGeneratingSession(data.sessionId) - }) - - window.api.on(STREAM_EVENTS.ERROR, (data) => { - sessionStore.removeGeneratingSession(data.sessionId) - }) -}) -``` - -## Edge Cases - -### 1. Backend Fails to Start Generation - -**Scenario:** Frontend adds to Set, but backend fails before emitting END/ERROR - -**Handling:** -- Frontend catches sendMessage() error -- Remove from Set in catch block -- Show error notification to user - -### 2. Multiple Messages in Same Session - -**Scenario:** User sends multiple messages rapidly (should be prevented by UI) - -**Handling:** -- Input box is disabled while generating -- Set already contains sessionId (no duplicate issue with Set data structure) - -### 3. Session Switch During Generation - -**Scenario:** User switches to different session while one is generating - -**Handling:** -- Set tracks all sessions independently -- Each session's generating state is preserved -- UI shows correct state when switching back - -### 4. App Refresh During Generation - -**Scenario:** User refreshes app while generation is in progress - -**Handling:** -- Set is cleared on refresh (in-memory only) -- On reconnection, backend state is source of truth -- Can optionally sync with backend on reconnect - -### 5. Network Disconnection - -**Scenario:** Network disconnects, END/ERROR never received - -**Handling:** -- Timeout mechanism (optional enhancement) -- User can manually cancel (see Feature 3) -- Set cleared on session deactivation - -## Testing Checklist - -### Unit Tests - -- [ ] `addGeneratingSession()` adds ID to Set -- [ ] `removeGeneratingSession()` removes ID from Set -- [ ] `isGenerating()` returns correct boolean -- [ ] Set remains reactive after add/remove operations -- [ ] Multiple session IDs can be tracked simultaneously - -### Integration Tests - -- [ ] Send message → Set contains session ID -- [ ] Receive END event → Set doesn't contain session ID -- [ ] Receive ERROR event → Set doesn't contain session ID -- [ ] Input box disabled when session is generating -- [ ] Stop button visible when session is generating - -### Manual Tests - -- [ ] Send message in Session A → Session A shows generating state -- [ ] Send message in Session B → Both sessions tracked independently -- [ ] Wait for generation complete → Input re-enabled -- [ ] Trigger error → Input re-enabled, error shown -- [ ] Switch sessions during generation → State preserved - -### Edge Case Tests - -- [ ] Rapid message sending (should be blocked by disabled input) -- [ ] Network disconnection during generation -- [ ] App refresh during generation -- [ ] Backend crash during generation - -## Dependencies - -### Internal Dependencies - -- None - This feature is foundational and independent - -### External Dependencies - -- Backend emits `STREAM_EVENTS.END` ✅ (already implemented) -- Backend emits `STREAM_EVENTS.ERROR` ✅ (already implemented) -- Vue 3 reactivity system ✅ (already available) - -## Related Features - -- **Feature 2:** Input Box Disable + Stop Button (uses this feature) -- **Feature 3:** CancelGenerating Implementation (uses this feature) -- **Feature 6:** Optimistic User Messages (coordinates with this feature) - ---- - -**Status:** 📝 Spec Complete -**Priority:** P0 -**Estimated Implementation:** 2-3 hours -**Risk Level:** Low (frontend-only, no backend changes) diff --git a/docs/specs/p0-implementation/feature-01-generating-session-ids/tasks.md b/docs/specs/p0-implementation/feature-01-generating-session-ids/tasks.md deleted file mode 100644 index c8a0ba20c..000000000 --- a/docs/specs/p0-implementation/feature-01-generating-session-ids/tasks.md +++ /dev/null @@ -1,458 +0,0 @@ -# Generating Session IDs Tracking - Implementation Tasks - -## Implementation Sync (2026-03-04) - -**Overall:** 🟡 Partial (implemented with a different mechanism) - -### Done in current codebase - -1. Generation UI state is driven by `sessionStore.activeSession.status === 'working'` and `messageStore.isStreaming`. -2. Input submit disabling and stop-button visibility already bind to that computed generating state. -3. Stream END/ERROR both trigger `messageStore` cleanup and message reload. - -### Not done / differs from this task doc - -1. No dedicated reactive `generatingSessionIds: Set` state exists. -2. No explicit add/remove helper API (`addGeneratingSession/removeGeneratingSession/isGenerating`) exists. -3. Multi-session generation tracking is not represented as a standalone Set. - -### Evidence (current files) - -1. `src/renderer/src/pages/ChatPage.vue` -2. `src/renderer/src/stores/ui/message.ts` -3. `src/renderer/src/stores/ui/session.ts` - -## Task List - -### Task 1: Add generatingSessionIds State to Session Store - -**File:** `src/renderer/src/stores/session.ts` - -**Current State:** -```typescript -export const sessionStore = defineStore('session', { - state: () => ({ - sessions: [] as Session[], - activeSessionId: null as string | null, - // ... other state - }) -}) -``` - -**Required Change:** -```typescript -import { ref } from 'vue' - -// Add to store state -export const generatingSessionIds = ref>(new Set()) - -// Add helper functions -export function addGeneratingSession(sessionId: string) { - generatingSessionIds.value.add(sessionId) -} - -export function removeGeneratingSession(sessionId: string) { - generatingSessionIds.value.delete(sessionId) -} - -export function isGenerating(sessionId: string): boolean { - return generatingSessionIds.value.has(sessionId) -} - -// Export for use in components -export { generatingSessionIds, addGeneratingSession, removeGeneratingSession, isGenerating } -``` - -**Expected Behavior:** -- Set is reactive (Vue 3 ref) -- Functions properly add/remove/check session IDs -- Multiple sessions can be tracked simultaneously - -**Test Case:** -```typescript -// src/renderer/src/stores/__tests__/session.test.ts -import { addGeneratingSession, removeGeneratingSession, isGenerating } from '../session' - -test('should track generating sessions', () => { - expect(isGenerating('session-1')).toBe(false) - - addGeneratingSession('session-1') - expect(isGenerating('session-1')).toBe(true) - - removeGeneratingSession('session-1') - expect(isGenerating('session-1')).toBe(false) -}) - -test('should track multiple sessions', () => { - addGeneratingSession('session-1') - addGeneratingSession('session-2') - - expect(isGenerating('session-1')).toBe(true) - expect(isGenerating('session-2')).toBe(true) - - removeGeneratingSession('session-1') - expect(isGenerating('session-1')).toBe(false) - expect(isGenerating('session-2')).toBe(true) -}) -``` - ---- - -### Task 2: Update ChatPage to Track Generation - -**File:** `src/renderer/src/views/ChatPage.vue` - -**Current State:** -```typescript -async function sendMessage(content: string) { - const sessionId = sessionStore.activeSession?.id - if (!sessionId) return - - await agentPresenter.sendMessage(sessionId, content) - // No tracking -} -``` - -**Required Change:** -```typescript -import { addGeneratingSession, removeGeneratingSession } from '@/stores/session' -import { STREAM_EVENTS } from '@shared/events' - -async function sendMessage(content: string) { - const sessionId = sessionStore.activeSession?.id - if (!sessionId) return - - // Add to generating set immediately - addGeneratingSession(sessionId) - - try { - await agentPresenter.sendMessage(sessionId, content) - // Don't remove here - wait for END/ERROR event - } catch (error) { - // On error, remove from set - removeGeneratingSession(sessionId) - throw error - } -} - -// Set up event listeners -onMounted(() => { - // Listen for stream end - window.api.on(STREAM_EVENTS.END, (data) => { - removeGeneratingSession(data.sessionId) - }) - - // Listen for stream error - window.api.on(STREAM_EVENTS.ERROR, (data) => { - removeGeneratingSession(data.sessionId) - }) -}) - -onUnmounted(() => { - // Clean up listeners - window.api.removeAllListeners(STREAM_EVENTS.END) - window.api.removeAllListeners(STREAM_EVENTS.ERROR) -}) -``` - -**Expected Behavior:** -- Session ID added to Set immediately when send is clicked -- Session ID removed when END event received -- Session ID removed when ERROR event received -- Error handling removes from Set if sendMessage fails - -**Test Case:** -```typescript -// tests/integration/chatpage-generation.test.ts -import { generatingSessionIds } from '@/stores/session' - -test('should add session to generating set on send', async () => { - const sessionId = 'test-session' - await sessionStore.setActiveSession(sessionId) - - await chatPage.sendMessage('Hello') - - expect(generatingSessionIds.value.has(sessionId)).toBe(true) -}) - -test('should remove session from generating set on END', async () => { - const sessionId = 'test-session' - await sessionStore.setActiveSession(sessionId) - - await chatPage.sendMessage('Hello') - expect(generatingSessionIds.value.has(sessionId)).toBe(true) - - // Simulate END event - window.api.emit(STREAM_EVENTS.END, { sessionId }) - await nextTick() - - expect(generatingSessionIds.value.has(sessionId)).toBe(false) -}) - -test('should remove session from generating set on ERROR', async () => { - const sessionId = 'test-session' - await sessionStore.setActiveSession(sessionId) - - await chatPage.sendMessage('Hello') - - // Simulate ERROR event - window.api.emit(STREAM_EVENTS.ERROR, { sessionId, error: 'Test error' }) - await nextTick() - - expect(generatingSessionIds.value.has(sessionId)).toBe(false) -}) -``` - ---- - -### Task 3: Update ChatInputBox to Check Generating State - -**File:** `src/renderer/src/components/chat/ChatInputBox.vue` - -**Current State:** -```vue -