From 1262d90bac3ec9565f25202841b3f0868db35007 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 28 May 2026 10:59:33 +0800 Subject: [PATCH] docs: clean stale sdd docs --- docs/ARCHITECTURE.md | 134 +- docs/FLOWS.md | 207 +- docs/README.md | 158 +- docs/architecture/acp-client-runtime/plan.md | 32 - docs/architecture/acp-client-runtime/spec.md | 47 - docs/architecture/acp-client-runtime/tasks.md | 16 - .../agent-provider-simplification/plan.md | 14 - .../agent-provider-simplification/spec.md | 43 - .../agent-provider-simplification/tasks.md | 7 - docs/architecture/agent-refactor/plan.md | 29 - docs/architecture/agent-refactor/spec.md | 39 - docs/architecture/agent-refactor/tasks.md | 14 - .../agent-refactor/tool-result-envelope.md | 112 - docs/architecture/agent-system.md | 20 +- .../architecture-simplification/plan.md | 37 - .../architecture-simplification/spec.md | 40 - .../architecture-simplification/tasks.md | 31 - .../chat-store-zero-migration/plan.md | 14 - .../chat-store-zero-migration/spec.md | 21 - .../chat-store-zero-migration/tasks.md | 7 - .../config-sqlite-storage/plan.md | 34 - .../config-sqlite-storage/spec.md | 32 - .../config-sqlite-storage/tasks.md | 9 - docs/architecture/event-system.md | 3 +- .../main-kernel-refactor/acceptance.md | 216 -- .../main-kernel-refactor/build-vs-buy.md | 136 - .../eventbus-migration.md | 134 - .../migration-governance.md | 189 -- .../architecture/main-kernel-refactor/plan.md | 236 -- .../ports-and-scheduler.md | 221 -- .../route-schema-catalog.md | 208 -- .../architecture/main-kernel-refactor/spec.md | 216 -- .../main-kernel-refactor/tasks.md | 84 - .../main-kernel-refactor/test-plan.md | 264 -- .../new-ui-implementation-plan.md | 619 ---- docs/architecture/remove-rebrand-tool/plan.md | 20 - docs/architecture/remove-rebrand-tool/spec.md | 27 - .../architecture/remove-rebrand-tool/tasks.md | 7 - .../renderer-main-single-track/plan.md | 468 --- .../renderer-main-single-track/spec.md | 198 -- .../renderer-main-single-track/tasks.md | 122 - docs/architecture/session-management.md | 11 + .../skill-runtime-hardening/plan.md | 32 - .../skill-runtime-hardening/spec.md | 48 - .../skill-runtime-hardening/tasks.md | 9 - .../sqlite-database-encryption/plan.md | 247 -- .../sqlite-database-encryption/spec.md | 117 - .../sqlite-database-encryption/tasks.md | 79 - .../startup-orchestration/acceptance.md | 126 - .../startup-orchestration/plan.md | 213 -- .../startup-orchestration/spec.md | 219 -- .../startup-orchestration/tasks.md | 98 - .../sync-config-import-versioning/plan.md | 24 - .../sync-config-import-versioning/spec.md | 22 - .../sync-config-import-versioning/tasks.md | 8 - docs/archives/agent-cleanup/plan.md | 43 - docs/archives/agent-cleanup/spec.md | 68 - docs/archives/agent-cleanup/tasks.md | 45 - docs/archives/agent-tooling-v2/plan.md | 398 --- docs/archives/agent-tooling-v2/spec.md | 300 -- docs/archives/agent-tooling-v2/tasks.md | 60 - .../agentpresenter-mvp-replacement/plan.md | 151 - .../agentpresenter-mvp-replacement/spec.md | 163 - .../agentpresenter-mvp-replacement/tasks.md | 247 -- docs/archives/ai-sdk-runtime/plan.md | 13 - docs/archives/ai-sdk-runtime/spec.md | 96 - docs/archives/ai-sdk-runtime/tasks.md | 20 - docs/archives/chat-audio-tts-routing/plan.md | 21 - docs/archives/chat-audio-tts-routing/spec.md | 26 - docs/archives/chat-audio-tts-routing/tasks.md | 8 - .../cua-runtime-plugin/non-macos-handoff.md | 55 - docs/archives/cua-runtime-plugin/plan.md | 546 ---- docs/archives/cua-runtime-plugin/spec.md | 444 --- docs/archives/cua-runtime-plugin/tasks.md | 332 -- docs/archives/default-model-settings/plan.md | 130 - docs/archives/default-model-settings/spec.md | 92 - docs/archives/default-model-settings/tasks.md | 60 - .../i18n-missing-translations/plan.md | 25 - .../i18n-missing-translations/spec.md | 22 - .../i18n-missing-translations/tasks.md | 8 - .../legacy-agentpresenter-architecture.md | 320 -- docs/archives/legacy-agentpresenter-flows.md | 654 ---- .../legacy-agentpresenter-retirement/plan.md | 30 - .../legacy-agentpresenter-retirement/spec.md | 56 - .../legacy-agentpresenter-retirement/tasks.md | 41 - .../plan.md | 23 - .../spec.md | 48 - .../tasks.md | 20 - docs/archives/legacy-llm-provider-runtime.md | 41 - docs/archives/mac-computer-use/README.md | 40 - docs/archives/mac-computer-use/packaging.md | 277 -- .../mac-computer-use/permissions-ux.md | 155 - docs/archives/mac-computer-use/plan.md | 231 -- docs/archives/mac-computer-use/references.md | 91 - docs/archives/mac-computer-use/spec.md | 89 - docs/archives/mac-computer-use/tasks.md | 117 - docs/archives/mac-computer-use/test-plan.md | 106 - docs/archives/multi-window-cleanup/plan.md | 10 - docs/archives/multi-window-cleanup/spec.md | 51 - docs/archives/multi-window-cleanup/tasks.md | 14 - docs/archives/new-agent/plan.md | 414 --- docs/archives/new-agent/spec.md | 216 -- docs/archives/new-agent/tasks.md | 116 - docs/archives/new-agent/v1-spec.md | 62 - docs/archives/new-agent/v2-spec.md | 135 - docs/archives/new-agent/v3-spec.md | 275 -- docs/archives/new-ui-agent-session/spec.md | 31 - docs/archives/new-ui-agent-store/spec.md | 158 - docs/archives/new-ui-chat-components/spec.md | 217 -- docs/archives/new-ui-implementation/todo.md | 314 -- .../new-ui-markdown-rendering/spec.md | 45 - docs/archives/new-ui-page-state/spec.md | 140 - docs/archives/new-ui-pages/spec.md | 275 -- docs/archives/new-ui-project-store/spec.md | 133 - docs/archives/new-ui-session-store/spec.md | 365 --- docs/archives/new-ui-sidebar/spec.md | 206 -- docs/archives/new-ui-status-bar/spec.md | 42 - .../provider-layer-simplification/plan.md | 18 - .../provider-layer-simplification/spec.md | 70 - .../provider-layer-simplification/tasks.md | 16 - docs/archives/remove-chat-mode/plan.md | 288 -- docs/archives/remove-chat-mode/spec.md | 179 -- docs/archives/skills-system/design.md | 647 ---- docs/archives/skills-system/research.md | 477 --- .../skills-syncing-antigravity.md | 237 -- .../skills-syncing-claude-code.md | 203 -- .../skills-system/skills-syncing-copilot.md | 247 -- .../skills-system/skills-syncing-cursor.md | 184 -- .../skills-system/skills-syncing-kiro.md | 286 -- .../skills-system/skills-syncing-tasks.md | 430 --- .../skills-system/skills-syncing-windsurf.md | 231 -- docs/archives/skills-system/skills-syncing.md | 721 ----- docs/archives/skills-system/ui-design.md | 549 ---- docs/archives/skills-ux-redesign/analysis.md | 223 -- docs/archives/skills-ux-redesign/spec.md | 777 ----- docs/archives/skills-ux-redesign/tasks.md | 450 --- docs/archives/specs-migration-2026-05-02.md | 39 - docs/archives/telegram-remote-control/plan.md | 235 -- docs/archives/telegram-remote-control/spec.md | 176 -- .../archives/telegram-remote-control/tasks.md | 112 - .../thread-presenter-migration-plan.md | 2727 ----------------- .../workspace-agent-refactoring-summary.md | 323 -- docs/features/acp-agent-uninstall/plan.md | 21 - docs/features/acp-agent-uninstall/spec.md | 25 - docs/features/acp-agent-uninstall/tasks.md | 7 - .../acp-session-config-options/plan.md | 127 - .../acp-session-config-options/spec.md | 93 - .../acp-session-config-options/tasks.md | 52 - docs/features/active-input-routing/plan.md | 40 - docs/features/active-input-routing/spec.md | 32 - docs/features/active-input-routing/tasks.md | 10 - docs/features/agent-db-legacy-import/plan.md | 46 - docs/features/agent-db-legacy-import/spec.md | 32 - docs/features/agent-db-legacy-import/tasks.md | 7 - .../agent-input-advanced-config/plan.md | 63 - .../agent-input-advanced-config/spec.md | 80 - .../agent-input-advanced-config/tasks.md | 62 - docs/features/agent-progress-todo/plan.md | 175 -- docs/features/agent-progress-todo/spec.md | 194 -- docs/features/agent-progress-todo/tasks.md | 79 - docs/features/app-spotlight-search/plan.md | 300 -- docs/features/app-spotlight-search/spec.md | 178 -- docs/features/app-spotlight-search/tasks.md | 84 - .../cc-switch-provider-import/plan.md | 27 - .../cc-switch-provider-import/spec.md | 30 - .../cc-switch-provider-import/tasks.md | 11 - .../chat-input-hero-transition/plan.md | 32 - .../chat-input-hero-transition/spec.md | 28 - .../chat-input-hero-transition/tasks.md | 7 - docs/features/chat-settings-control/plan.md | 14 - docs/features/chat-settings-control/spec.md | 85 - docs/features/chat-settings-control/tasks.md | 7 - .../chat-sidebar-input-polish/plan.md | 14 - .../chat-sidebar-input-polish/spec.md | 21 - .../chat-sidebar-input-polish/tasks.md | 7 - .../context-engineering-label/plan.md | 10 - .../context-engineering-label/spec.md | 15 - .../context-engineering-label/tasks.md | 5 - .../data-settings-danger-zone-entry/plan.md | 19 - .../data-settings-danger-zone-entry/spec.md | 19 - .../data-settings-danger-zone-entry/tasks.md | 7 - docs/features/data-sync-card-header/plan.md | 12 - docs/features/data-sync-card-header/spec.md | 16 - docs/features/data-sync-card-header/tasks.md | 6 - .../deepchat-data-import-skill/plan.md | 35 - .../deepchat-data-import-skill/spec.md | 38 - .../deepchat-data-import-skill/tasks.md | 9 - docs/features/edit-file-tool/plan.md | 14 - docs/features/edit-file-tool/spec.md | 77 - docs/features/edit-file-tool/tasks.md | 7 - docs/features/electron-vite-5-upgrade/plan.md | 14 - docs/features/electron-vite-5-upgrade/spec.md | 32 - .../features/electron-vite-5-upgrade/tasks.md | 7 - docs/features/file-attachment-support/plan.md | 14 - docs/features/file-attachment-support/spec.md | 19 - .../features/file-attachment-support/tasks.md | 7 - docs/features/floating-agent-widget/plan.md | 91 - docs/features/floating-agent-widget/spec.md | 52 - docs/features/floating-agent-widget/tasks.md | 9 - .../high-priority-i18n-languages/plan.md | 27 - .../high-priority-i18n-languages/spec.md | 29 - .../high-priority-i18n-languages/tasks.md | 8 - docs/features/hooks-notifications/plan.md | 43 - docs/features/hooks-notifications/spec.md | 175 -- docs/features/hooks-notifications/tasks.md | 7 - .../manual-compaction-command/plan.md | 28 - .../manual-compaction-command/spec.md | 39 - .../manual-compaction-command/tasks.md | 8 - docs/features/mcp-footer-alignment/plan.md | 12 - docs/features/mcp-footer-alignment/spec.md | 19 - docs/features/mcp-footer-alignment/tasks.md | 7 - docs/features/mcp-settings-status-bar/plan.md | 13 - docs/features/mcp-settings-status-bar/spec.md | 18 - .../features/mcp-settings-status-bar/tasks.md | 7 - docs/features/message-toolbar-actions/plan.md | 90 - docs/features/message-toolbar-actions/spec.md | 102 - .../features/message-toolbar-actions/tasks.md | 41 - docs/features/message-trace-storage/plan.md | 112 - docs/features/message-trace-storage/spec.md | 96 - docs/features/message-trace-storage/tasks.md | 55 - .../features/mistral-provider-support/plan.md | 19 - .../features/mistral-provider-support/spec.md | 20 - .../mistral-provider-support/tasks.md | 8 - docs/features/model-top-p-settings/plan.md | 38 - docs/features/model-top-p-settings/spec.md | 36 - docs/features/model-top-p-settings/tasks.md | 10 - docs/features/ollama-model-selection/plan.md | 48 - docs/features/ollama-model-selection/spec.md | 71 - docs/features/ollama-model-selection/tasks.md | 11 - .../plan.md | 42 - .../spec.md | 32 - .../tasks.md | 25 - .../openai-image-generation-settings/plan.md | 28 - .../openai-image-generation-settings/spec.md | 27 - .../openai-image-generation-settings/tasks.md | 10 - docs/features/privacy-mode/plan.md | 26 - docs/features/privacy-mode/spec.md | 51 - docs/features/privacy-mode/tasks.md | 9 - docs/features/process-tool/plan.md | 14 - docs/features/process-tool/spec.md | 109 - docs/features/process-tool/tasks.md | 7 - docs/features/provider-config-import/plan.md | 89 - docs/features/provider-config-import/spec.md | 99 - docs/features/provider-config-import/tasks.md | 14 - .../features/provider-deeplink-import/plan.md | 180 -- .../features/provider-deeplink-import/spec.md | 136 - .../provider-deeplink-import/tasks.md | 59 - .../provider-detail-simplification/plan.md | 18 - .../provider-detail-simplification/spec.md | 18 - .../provider-detail-simplification/tasks.md | 7 - docs/features/remote-acp-control/plan.md | 106 - docs/features/remote-acp-control/spec.md | 50 - docs/features/remote-acp-control/tasks.md | 67 - docs/features/remote-block-streaming/plan.md | 65 - docs/features/remote-block-streaming/spec.md | 41 - docs/features/remote-block-streaming/tasks.md | 37 - docs/features/remote-discord-lark/plan.md | 63 - docs/features/remote-discord-lark/spec.md | 41 - docs/features/remote-discord-lark/tasks.md | 14 - docs/features/remote-multi-channel/plan.md | 255 -- docs/features/remote-multi-channel/spec.md | 88 - docs/features/remote-multi-channel/tasks.md | 125 - docs/features/remote-process-log/plan.md | 14 - docs/features/remote-process-log/spec.md | 31 - docs/features/remote-process-log/tasks.md | 7 - docs/features/remote-system-group/plan.md | 9 - docs/features/remote-system-group/spec.md | 15 - docs/features/remote-system-group/tasks.md | 5 - .../features/remote-tool-interactions/plan.md | 125 - .../features/remote-tool-interactions/spec.md | 47 - .../remote-tool-interactions/tasks.md | 103 - docs/features/right-sidepanel/plan.md | 199 -- docs/features/right-sidepanel/spec.md | 133 - docs/features/right-sidepanel/tasks.md | 52 - docs/features/scheduled-tasks/plan.md | 63 - docs/features/scheduled-tasks/spec.md | 59 - docs/features/scheduled-tasks/tasks.md | 16 - .../settings-agent-capability-groups/plan.md | 11 - .../settings-agent-capability-groups/spec.md | 18 - .../settings-agent-capability-groups/tasks.md | 7 - docs/features/settings-control-center/plan.md | 27 - docs/features/settings-control-center/spec.md | 47 - .../features/settings-control-center/tasks.md | 13 - docs/features/settings-dashboard/plan.md | 46 - docs/features/settings-dashboard/spec.md | 33 - docs/features/settings-dashboard/tasks.md | 8 - docs/features/settings-environments/plan.md | 67 - docs/features/settings-environments/spec.md | 50 - docs/features/settings-environments/tasks.md | 7 - .../settings-overview-agent-card/plan.md | 11 - .../settings-overview-agent-card/spec.md | 17 - .../settings-overview-agent-card/tasks.md | 6 - docs/features/settings-overview-cards/plan.md | 20 - docs/features/settings-overview-cards/spec.md | 27 - .../features/settings-overview-cards/tasks.md | 8 - .../plan.md | 12 - .../spec.md | 20 - .../tasks.md | 7 - docs/features/shortcut-system-group/plan.md | 9 - docs/features/shortcut-system-group/spec.md | 15 - docs/features/shortcut-system-group/tasks.md | 5 - .../sidebar-session-context-menu/plan.md | 14 - .../sidebar-session-context-menu/spec.md | 29 - .../sidebar-session-context-menu/tasks.md | 7 - .../sidebar-workspace-shortcuts/plan.md | 14 - .../sidebar-workspace-shortcuts/spec.md | 25 - .../sidebar-workspace-shortcuts/tasks.md | 7 - docs/features/subagent-orchestrator/plan.md | 219 -- docs/features/subagent-orchestrator/spec.md | 312 -- docs/features/subagent-orchestrator/tasks.md | 55 - docs/features/tool-call-image-preview/plan.md | 14 - docs/features/tool-call-image-preview/spec.md | 27 - .../features/tool-call-image-preview/tasks.md | 7 - docs/features/unified-tts-provider/plan.md | 37 - docs/features/unified-tts-provider/spec.md | 34 - docs/features/unified-tts-provider/tasks.md | 22 - docs/features/user-message-collapse/plan.md | 114 - docs/features/user-message-collapse/spec.md | 42 - docs/features/user-message-collapse/tasks.md | 60 - docs/features/v1-0-4-stable-release/plan.md | 20 - docs/features/v1-0-4-stable-release/spec.md | 22 - docs/features/v1-0-4-stable-release/tasks.md | 8 - docs/features/v1-0-5-beta-1-release/plan.md | 22 - docs/features/v1-0-5-beta-1-release/spec.md | 22 - docs/features/v1-0-5-beta-1-release/tasks.md | 9 - .../voice-input-transcription/plan.md | 44 - .../voice-input-transcription/spec.md | 45 - .../voice-input-transcription/tasks.md | 16 - docs/features/workspace-lifecycle/plan.md | 45 - docs/features/workspace-lifecycle/spec.md | 26 - docs/features/workspace-lifecycle/tasks.md | 7 - .../yobrowser-activity-visualization/plan.md | 28 - .../yobrowser-activity-visualization/spec.md | 29 - .../yobrowser-activity-visualization/tasks.md | 9 - docs/features/yobrowser-optimization/plan.md | 42 - docs/features/yobrowser-optimization/spec.md | 67 - docs/features/yobrowser-optimization/tasks.md | 9 - docs/guides/code-navigation.md | 8 +- docs/guides/getting-started.md | 17 +- docs/issues/acp-context-budget-bypass/plan.md | 26 - docs/issues/acp-context-budget-bypass/spec.md | 36 - .../issues/acp-context-budget-bypass/tasks.md | 9 - .../issues/acp-initialization-logging/plan.md | 29 - .../issues/acp-initialization-logging/spec.md | 26 - .../acp-initialization-logging/tasks.md | 8 - docs/issues/acp-pr-1614-review-fixes/plan.md | 16 - docs/issues/acp-pr-1614-review-fixes/spec.md | 21 - docs/issues/acp-pr-1614-review-fixes/tasks.md | 7 - docs/issues/acp-workdir-npx-recovery/plan.md | 21 - docs/issues/acp-workdir-npx-recovery/spec.md | 26 - docs/issues/acp-workdir-npx-recovery/tasks.md | 8 - docs/issues/agent-tool-context-budget/plan.md | 81 - docs/issues/agent-tool-context-budget/spec.md | 87 - .../issues/agent-tool-context-budget/tasks.md | 20 - .../background-exec-shell-fallback/plan.md | 27 - .../background-exec-shell-fallback/spec.md | 25 - .../background-exec-shell-fallback/tasks.md | 10 - docs/issues/beta-7-release-test-gate/plan.md | 15 - docs/issues/beta-7-release-test-gate/spec.md | 21 - docs/issues/beta-7-release-test-gate/tasks.md | 8 - .../chat-history-pagination-stability/plan.md | 29 - .../chat-history-pagination-stability/spec.md | 35 - .../tasks.md | 8 - docs/issues/cua-driver-v0-1-5-sync/plan.md | 30 - docs/issues/cua-driver-v0-1-5-sync/spec.md | 36 - docs/issues/cua-driver-v0-1-5-sync/tasks.md | 9 - docs/issues/cua-driver-v014-sync/plan.md | 25 - docs/issues/cua-driver-v014-sync/spec.md | 29 - docs/issues/cua-driver-v014-sync/tasks.md | 9 - .../issues/cua-settings-empty-message/plan.md | 23 - .../issues/cua-settings-empty-message/spec.md | 22 - .../cua-settings-empty-message/tasks.md | 6 - .../danger-zone-button-overflow/plan.md | 11 - .../danger-zone-button-overflow/spec.md | 16 - .../danger-zone-button-overflow/tasks.md | 6 - docs/issues/e2e-smoke-regression/plan.md | 355 --- docs/issues/e2e-smoke-regression/spec.md | 140 - docs/issues/e2e-smoke-regression/tasks.md | 139 - docs/issues/failed-message-context/plan.md | 19 - docs/issues/failed-message-context/spec.md | 22 - docs/issues/failed-message-context/tasks.md | 7 - .../feishu-post-message-payload/plan.md | 12 - .../feishu-post-message-payload/spec.md | 21 - .../feishu-post-message-payload/tasks.md | 7 - .../markdown-smooth-streaming-control/plan.md | 19 - .../markdown-smooth-streaming-control/spec.md | 17 - .../tasks.md | 8 - docs/issues/mcp-toggle-opens-detail/plan.md | 16 - docs/issues/mcp-toggle-opens-detail/spec.md | 20 - docs/issues/mcp-toggle-opens-detail/tasks.md | 6 - docs/issues/merge-dev-into-gen-video/plan.md | 20 - docs/issues/merge-dev-into-gen-video/spec.md | 23 - docs/issues/merge-dev-into-gen-video/tasks.md | 8 - .../permission-flow-stabilization/plan.md | 14 - .../permission-flow-stabilization/spec.md | 119 - .../permission-flow-stabilization/tasks.md | 7 - .../plugin-mcp-lifecycle-isolation/plan.md | 32 - .../plugin-mcp-lifecycle-isolation/spec.md | 26 - .../plugin-mcp-lifecycle-isolation/tasks.md | 9 - .../plugin-settings-surface-isolation/plan.md | 67 - .../plugin-settings-surface-isolation/spec.md | 52 - .../tasks.md | 17 - .../issues/plugin-skill-tool-guidance/plan.md | 28 - .../issues/plugin-skill-tool-guidance/spec.md | 34 - .../plugin-skill-tool-guidance/tasks.md | 6 - docs/issues/pr-review-followups/plan.md | 13 - docs/issues/pr-review-followups/spec.md | 20 - docs/issues/pr-review-followups/tasks.md | 7 - .../provider-validation-disabled/plan.md | 21 - .../provider-validation-disabled/spec.md | 22 - .../provider-validation-disabled/tasks.md | 7 - .../question-tool-prompt-optimization/plan.md | 14 - .../question-tool-prompt-optimization/spec.md | 28 - .../tasks.md | 7 - docs/issues/tool-call-path-summary/plan.md | 17 - docs/issues/tool-call-path-summary/spec.md | 25 - docs/issues/tool-call-path-summary/tasks.md | 7 - docs/issues/tool-output-guardrails/plan.md | 30 - docs/issues/tool-output-guardrails/spec.md | 77 - docs/issues/tool-output-guardrails/tasks.md | 7 - .../windows-exec-output-encoding/plan.md | 21 - .../windows-exec-output-encoding/spec.md | 26 - .../windows-exec-output-encoding/tasks.md | 8 - docs/spec-driven-dev.md | 9 +- .../sqlite-mainline-normalization/plan.md | 182 -- .../sqlite-mainline-normalization/spec.md | 123 - .../sqlite-mainline-normalization/tasks.md | 80 - 427 files changed, 304 insertions(+), 35608 deletions(-) delete mode 100644 docs/architecture/acp-client-runtime/plan.md delete mode 100644 docs/architecture/acp-client-runtime/spec.md delete mode 100644 docs/architecture/acp-client-runtime/tasks.md delete mode 100644 docs/architecture/agent-provider-simplification/plan.md delete mode 100644 docs/architecture/agent-provider-simplification/spec.md delete mode 100644 docs/architecture/agent-provider-simplification/tasks.md delete mode 100644 docs/architecture/agent-refactor/plan.md delete mode 100644 docs/architecture/agent-refactor/spec.md delete mode 100644 docs/architecture/agent-refactor/tasks.md delete mode 100644 docs/architecture/agent-refactor/tool-result-envelope.md delete mode 100644 docs/architecture/architecture-simplification/plan.md delete mode 100644 docs/architecture/architecture-simplification/spec.md delete mode 100644 docs/architecture/architecture-simplification/tasks.md delete mode 100644 docs/architecture/chat-store-zero-migration/plan.md delete mode 100644 docs/architecture/chat-store-zero-migration/spec.md delete mode 100644 docs/architecture/chat-store-zero-migration/tasks.md delete mode 100644 docs/architecture/config-sqlite-storage/plan.md delete mode 100644 docs/architecture/config-sqlite-storage/spec.md delete mode 100644 docs/architecture/config-sqlite-storage/tasks.md delete mode 100644 docs/architecture/main-kernel-refactor/acceptance.md delete mode 100644 docs/architecture/main-kernel-refactor/build-vs-buy.md delete mode 100644 docs/architecture/main-kernel-refactor/eventbus-migration.md delete mode 100644 docs/architecture/main-kernel-refactor/migration-governance.md delete mode 100644 docs/architecture/main-kernel-refactor/plan.md delete mode 100644 docs/architecture/main-kernel-refactor/ports-and-scheduler.md delete mode 100644 docs/architecture/main-kernel-refactor/route-schema-catalog.md delete mode 100644 docs/architecture/main-kernel-refactor/spec.md delete mode 100644 docs/architecture/main-kernel-refactor/tasks.md delete mode 100644 docs/architecture/main-kernel-refactor/test-plan.md delete mode 100644 docs/architecture/new-ui-implementation-plan.md delete mode 100644 docs/architecture/remove-rebrand-tool/plan.md delete mode 100644 docs/architecture/remove-rebrand-tool/spec.md delete mode 100644 docs/architecture/remove-rebrand-tool/tasks.md delete mode 100644 docs/architecture/renderer-main-single-track/plan.md delete mode 100644 docs/architecture/renderer-main-single-track/spec.md delete mode 100644 docs/architecture/renderer-main-single-track/tasks.md delete mode 100644 docs/architecture/skill-runtime-hardening/plan.md delete mode 100644 docs/architecture/skill-runtime-hardening/spec.md delete mode 100644 docs/architecture/skill-runtime-hardening/tasks.md delete mode 100644 docs/architecture/sqlite-database-encryption/plan.md delete mode 100644 docs/architecture/sqlite-database-encryption/spec.md delete mode 100644 docs/architecture/sqlite-database-encryption/tasks.md delete mode 100644 docs/architecture/startup-orchestration/acceptance.md delete mode 100644 docs/architecture/startup-orchestration/plan.md delete mode 100644 docs/architecture/startup-orchestration/spec.md delete mode 100644 docs/architecture/startup-orchestration/tasks.md delete mode 100644 docs/architecture/sync-config-import-versioning/plan.md delete mode 100644 docs/architecture/sync-config-import-versioning/spec.md delete mode 100644 docs/architecture/sync-config-import-versioning/tasks.md delete mode 100644 docs/archives/agent-cleanup/plan.md delete mode 100644 docs/archives/agent-cleanup/spec.md delete mode 100644 docs/archives/agent-cleanup/tasks.md delete mode 100644 docs/archives/agent-tooling-v2/plan.md delete mode 100644 docs/archives/agent-tooling-v2/spec.md delete mode 100644 docs/archives/agent-tooling-v2/tasks.md delete mode 100644 docs/archives/agentpresenter-mvp-replacement/plan.md delete mode 100644 docs/archives/agentpresenter-mvp-replacement/spec.md delete mode 100644 docs/archives/agentpresenter-mvp-replacement/tasks.md delete mode 100644 docs/archives/ai-sdk-runtime/plan.md delete mode 100644 docs/archives/ai-sdk-runtime/spec.md delete mode 100644 docs/archives/ai-sdk-runtime/tasks.md delete mode 100644 docs/archives/chat-audio-tts-routing/plan.md delete mode 100644 docs/archives/chat-audio-tts-routing/spec.md delete mode 100644 docs/archives/chat-audio-tts-routing/tasks.md delete mode 100644 docs/archives/cua-runtime-plugin/non-macos-handoff.md delete mode 100644 docs/archives/cua-runtime-plugin/plan.md delete mode 100644 docs/archives/cua-runtime-plugin/spec.md delete mode 100644 docs/archives/cua-runtime-plugin/tasks.md delete mode 100644 docs/archives/default-model-settings/plan.md delete mode 100644 docs/archives/default-model-settings/spec.md delete mode 100644 docs/archives/default-model-settings/tasks.md delete mode 100644 docs/archives/i18n-missing-translations/plan.md delete mode 100644 docs/archives/i18n-missing-translations/spec.md delete mode 100644 docs/archives/i18n-missing-translations/tasks.md delete mode 100644 docs/archives/legacy-agentpresenter-architecture.md delete mode 100644 docs/archives/legacy-agentpresenter-flows.md delete mode 100644 docs/archives/legacy-agentpresenter-retirement/plan.md delete mode 100644 docs/archives/legacy-agentpresenter-retirement/spec.md delete mode 100644 docs/archives/legacy-agentpresenter-retirement/tasks.md delete mode 100644 docs/archives/legacy-llm-provider-runtime-retirement/plan.md delete mode 100644 docs/archives/legacy-llm-provider-runtime-retirement/spec.md delete mode 100644 docs/archives/legacy-llm-provider-runtime-retirement/tasks.md delete mode 100644 docs/archives/legacy-llm-provider-runtime.md delete mode 100644 docs/archives/mac-computer-use/README.md delete mode 100644 docs/archives/mac-computer-use/packaging.md delete mode 100644 docs/archives/mac-computer-use/permissions-ux.md delete mode 100644 docs/archives/mac-computer-use/plan.md delete mode 100644 docs/archives/mac-computer-use/references.md delete mode 100644 docs/archives/mac-computer-use/spec.md delete mode 100644 docs/archives/mac-computer-use/tasks.md delete mode 100644 docs/archives/mac-computer-use/test-plan.md delete mode 100644 docs/archives/multi-window-cleanup/plan.md delete mode 100644 docs/archives/multi-window-cleanup/spec.md delete mode 100644 docs/archives/multi-window-cleanup/tasks.md delete mode 100644 docs/archives/new-agent/plan.md delete mode 100644 docs/archives/new-agent/spec.md delete mode 100644 docs/archives/new-agent/tasks.md delete mode 100644 docs/archives/new-agent/v1-spec.md delete mode 100644 docs/archives/new-agent/v2-spec.md delete mode 100644 docs/archives/new-agent/v3-spec.md delete mode 100644 docs/archives/new-ui-agent-session/spec.md delete mode 100644 docs/archives/new-ui-agent-store/spec.md delete mode 100644 docs/archives/new-ui-chat-components/spec.md delete mode 100644 docs/archives/new-ui-implementation/todo.md delete mode 100644 docs/archives/new-ui-markdown-rendering/spec.md delete mode 100644 docs/archives/new-ui-page-state/spec.md delete mode 100644 docs/archives/new-ui-pages/spec.md delete mode 100644 docs/archives/new-ui-project-store/spec.md delete mode 100644 docs/archives/new-ui-session-store/spec.md delete mode 100644 docs/archives/new-ui-sidebar/spec.md delete mode 100644 docs/archives/new-ui-status-bar/spec.md delete mode 100644 docs/archives/provider-layer-simplification/plan.md delete mode 100644 docs/archives/provider-layer-simplification/spec.md delete mode 100644 docs/archives/provider-layer-simplification/tasks.md delete mode 100644 docs/archives/remove-chat-mode/plan.md delete mode 100644 docs/archives/remove-chat-mode/spec.md delete mode 100644 docs/archives/skills-system/design.md delete mode 100644 docs/archives/skills-system/research.md delete mode 100644 docs/archives/skills-system/skills-syncing-antigravity.md delete mode 100644 docs/archives/skills-system/skills-syncing-claude-code.md delete mode 100644 docs/archives/skills-system/skills-syncing-copilot.md delete mode 100644 docs/archives/skills-system/skills-syncing-cursor.md delete mode 100644 docs/archives/skills-system/skills-syncing-kiro.md delete mode 100644 docs/archives/skills-system/skills-syncing-tasks.md delete mode 100644 docs/archives/skills-system/skills-syncing-windsurf.md delete mode 100644 docs/archives/skills-system/skills-syncing.md delete mode 100644 docs/archives/skills-system/ui-design.md delete mode 100644 docs/archives/skills-ux-redesign/analysis.md delete mode 100644 docs/archives/skills-ux-redesign/spec.md delete mode 100644 docs/archives/skills-ux-redesign/tasks.md delete mode 100644 docs/archives/specs-migration-2026-05-02.md delete mode 100644 docs/archives/telegram-remote-control/plan.md delete mode 100644 docs/archives/telegram-remote-control/spec.md delete mode 100644 docs/archives/telegram-remote-control/tasks.md delete mode 100644 docs/archives/thread-presenter-migration-plan.md delete mode 100644 docs/archives/workspace-agent-refactoring-summary.md delete mode 100644 docs/features/acp-agent-uninstall/plan.md delete mode 100644 docs/features/acp-agent-uninstall/spec.md delete mode 100644 docs/features/acp-agent-uninstall/tasks.md delete mode 100644 docs/features/acp-session-config-options/plan.md delete mode 100644 docs/features/acp-session-config-options/spec.md delete mode 100644 docs/features/acp-session-config-options/tasks.md delete mode 100644 docs/features/active-input-routing/plan.md delete mode 100644 docs/features/active-input-routing/spec.md delete mode 100644 docs/features/active-input-routing/tasks.md delete mode 100644 docs/features/agent-db-legacy-import/plan.md delete mode 100644 docs/features/agent-db-legacy-import/spec.md delete mode 100644 docs/features/agent-db-legacy-import/tasks.md delete mode 100644 docs/features/agent-input-advanced-config/plan.md delete mode 100644 docs/features/agent-input-advanced-config/spec.md delete mode 100644 docs/features/agent-input-advanced-config/tasks.md delete mode 100644 docs/features/agent-progress-todo/plan.md delete mode 100644 docs/features/agent-progress-todo/spec.md delete mode 100644 docs/features/agent-progress-todo/tasks.md delete mode 100644 docs/features/app-spotlight-search/plan.md delete mode 100644 docs/features/app-spotlight-search/spec.md delete mode 100644 docs/features/app-spotlight-search/tasks.md delete mode 100644 docs/features/cc-switch-provider-import/plan.md delete mode 100644 docs/features/cc-switch-provider-import/spec.md delete mode 100644 docs/features/cc-switch-provider-import/tasks.md delete mode 100644 docs/features/chat-input-hero-transition/plan.md delete mode 100644 docs/features/chat-input-hero-transition/spec.md delete mode 100644 docs/features/chat-input-hero-transition/tasks.md delete mode 100644 docs/features/chat-settings-control/plan.md delete mode 100644 docs/features/chat-settings-control/spec.md delete mode 100644 docs/features/chat-settings-control/tasks.md delete mode 100644 docs/features/chat-sidebar-input-polish/plan.md delete mode 100644 docs/features/chat-sidebar-input-polish/spec.md delete mode 100644 docs/features/chat-sidebar-input-polish/tasks.md delete mode 100644 docs/features/context-engineering-label/plan.md delete mode 100644 docs/features/context-engineering-label/spec.md delete mode 100644 docs/features/context-engineering-label/tasks.md delete mode 100644 docs/features/data-settings-danger-zone-entry/plan.md delete mode 100644 docs/features/data-settings-danger-zone-entry/spec.md delete mode 100644 docs/features/data-settings-danger-zone-entry/tasks.md delete mode 100644 docs/features/data-sync-card-header/plan.md delete mode 100644 docs/features/data-sync-card-header/spec.md delete mode 100644 docs/features/data-sync-card-header/tasks.md delete mode 100644 docs/features/deepchat-data-import-skill/plan.md delete mode 100644 docs/features/deepchat-data-import-skill/spec.md delete mode 100644 docs/features/deepchat-data-import-skill/tasks.md delete mode 100644 docs/features/edit-file-tool/plan.md delete mode 100644 docs/features/edit-file-tool/spec.md delete mode 100644 docs/features/edit-file-tool/tasks.md delete mode 100644 docs/features/electron-vite-5-upgrade/plan.md delete mode 100644 docs/features/electron-vite-5-upgrade/spec.md delete mode 100644 docs/features/electron-vite-5-upgrade/tasks.md delete mode 100644 docs/features/file-attachment-support/plan.md delete mode 100644 docs/features/file-attachment-support/spec.md delete mode 100644 docs/features/file-attachment-support/tasks.md delete mode 100644 docs/features/floating-agent-widget/plan.md delete mode 100644 docs/features/floating-agent-widget/spec.md delete mode 100644 docs/features/floating-agent-widget/tasks.md delete mode 100644 docs/features/high-priority-i18n-languages/plan.md delete mode 100644 docs/features/high-priority-i18n-languages/spec.md delete mode 100644 docs/features/high-priority-i18n-languages/tasks.md delete mode 100644 docs/features/hooks-notifications/plan.md delete mode 100644 docs/features/hooks-notifications/spec.md delete mode 100644 docs/features/hooks-notifications/tasks.md delete mode 100644 docs/features/manual-compaction-command/plan.md delete mode 100644 docs/features/manual-compaction-command/spec.md delete mode 100644 docs/features/manual-compaction-command/tasks.md delete mode 100644 docs/features/mcp-footer-alignment/plan.md delete mode 100644 docs/features/mcp-footer-alignment/spec.md delete mode 100644 docs/features/mcp-footer-alignment/tasks.md delete mode 100644 docs/features/mcp-settings-status-bar/plan.md delete mode 100644 docs/features/mcp-settings-status-bar/spec.md delete mode 100644 docs/features/mcp-settings-status-bar/tasks.md delete mode 100644 docs/features/message-toolbar-actions/plan.md delete mode 100644 docs/features/message-toolbar-actions/spec.md delete mode 100644 docs/features/message-toolbar-actions/tasks.md delete mode 100644 docs/features/message-trace-storage/plan.md delete mode 100644 docs/features/message-trace-storage/spec.md delete mode 100644 docs/features/message-trace-storage/tasks.md delete mode 100644 docs/features/mistral-provider-support/plan.md delete mode 100644 docs/features/mistral-provider-support/spec.md delete mode 100644 docs/features/mistral-provider-support/tasks.md delete mode 100644 docs/features/model-top-p-settings/plan.md delete mode 100644 docs/features/model-top-p-settings/spec.md delete mode 100644 docs/features/model-top-p-settings/tasks.md delete mode 100644 docs/features/ollama-model-selection/plan.md delete mode 100644 docs/features/ollama-model-selection/spec.md delete mode 100644 docs/features/ollama-model-selection/tasks.md delete mode 100644 docs/features/openai-compatible-video-generation/plan.md delete mode 100644 docs/features/openai-compatible-video-generation/spec.md delete mode 100644 docs/features/openai-compatible-video-generation/tasks.md delete mode 100644 docs/features/openai-image-generation-settings/plan.md delete mode 100644 docs/features/openai-image-generation-settings/spec.md delete mode 100644 docs/features/openai-image-generation-settings/tasks.md delete mode 100644 docs/features/privacy-mode/plan.md delete mode 100644 docs/features/privacy-mode/spec.md delete mode 100644 docs/features/privacy-mode/tasks.md delete mode 100644 docs/features/process-tool/plan.md delete mode 100644 docs/features/process-tool/spec.md delete mode 100644 docs/features/process-tool/tasks.md delete mode 100644 docs/features/provider-config-import/plan.md delete mode 100644 docs/features/provider-config-import/spec.md delete mode 100644 docs/features/provider-config-import/tasks.md delete mode 100644 docs/features/provider-deeplink-import/plan.md delete mode 100644 docs/features/provider-deeplink-import/spec.md delete mode 100644 docs/features/provider-deeplink-import/tasks.md delete mode 100644 docs/features/provider-detail-simplification/plan.md delete mode 100644 docs/features/provider-detail-simplification/spec.md delete mode 100644 docs/features/provider-detail-simplification/tasks.md delete mode 100644 docs/features/remote-acp-control/plan.md delete mode 100644 docs/features/remote-acp-control/spec.md delete mode 100644 docs/features/remote-acp-control/tasks.md delete mode 100644 docs/features/remote-block-streaming/plan.md delete mode 100644 docs/features/remote-block-streaming/spec.md delete mode 100644 docs/features/remote-block-streaming/tasks.md delete mode 100644 docs/features/remote-discord-lark/plan.md delete mode 100644 docs/features/remote-discord-lark/spec.md delete mode 100644 docs/features/remote-discord-lark/tasks.md delete mode 100644 docs/features/remote-multi-channel/plan.md delete mode 100644 docs/features/remote-multi-channel/spec.md delete mode 100644 docs/features/remote-multi-channel/tasks.md delete mode 100644 docs/features/remote-process-log/plan.md delete mode 100644 docs/features/remote-process-log/spec.md delete mode 100644 docs/features/remote-process-log/tasks.md delete mode 100644 docs/features/remote-system-group/plan.md delete mode 100644 docs/features/remote-system-group/spec.md delete mode 100644 docs/features/remote-system-group/tasks.md delete mode 100644 docs/features/remote-tool-interactions/plan.md delete mode 100644 docs/features/remote-tool-interactions/spec.md delete mode 100644 docs/features/remote-tool-interactions/tasks.md delete mode 100644 docs/features/right-sidepanel/plan.md delete mode 100644 docs/features/right-sidepanel/spec.md delete mode 100644 docs/features/right-sidepanel/tasks.md delete mode 100644 docs/features/scheduled-tasks/plan.md delete mode 100644 docs/features/scheduled-tasks/spec.md delete mode 100644 docs/features/scheduled-tasks/tasks.md delete mode 100644 docs/features/settings-agent-capability-groups/plan.md delete mode 100644 docs/features/settings-agent-capability-groups/spec.md delete mode 100644 docs/features/settings-agent-capability-groups/tasks.md delete mode 100644 docs/features/settings-control-center/plan.md delete mode 100644 docs/features/settings-control-center/spec.md delete mode 100644 docs/features/settings-control-center/tasks.md delete mode 100644 docs/features/settings-dashboard/plan.md delete mode 100644 docs/features/settings-dashboard/spec.md delete mode 100644 docs/features/settings-dashboard/tasks.md delete mode 100644 docs/features/settings-environments/plan.md delete mode 100644 docs/features/settings-environments/spec.md delete mode 100644 docs/features/settings-environments/tasks.md delete mode 100644 docs/features/settings-overview-agent-card/plan.md delete mode 100644 docs/features/settings-overview-agent-card/spec.md delete mode 100644 docs/features/settings-overview-agent-card/tasks.md delete mode 100644 docs/features/settings-overview-cards/plan.md delete mode 100644 docs/features/settings-overview-cards/spec.md delete mode 100644 docs/features/settings-overview-cards/tasks.md delete mode 100644 docs/features/settings-overview-quickstart-dashboard/plan.md delete mode 100644 docs/features/settings-overview-quickstart-dashboard/spec.md delete mode 100644 docs/features/settings-overview-quickstart-dashboard/tasks.md delete mode 100644 docs/features/shortcut-system-group/plan.md delete mode 100644 docs/features/shortcut-system-group/spec.md delete mode 100644 docs/features/shortcut-system-group/tasks.md delete mode 100644 docs/features/sidebar-session-context-menu/plan.md delete mode 100644 docs/features/sidebar-session-context-menu/spec.md delete mode 100644 docs/features/sidebar-session-context-menu/tasks.md delete mode 100644 docs/features/sidebar-workspace-shortcuts/plan.md delete mode 100644 docs/features/sidebar-workspace-shortcuts/spec.md delete mode 100644 docs/features/sidebar-workspace-shortcuts/tasks.md delete mode 100644 docs/features/subagent-orchestrator/plan.md delete mode 100644 docs/features/subagent-orchestrator/spec.md delete mode 100644 docs/features/subagent-orchestrator/tasks.md delete mode 100644 docs/features/tool-call-image-preview/plan.md delete mode 100644 docs/features/tool-call-image-preview/spec.md delete mode 100644 docs/features/tool-call-image-preview/tasks.md delete mode 100644 docs/features/unified-tts-provider/plan.md delete mode 100644 docs/features/unified-tts-provider/spec.md delete mode 100644 docs/features/unified-tts-provider/tasks.md delete mode 100644 docs/features/user-message-collapse/plan.md delete mode 100644 docs/features/user-message-collapse/spec.md delete mode 100644 docs/features/user-message-collapse/tasks.md delete mode 100644 docs/features/v1-0-4-stable-release/plan.md delete mode 100644 docs/features/v1-0-4-stable-release/spec.md delete mode 100644 docs/features/v1-0-4-stable-release/tasks.md delete mode 100644 docs/features/v1-0-5-beta-1-release/plan.md delete mode 100644 docs/features/v1-0-5-beta-1-release/spec.md delete mode 100644 docs/features/v1-0-5-beta-1-release/tasks.md delete mode 100644 docs/features/voice-input-transcription/plan.md delete mode 100644 docs/features/voice-input-transcription/spec.md delete mode 100644 docs/features/voice-input-transcription/tasks.md delete mode 100644 docs/features/workspace-lifecycle/plan.md delete mode 100644 docs/features/workspace-lifecycle/spec.md delete mode 100644 docs/features/workspace-lifecycle/tasks.md delete mode 100644 docs/features/yobrowser-activity-visualization/plan.md delete mode 100644 docs/features/yobrowser-activity-visualization/spec.md delete mode 100644 docs/features/yobrowser-activity-visualization/tasks.md delete mode 100644 docs/features/yobrowser-optimization/plan.md delete mode 100644 docs/features/yobrowser-optimization/spec.md delete mode 100644 docs/features/yobrowser-optimization/tasks.md delete mode 100644 docs/issues/acp-context-budget-bypass/plan.md delete mode 100644 docs/issues/acp-context-budget-bypass/spec.md delete mode 100644 docs/issues/acp-context-budget-bypass/tasks.md delete mode 100644 docs/issues/acp-initialization-logging/plan.md delete mode 100644 docs/issues/acp-initialization-logging/spec.md delete mode 100644 docs/issues/acp-initialization-logging/tasks.md delete mode 100644 docs/issues/acp-pr-1614-review-fixes/plan.md delete mode 100644 docs/issues/acp-pr-1614-review-fixes/spec.md delete mode 100644 docs/issues/acp-pr-1614-review-fixes/tasks.md delete mode 100644 docs/issues/acp-workdir-npx-recovery/plan.md delete mode 100644 docs/issues/acp-workdir-npx-recovery/spec.md delete mode 100644 docs/issues/acp-workdir-npx-recovery/tasks.md delete mode 100644 docs/issues/agent-tool-context-budget/plan.md delete mode 100644 docs/issues/agent-tool-context-budget/spec.md delete mode 100644 docs/issues/agent-tool-context-budget/tasks.md delete mode 100644 docs/issues/background-exec-shell-fallback/plan.md delete mode 100644 docs/issues/background-exec-shell-fallback/spec.md delete mode 100644 docs/issues/background-exec-shell-fallback/tasks.md delete mode 100644 docs/issues/beta-7-release-test-gate/plan.md delete mode 100644 docs/issues/beta-7-release-test-gate/spec.md delete mode 100644 docs/issues/beta-7-release-test-gate/tasks.md delete mode 100644 docs/issues/chat-history-pagination-stability/plan.md delete mode 100644 docs/issues/chat-history-pagination-stability/spec.md delete mode 100644 docs/issues/chat-history-pagination-stability/tasks.md delete mode 100644 docs/issues/cua-driver-v0-1-5-sync/plan.md delete mode 100644 docs/issues/cua-driver-v0-1-5-sync/spec.md delete mode 100644 docs/issues/cua-driver-v0-1-5-sync/tasks.md delete mode 100644 docs/issues/cua-driver-v014-sync/plan.md delete mode 100644 docs/issues/cua-driver-v014-sync/spec.md delete mode 100644 docs/issues/cua-driver-v014-sync/tasks.md delete mode 100644 docs/issues/cua-settings-empty-message/plan.md delete mode 100644 docs/issues/cua-settings-empty-message/spec.md delete mode 100644 docs/issues/cua-settings-empty-message/tasks.md delete mode 100644 docs/issues/danger-zone-button-overflow/plan.md delete mode 100644 docs/issues/danger-zone-button-overflow/spec.md delete mode 100644 docs/issues/danger-zone-button-overflow/tasks.md delete mode 100644 docs/issues/e2e-smoke-regression/plan.md delete mode 100644 docs/issues/e2e-smoke-regression/spec.md delete mode 100644 docs/issues/e2e-smoke-regression/tasks.md delete mode 100644 docs/issues/failed-message-context/plan.md delete mode 100644 docs/issues/failed-message-context/spec.md delete mode 100644 docs/issues/failed-message-context/tasks.md delete mode 100644 docs/issues/feishu-post-message-payload/plan.md delete mode 100644 docs/issues/feishu-post-message-payload/spec.md delete mode 100644 docs/issues/feishu-post-message-payload/tasks.md delete mode 100644 docs/issues/markdown-smooth-streaming-control/plan.md delete mode 100644 docs/issues/markdown-smooth-streaming-control/spec.md delete mode 100644 docs/issues/markdown-smooth-streaming-control/tasks.md delete mode 100644 docs/issues/mcp-toggle-opens-detail/plan.md delete mode 100644 docs/issues/mcp-toggle-opens-detail/spec.md delete mode 100644 docs/issues/mcp-toggle-opens-detail/tasks.md delete mode 100644 docs/issues/merge-dev-into-gen-video/plan.md delete mode 100644 docs/issues/merge-dev-into-gen-video/spec.md delete mode 100644 docs/issues/merge-dev-into-gen-video/tasks.md delete mode 100644 docs/issues/permission-flow-stabilization/plan.md delete mode 100644 docs/issues/permission-flow-stabilization/spec.md delete mode 100644 docs/issues/permission-flow-stabilization/tasks.md delete mode 100644 docs/issues/plugin-mcp-lifecycle-isolation/plan.md delete mode 100644 docs/issues/plugin-mcp-lifecycle-isolation/spec.md delete mode 100644 docs/issues/plugin-mcp-lifecycle-isolation/tasks.md delete mode 100644 docs/issues/plugin-settings-surface-isolation/plan.md delete mode 100644 docs/issues/plugin-settings-surface-isolation/spec.md delete mode 100644 docs/issues/plugin-settings-surface-isolation/tasks.md delete mode 100644 docs/issues/plugin-skill-tool-guidance/plan.md delete mode 100644 docs/issues/plugin-skill-tool-guidance/spec.md delete mode 100644 docs/issues/plugin-skill-tool-guidance/tasks.md delete mode 100644 docs/issues/pr-review-followups/plan.md delete mode 100644 docs/issues/pr-review-followups/spec.md delete mode 100644 docs/issues/pr-review-followups/tasks.md delete mode 100644 docs/issues/provider-validation-disabled/plan.md delete mode 100644 docs/issues/provider-validation-disabled/spec.md delete mode 100644 docs/issues/provider-validation-disabled/tasks.md delete mode 100644 docs/issues/question-tool-prompt-optimization/plan.md delete mode 100644 docs/issues/question-tool-prompt-optimization/spec.md delete mode 100644 docs/issues/question-tool-prompt-optimization/tasks.md delete mode 100644 docs/issues/tool-call-path-summary/plan.md delete mode 100644 docs/issues/tool-call-path-summary/spec.md delete mode 100644 docs/issues/tool-call-path-summary/tasks.md delete mode 100644 docs/issues/tool-output-guardrails/plan.md delete mode 100644 docs/issues/tool-output-guardrails/spec.md delete mode 100644 docs/issues/tool-output-guardrails/tasks.md delete mode 100644 docs/issues/windows-exec-output-encoding/plan.md delete mode 100644 docs/issues/windows-exec-output-encoding/spec.md delete mode 100644 docs/issues/windows-exec-output-encoding/tasks.md delete mode 100644 docs/specs/sqlite-mainline-normalization/plan.md delete mode 100644 docs/specs/sqlite-mainline-normalization/spec.md delete mode 100644 docs/specs/sqlite-mainline-normalization/tasks.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 01f2b92cf..51cb0c1de 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,6 +1,7 @@ # DeepChat 当前架构概览 -本文档描述 `2026-04-20` 完成 main kernel refactor phase5 收口后的主架构。 +本文档描述 `2026-05-28` 的主架构。当前目标不是再做一次全量 main-kernel rewrite, +而是维持 typed renderer-main boundary,并把新增能力接到既有 route/runtime owner 上。 ## 主链路 @@ -10,111 +11,97 @@ flowchart LR Client --> Bridge["window.deepchat / preload bridge"] Bridge --> Contracts["shared/contracts routes + events"] Contracts --> Routes["src/main/routes dispatcher"] - Routes --> Settings["settings handler"] - Routes --> Sessions["SessionService"] - Routes --> Chat["ChatService"] - Routes --> Providers["ProviderService"] - Settings --> Config["configPresenter"] - Sessions --> Ports["presenter-backed hot path ports"] - Chat --> Ports - Providers --> Ports + Routes --> Services["route services / handlers"] + Services --> Ports["presenter-backed ports"] Ports --> AgentSession["agentSessionPresenter"] - Ports --> Llm["llmProviderPresenter"] - Ports --> Window["windowPresenter"] AgentSession --> Runtime["agentRuntimePresenter"] Runtime --> Tool["toolPresenter"] Runtime --> SQLite["sqlitePresenter"] Tool --> Mcp["mcpPresenter"] Tool --> AgentTools["toolPresenter/agentTools"] - Llm --> Acp["llmProviderPresenter/acp"] + Ports --> Provider["llmProviderPresenter"] + Provider --> Acp["llmProviderPresenter/acp"] ``` 主结论: -- migrated renderer boundary 先经过 `renderer/api`、`window.deepchat` 和 shared contracts,再进入 main。 -- `src/main/routes/index.ts` 是 settings / sessions / chat / providers 这些 migrated path 的 typed dispatcher 和装配入口。 -- 现有 presenter 仍然存在,但主要通过 `hotPathPorts` / `runtimePorts` 这样的窄 port 暴露给 route services。 -- `SessionPresenter` 继续保留为 legacy 数据平面和导出边界,不再是 migrated chat hot path 的 owner。 +- renderer 业务代码优先经过 `renderer/api/*Client`、`window.deepchat` 和 shared contracts。 +- `src/main/routes/index.ts` 是 typed route dispatcher,并装配 settings、sessions、chat、 + providers、models、config、MCP、plugins、skills、sync、browser、database security、 + scheduled tasks 等 route。 +- presenter 仍是 runtime owner,但 route services 只通过窄 port 或明确 client 依赖使用它们。 +- `SessionPresenter` 仍保留为 legacy 数据访问、导出和兼容边界,不再是当前聊天主链路 owner。 ## 模块职责 | 模块 | 位置 | 职责 | | --- | --- | --- | -| `renderer/api` | `src/renderer/api/` | 为 settings / sessions / chat / providers 提供 typed renderer client | -| shared contracts | `src/shared/contracts/` | 维护 route registry、schema、typed event catalog | -| preload bridge | `src/preload/createBridge.ts` / `src/preload/index.ts` | 统一暴露 `window.deepchat.invoke/on` | -| main route runtime | `src/main/routes/` | typed route dispatch、settings handler、session/chat/provider services | -| presenter-backed ports | `src/main/routes/hotPathPorts.ts` / `src/main/presenter/runtimePorts.ts` | 把现有 presenter 收敛成 route service 可依赖的窄接口 | +| `renderer/api` | `src/renderer/api/` | typed renderer clients,吸收 bridge/channel 细节 | +| shared contracts | `src/shared/contracts/` | route registry、schema、typed event catalog | +| preload bridge | `src/preload/createBridge.ts` / `src/preload/index.ts` | 暴露 `window.deepchat.invoke/on` | +| main routes | `src/main/routes/` | typed route dispatch、services、handlers | +| hot path ports | `src/main/routes/hotPathPorts.ts` / `src/main/presenter/runtimePorts.ts` | route runtime 到 presenter 的最小接口 | | `AgentSessionPresenter` | `src/main/presenter/agentSessionPresenter/` | session registry、window binding、legacy import、runtime delegation | -| `AgentRuntimePresenter` | `src/main/presenter/agentRuntimePresenter/` | 聊天 runtime、stream loop、tool interaction、message persistence | -| `ToolPresenter` | `src/main/presenter/toolPresenter/` | 工具定义聚合、调用路由、权限预检查 | -| `LLMProviderPresenter` | `src/main/presenter/llmProviderPresenter/` | provider 实例、stream state、model 管理、ACP provider helper | -| `SessionPresenter` | `src/main/presenter/sessionPresenter/` | legacy 会话数据访问、导出、历史兼容边界 | +| `AgentRuntimePresenter` | `src/main/presenter/agentRuntimePresenter/` | 聊天 loop、stream、tool interaction、message/session persistence | +| `ToolPresenter` | `src/main/presenter/toolPresenter/` | MCP tools 与本地 agent tools 聚合、权限预检查、调用路由 | +| `LLMProviderPresenter` | `src/main/presenter/llmProviderPresenter/` | provider 实例、model/runtime 管理、ACP helper、AI SDK runtime | +| `StartupWorkloadCoordinator` | `src/main/presenter/startupWorkloadCoordinator/` | startup/settings/floating 等目标的分阶段后台任务调度 | +| `RemoteControlPresenter` | `src/main/presenter/remoteControlPresenter/` | Telegram、Feishu/Lark、QQBot、Discord、WeChat iLink 远程控制 | +| `ScheduledTasksService` | `src/main/presenter/scheduledTasks/` | 一次性、每日、每周任务调度和 prompt/notify action dispatch | +| `DatabaseSecurityPresenter` | `src/main/presenter/databaseSecurityPresenter/` | SQLCipher 启用、改密、关闭、safeStorage/manual unlock | +| Spotlight search | `src/renderer/src/stores/ui/spotlight.ts` | 全局搜索、会话/消息跳转、设置导航和非破坏性 action | ## 当前分层 -### 1. Renderer-main boundary +### 1. Renderer-Main Boundary -- `src/shared/contracts/routes*.ts` 与 `events*.ts` 是 migrated path 的唯一契约真源。 -- `src/preload/createBridge.ts` 负责把 route invoke 和 typed event subscribe 统一暴露到 `window.deepchat`。 -- `src/renderer/api/*Client.ts` 吸收 bridge 细节,组件和 store 不直接拼新的 raw channel 字符串。 +- `src/shared/contracts/routes*.ts` 与 `events*.ts` 是 migrated path 的契约真源。 +- `src/preload/createBridge.ts` 统一 route invoke 和 typed event subscribe。 +- `src/renderer/api/*Client.ts` 是组件和 store 的默认入口。 +- `src/renderer/api/legacy/**` 是唯一 legacy quarantine。当前保留 `presenters.ts`、 + `presenterTransport.ts`、`runtime.ts` 三个兼容文件;新业务模块不应直接导入 legacy transport。 -### 2. Main route runtime +### 2. Main Route Runtime - `src/main/routes/index.ts` 根据 route registry 分发请求。 -- settings 走 `settings handler + adapter`。 -- sessions、chat、providers 分别走 `SessionService`、`ChatService`、`ProviderService`。 -- `Scheduler` 统一承接 migrated chat path 上的 timeout、retry、cancel 语义。 +- `SessionService`、`ChatService`、`ProviderService` 负责 migrated chat/session/provider hot path。 +- `ProviderImportService` 负责外部 provider 配置扫描与应用。 +- models routes 提供 model catalog、runtime list、config import/export、audio transcription。 +- database security 与 scheduled tasks 已经是 typed route,renderer 通过专用 client 调用。 -### 3. Presenter-backed runtime +### 3. Agent Runtime -- `createPresenterHotPathPorts()` 把 `agentSessionPresenter`、`configPresenter`、`llmProviderPresenter` 收敛成 route service 依赖的最小 port。 -- `AgentSessionPresenter` 仍持有 session registry、window 绑定和 legacy import helper。 -- `AgentRuntimePresenter` 继续拥有消息主循环、工具暂停恢复、消息持久化和增量 stream echo。 -- `ToolPresenter`、`LLMProviderPresenter` 和 `mcpPresenter` 继续服务未迁移能力与 runtime 内部协作。 +- `AgentSessionPresenter` 创建/恢复/激活 session,并把执行交给 `AgentRuntimePresenter`。 +- `AgentRuntimePresenter` 拥有 stream loop、tool loop、pending input、manual/auto compaction、 + message trace 和结构化消息持久化。 +- `DeepChatMessageStore` 采用头表 + 结构化子表模型,并在读路径缺行时回退旧 JSON。 +- 历史搜索使用 `deepchat_search_documents` 与 FTS5,FTS 不可用时回退 `LIKE`。 +- Agent progress 使用 `agent-core/update_plan`、`chat.plan.updated` 和 renderer 浮层展示任务计划。 -### 4. Compatibility boundary +### 4. Provider And Media Runtime -仍然保留但已降级为兼容职责的边界: +- `ModelType` 当前包含 chat、embedding、rerank、imageGeneration、videoGeneration、tts。 +- OpenAI-compatible image/video generation 和 TTS 通过 model config、provider route meta、 + AI SDK runtime 与消息渲染协作。 +- 本地录音转写走 `ModelClient.transcribeAudio()` / `models.transcribeAudio`,由 provider runtime 完成。 +- provider deeplink 与 provider config import 都会在写入前做 preview、校验、冲突处理和脱敏展示。 + +### 5. Compatibility Boundary + +仍保留但只承担兼容职责: - `src/main/presenter/agentSessionPresenter/legacyImportService.ts` - 旧 `conversations/messages` 数据域,作为 import-only 与导出数据源 - `src/main/presenter/sessionPresenter/`,作为 main 内部 compatibility/data facade -- `src/main/eventbus.ts`,继续服务未迁移路径,但 migrated UI 通知优先走 typed events - -## Phase 5 结论 - -- 本轮已经完成“边界稳定化 + 热路径减耦 + 可测试性提升”的目标。 -- 当前不继续发起一次性全量 `main kernel` rewrite。 -- 后续只在出现新的明确 hot path 收益时,再继续做 slice-driven typed-boundary migration。 - -## Post-P5 执行规则 - -`phase5` 收口不等于 renderer-main 已经完成单轨化。 +- `src/main/eventbus.ts`,继续服务未迁移路径;migrated UI 通知优先走 typed events -当前后续工作的默认规则是: +## 防回归规则 -- renderer 新功能优先走 `renderer/api/*Client` + `window.deepchat` + shared contracts -- `useLegacyPresenter()`、`window.electron`、`window.api` 只视为兼容路径,不再作为业务层默认入口 -- `src/renderer/api/legacy/presenters.ts` 已退役,剩余 quarantine-only 入口固定为 - `src/renderer/api/legacy/presenters.ts` -- renderer 业务模块不应再混用 typed client 与 legacy transport -- renderer legacy quarantine 目录固定为 `src/renderer/api/legacy/**`,不再允许创建第二个 quarantine 路径 - -单轨化的目标、阶段和 merge gate 见: - -- [architecture/renderer-main-single-track/spec.md](./architecture/renderer-main-single-track/spec.md) -- [architecture/renderer-main-single-track/plan.md](./architecture/renderer-main-single-track/plan.md) -- [architecture/renderer-main-single-track/tasks.md](./architecture/renderer-main-single-track/tasks.md) - -## 历史对照与防回归 - -- 历史架构文档见 [archives/legacy-agentpresenter-architecture.md](./archives/legacy-agentpresenter-architecture.md) -- 历史流程文档见 [archives/legacy-agentpresenter-flows.md](./archives/legacy-agentpresenter-flows.md) -- main kernel 边界回归由 `scripts/architecture-guard.mjs` 和 `docs/architecture/baselines/main-kernel-*.{md,json}` 追踪 -- `scripts/architecture-guard.mjs` 负责固定 `src/renderer/api/legacy/**`、禁止业务层新增 direct legacy transport,并把 typed boundary 外的 legacy access 视为违规 -- legacy agent cleanup 回归由 `scripts/agent-cleanup-guard.mjs` 追踪 -- renderer-main 单轨化后续治理由 `docs/architecture/renderer-main-single-track/` 追踪 +- 新 renderer-main 能力默认走 `renderer/api/*Client` + `window.deepchat` + shared contracts。 +- legacy transport 只能留在 `src/renderer/api/legacy/**`,不新增第二个 quarantine 目录。 +- `scripts/architecture-guard.mjs` 固定 quarantine 文件数、检测 direct legacy transport、 + 并读取 `docs/architecture/baselines/main-kernel-bridge-register.json`。 +- `scripts/agent-cleanup-guard.mjs` 用于防止已退休 agent runtime 入口回流。 ## 推荐阅读顺序 @@ -124,4 +111,3 @@ flowchart LR 4. [architecture/agent-system.md](./architecture/agent-system.md) 5. [architecture/tool-system.md](./architecture/tool-system.md) 6. [architecture/session-management.md](./architecture/session-management.md) - diff --git a/docs/FLOWS.md b/docs/FLOWS.md index eb5c295cd..28961bf35 100644 --- a/docs/FLOWS.md +++ b/docs/FLOWS.md @@ -1,31 +1,34 @@ # DeepChat 当前核心流程 -本文档只描述 retirement 后仍然有效的主流程。旧 `AgentPresenter` 流程已移到 -[archives/legacy-agentpresenter-flows.md](./archives/legacy-agentpresenter-flows.md)。 +本文档只描述当前代码仍在使用的流程。旧 `AgentPresenter` / `startStreamCompletion` +等历史流程不再作为仓库内长期文档保留,需要追溯时用 `git log` / `git show` 查看历史提交。 ## 1. 创建会话并发送消息 ```mermaid sequenceDiagram participant R as Renderer + participant C as SessionClient/ChatClient + participant Route as src/main/routes participant N as AgentSessionPresenter - participant A as AgentRegistry participant D as AgentRuntimePresenter participant S as NewSessionManager - R->>N: createSession(input, webContentsId) - N->>A: resolve agent implementation - N->>S: create session record - N->>D: initSession(sessionId, config) - N->>S: bindWindow(webContentsId, sessionId) - N-->>R: SessionWithState - N->>D: queuePendingInput()/processMessage() + R->>C: create/send/restore + C->>Route: window.deepchat.invoke(route) + Route->>N: createSession/restore/listMessagesPage + N->>S: create/bind/read session + N->>D: initSession/processMessage + D-->>R: chat.stream.* typed events ``` 关键文件: +- `src/renderer/api/SessionClient.ts` +- `src/renderer/api/ChatClient.ts` +- `src/main/routes/sessions/sessionService.ts` +- `src/main/routes/chat/chatService.ts` - `src/main/presenter/agentSessionPresenter/index.ts` -- `src/main/presenter/agentSessionPresenter/sessionManager.ts` - `src/main/presenter/agentRuntimePresenter/index.ts` ## 2. DeepChat 消息处理主循环 @@ -43,53 +46,79 @@ flowchart TD Resume -->|no| Continue["append tool results"] Wait --> Continue Continue --> Context - Finalize --> Persist["messageStore / sessionStore"] + Finalize --> Persist["messageStore / sessionStore / trace"] ``` -关键文件: +关键语义: -- `src/main/presenter/agentRuntimePresenter/process.ts` -- `src/main/presenter/agentRuntimePresenter/dispatch.ts` -- `src/main/presenter/agentRuntimePresenter/contextBuilder.ts` -- `src/main/presenter/agentRuntimePresenter/messageStore.ts` +- `generationSettings` 在 session 创建、草稿和 active session 中统一传递,覆盖 system prompt、 + temperature、topP、max tokens、reasoning effort、verbosity 等运行时设置。 +- `sessions.compact` 触发手动上下文压缩;自动压缩设置保存在 agent/session 配置中。 +- message trace 独立落库,renderer 通过 `sessions.listMessageTraces` 查询。 +- 失败消息会保留恢复上下文,tool output guard 会限制过大的工具输出进入后续上下文。 +- `agent-core/update_plan` 工具只更新 plan state 和 `chat.plan.updated` event,不把内部 tool call + 暴露成普通消息块。 -## 3. 工具调用与权限 +## 3. 工具调用、权限和 Subagents ```mermaid sequenceDiagram participant D as AgentRuntimePresenter participant T as ToolPresenter participant M as MCP Presenter - participant G as AgentToolManager + participant A as AgentToolManager participant P as Permission Services participant R as Renderer D->>T: getAllToolDefinitions() D->>T: preCheckToolPermission()/callTool() - alt agent tool - T->>G: callTool(name, args, conversationId) - G->>P: check/consume approvals - G-->>T: tool result - else mcp tool + alt MCP tool T->>M: callTool(request) - M-->>T: tool result + M-->>T: result + else local agent tool + T->>A: callTool(name, args, conversationId) + A->>P: check/consume approvals + A-->>T: result end alt requires interaction - D-->>R: emit paused interaction + D-->>R: paused interaction event R->>D: respondToolInteraction() end ``` -关键文件: +当前本地 agent tools 包括文件系统、命令执行、chat settings、subagent orchestration 等能力。 +Subagent 会话以 `sessionKind='subagent'` 存储,父会话通过 tape merge/discard 处理子会话结果。 + +## 4. 会话恢复、分页和搜索 + +```mermaid +sequenceDiagram + participant R as Renderer messageStore + participant S as SessionClient + participant Route as SessionService + participant N as AgentSessionPresenter + participant DB as DeepChatMessageStore + + R->>S: restore(sessionId, limit=100) + S->>Route: sessions.restore + Route->>N: restoreSession + N->>DB: listPageBySession + DB-->>R: latest page + nextCursor + R->>S: listMessagesPage(cursor) + S->>Route: sessions.listMessagesPage +``` -- `src/main/presenter/toolPresenter/index.ts` -- `src/main/presenter/toolPresenter/agentTools/agentToolManager.ts` -- `src/main/presenter/toolPresenter/agentTools/agentFileSystemHandler.ts` -- `src/main/presenter/mcpPresenter/toolManager.ts` +结构化持久化当前模型: -## 4. ACP draft session / runtime preparation +- `deepchat_messages` 存消息头和稳定 JSON fallback。 +- `deepchat_user_messages`、`deepchat_user_message_files`、`deepchat_user_message_links` + 存 user message 热字段。 +- `deepchat_assistant_blocks` 存 assistant block 增量。 +- `deepchat_search_documents` / `_fts` 存历史搜索索引。 + +## 5. ACP Session / Runtime Preparation ```mermaid sequenceDiagram @@ -100,39 +129,109 @@ sequenceDiagram participant A as ACP helpers R->>N: ensureAcpDraftSession(agentId, projectDir) - N->>D: initSession(... providerId='acp') + N->>D: initSession(providerId='acp') N->>L: prepareAcpSession(sessionId, agentId, projectDir) - L->>A: process/session persistence + config helpers + L->>A: process/session persistence + config options L-->>N: ACP session ready N-->>R: SessionWithState ``` -关键文件: +ACP 配置选项走 `sessions.getAcpSessionConfigOptions` / +`sessions.setAcpSessionConfigOption`;远程控制创建 ACP session 时会使用 channel +`defaultWorkdir` 或全局默认项目路径,并拒绝没有 workdir 的 ACP 默认 agent。 -- `src/main/presenter/agentSessionPresenter/index.ts` -- `src/main/presenter/llmProviderPresenter/index.ts` -- `src/main/presenter/llmProviderPresenter/acp/` +## 6. Spotlight Search + +```mermaid +sequenceDiagram + participant UI as Spotlight overlay + participant Store as spotlight store + participant Session as AgentSessionPresenter + participant Settings as settings navigation registry + + UI->>Store: open/query/select + Store->>Session: searchHistory(query) + Session-->>Store: sessions/messages hits + Store->>Settings: merge settings/actions/agents + Store-->>UI: mixed results +``` + +Spotlight 默认由 `CommandOrControl+P` 打开,混排 recent sessions、agents、settings、actions +和历史消息。消息命中会写入 pending jump,`ChatPage` 在目标消息加载完成后滚动并高亮。 -## 5. Legacy 数据导入 +## 7. Provider Import And Deeplinks ```mermaid sequenceDiagram - participant Hook as lifecycle import hook - participant N as AgentSessionPresenter - participant I as LegacyChatImportService - participant DB as SQLite / legacy tables - - Hook->>N: startLegacyImport() - N->>I: importLegacyChats() - I->>DB: read legacy conversations/messages - I->>DB: write new_sessions / deepchat_messages - I->>DB: backfill normalized message/session hot-path tables - I-->>N: import status + participant OS as deepchat:// URL + participant D as DeeplinkPresenter + participant W as Settings window + participant P as ProviderImportService + participant C as ConfigPresenter + + OS->>D: deepchat://provider/install?v=1&data=... + D->>W: provider install preview event + W->>P: validate/apply preview + P->>C: update builtin provider or create custom provider ``` -这个流程仍然保留,但只负责历史数据迁移,不再恢复旧 runtime。 +当前支持: -关键文件: +- `deepchat://start` +- `deepchat://mcp/install` +- `deepchat://provider/install` +- provider config import scan/apply,包括 Codex、Claude Code、Cherry Studio、CC Switch 等来源 +- model config import/export,以及 built-in/custom provider 的 credential-only import + +## 8. Scheduled Tasks + +```mermaid +sequenceDiagram + participant UI as Settings Scheduled Tasks + participant Client as ScheduledTasksClient + participant Service as ScheduledTasksService + participant Notify as NotificationPresenter + participant Session as Session creator + + UI->>Client: list/upsert/toggle/delete/fireNow + Client->>Service: scheduledTasks.* route + Service->>Service: compute next fire time + alt notify action + Service->>Notify: showNotification + else prompt action + Service->>Session: create session, optional autoSend + end +``` + +Triggers 支持 once、daily、weekly;actions 支持 notification 和 prompt。Prompt action 可指定 +agent、provider、model、system prompt,并通过 route runtime 创建会话。 + +## 9. Remote Control + +```mermaid +flowchart LR + Telegram["Telegram"] --> Remote["RemoteControlPresenter"] + Feishu["Feishu/Lark"] --> Remote + QQ["QQBot"] --> Remote + Discord["Discord"] --> Remote + WeChat["WeChat iLink"] --> Remote + Remote --> Auth["channel auth / binding store"] + Remote --> Runner["remote conversation runner"] + Runner --> Agent["AgentSessionPresenter"] +``` + +统一远程控制支持绑定、默认 agent、默认 workdir、`/sessions`、`/model`、状态输出、媒体/Markdown +渲染和工具交互提示。各 channel 的协议差异留在 `remoteControlPresenter//` +和 `remoteControlPresenter/services/*CommandRouter.ts`。 + +## 10. Local Data Security + +SQLite 数据库加密由 `DatabaseSecurityPresenter` 管理: + +- `databaseSecurity.getStatus` +- `databaseSecurity.enable` +- `databaseSecurity.changePassword` +- `databaseSecurity.disable` -- `src/main/presenter/agentSessionPresenter/legacyImportService.ts` -- `src/main/presenter/lifecyclePresenter/hooks/after-start/legacyImportHook.ts` +启用后使用 SQLCipher 迁移 `agent.db`,密码优先通过 Electron `safeStorage` 包装保存; +safeStorage 不可用或解包失败时进入 manual unlock。 diff --git a/docs/README.md b/docs/README.md index f83e0b63b..d16216fff 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,104 +1,54 @@ # DeepChat 文档索引 -本文档反映 `2026-04-20` 完成 main kernel refactor phase5 自动化收口后的代码结构。 +本文档反映 `2026-05-28` 的当前代码结构。历史 SDD 已清理为“活跃目标才保留”的模型: +已经落地的实现只在当前项目文档中保留维护信息,不再保留一次性 `spec/plan/tasks` +过程文档。 -当前仓库的后续治理重点不是再发起一次新的 `main kernel` 全量重写,而是把 renderer-main 边界继续收成 -single-track。换句话说,typed client / typed event 现在已经是默认方向,`useLegacyPresenter()`、 -`window.electron`、`window.api` 只应被视为兼容路径,而不是新功能入口。 -唯一允许的 quarantine 目录固定为 `src/renderer/api/legacy/**`,原先位于 -`src/renderer/api/legacy/presenters.ts` 的 shim 已在 `P5` 退役,剩余 legacy transport -只允许从 quarantine adapter 引用。 - -当前 migrated 聊天热路径已经收敛为: +当前 renderer-main 默认路径是 typed client / typed event: ```text Renderer - -> renderer/api (SettingsClient / SessionClient / ChatClient / ProviderClient) + -> renderer/api clients -> window.deepchat -> shared/contracts/routes + shared/contracts/events - -> main route runtime (settings handler / SessionService / ChatService / ProviderService) - -> presenter-backed hot path ports - -> agentSessionPresenter / configPresenter / llmProviderPresenter / windowPresenter - -> agentRuntimePresenter / toolPresenter / mcpPresenter / sqlitePresenter + -> src/main/routes dispatcher + -> route services / presenter-backed ports + -> agentSessionPresenter / agentRuntimePresenter / toolPresenter / llmProviderPresenter ``` -`SessionPresenter` 和旧 `conversations/messages` 数据域仍然保留,但只承担兼容、导出和历史数据访问职责,不再是 migrated chat/session hot path 的主入口。`phase5` 的结论也已经固定:当前不继续发起一次性全量 `main kernel` rewrite,后续只在明确 hot path 需要时继续做增量 typed-boundary migration。 +`useLegacyPresenter()`、`window.electron`、`window.api` 只允许作为兼容路径留在 +`src/renderer/api/legacy/**` quarantine 中。业务模块的新能力应从 `renderer/api/*Client` +和 shared contracts 进入。 ## 当前必读 | 文档 | 用途 | | --- | --- | -| [ARCHITECTURE.md](./ARCHITECTURE.md) | 当前主架构总览 | -| [FLOWS.md](./FLOWS.md) | 当前消息、工具、ACP、导入流程 | +| [ARCHITECTURE.md](./ARCHITECTURE.md) | 当前主架构、能力 owner、typed boundary 规则 | +| [FLOWS.md](./FLOWS.md) | 当前消息、工具、ACP、导入、定时任务、远程控制流程 | | [architecture/agent-system.md](./architecture/agent-system.md) | `agentSessionPresenter` / `agentRuntimePresenter` 细节 | | [architecture/tool-system.md](./architecture/tool-system.md) | `ToolPresenter`、agent tools、ACP helper 分层 | -| [architecture/session-management.md](./architecture/session-management.md) | 新会话管理与 legacy 数据平面边界 | +| [architecture/session-management.md](./architecture/session-management.md) | 新会话管理、分页恢复、legacy 数据平面边界 | +| [architecture/event-system.md](./architecture/event-system.md) | EventBus 与 typed events 的当前分工 | | [guides/code-navigation.md](./guides/code-navigation.md) | 当前代码导航入口 | | [guides/getting-started.md](./guides/getting-started.md) | 新开发者快速上手 | | [guides/plugin-packaging.md](./guides/plugin-packaging.md) | `.dcplugin` 打包、内置分发和 release 规则 | -| [spec-driven-dev.md](./spec-driven-dev.md) | SDD 目录规则与变更前置流程 | -| [architecture/baselines/dependency-report.md](./architecture/baselines/dependency-report.md) | 当前依赖与耦合基线 | -| [architecture/baselines/main-kernel-boundary-baseline.md](./architecture/baselines/main-kernel-boundary-baseline.md) | main kernel refactor 当前阶段的边界指标与 hot path 快照 | -| [architecture/baselines/main-kernel-bridge-register.md](./architecture/baselines/main-kernel-bridge-register.md) | main kernel refactor 的临时 bridge 登记表 | -| [architecture/baselines/main-kernel-migration-scoreboard.md](./architecture/baselines/main-kernel-migration-scoreboard.md) | main kernel refactor 的轻量 migration scoreboard | -| [architecture/renderer-main-single-track/spec.md](./architecture/renderer-main-single-track/spec.md) | `phase5` 之后 renderer-main 单轨化目标与验收标准 | -| [architecture/renderer-main-single-track/plan.md](./architecture/renderer-main-single-track/plan.md) | 单轨化阶段计划、family 优先级与 merge gate | -| [architecture/renderer-main-single-track/tasks.md](./architecture/renderer-main-single-track/tasks.md) | 单轨化执行清单 | -| [architecture/baselines/test-failure-groups.md](./architecture/baselines/test-failure-groups.md) | 当前测试失败分组基线 | - -## 本次清理落库 - -| 位置 | 内容 | -| --- | --- | -| [docs/archives/specs-migration-2026-05-02.md](./archives/specs-migration-2026-05-02.md) | 旧 SDD 目录拆分到 features / issues / architecture / archives 的迁移记录 | -| [docs/archives/legacy-agentpresenter-retirement/spec.md](./archives/legacy-agentpresenter-retirement/spec.md) | 本次 retirement 的目标、范围、兼容边界 | -| [docs/archives/legacy-agentpresenter-retirement/plan.md](./archives/legacy-agentpresenter-retirement/plan.md) | 迁移/归档/验证计划 | -| [docs/archives/legacy-agentpresenter-retirement/tasks.md](./archives/legacy-agentpresenter-retirement/tasks.md) | 已执行清单 | -| [docs/archives/legacy-llm-provider-runtime-retirement/spec.md](./archives/legacy-llm-provider-runtime-retirement/spec.md) | legacy provider runtime retirement 规格 | -| [docs/archives/legacy-llm-provider-runtime-retirement/plan.md](./archives/legacy-llm-provider-runtime-retirement/plan.md) | provider runtime 收口与依赖清理计划 | -| [docs/archives/legacy-llm-provider-runtime-retirement/tasks.md](./archives/legacy-llm-provider-runtime-retirement/tasks.md) | provider runtime 退役执行清单 | -| [docs/archives/provider-layer-simplification/spec.md](./archives/provider-layer-simplification/spec.md) | provider layer 第二轮内部收口规格 | -| [docs/archives/provider-layer-simplification/plan.md](./archives/provider-layer-simplification/plan.md) | registry + generic provider 合并计划 | -| [docs/archives/provider-layer-simplification/tasks.md](./archives/provider-layer-simplification/tasks.md) | provider layer 第二轮执行清单 | -| [docs/archives/ai-sdk-runtime/spec.md](./archives/ai-sdk-runtime/spec.md) | AI SDK runtime 规格,现已更新为 retired 状态 | -| [docs/architecture/architecture-simplification/spec.md](./architecture/architecture-simplification/spec.md) | 整体减负治理规格 | -| [docs/architecture/architecture-simplification/plan.md](./architecture/architecture-simplification/plan.md) | 分层/基线/guard 计划 | -| [docs/architecture/architecture-simplification/tasks.md](./architecture/architecture-simplification/tasks.md) | 首期实施清单 | -| [docs/archives/agent-cleanup/spec.md](./archives/agent-cleanup/spec.md) | cleanup 主规格,已更新到 retirement 完成态 | - -## 主内核边界稳定化计划记录 +| [spec-driven-dev.md](./spec-driven-dev.md) | SDD 目录规则、保留期限与清理规则 | -以下文档记录的是本轮已经完成的边界稳定化计划、验收证据和后续治理结论。 +## 仍有运行时用途的基线 -重点不再是一次性交付完整 `Clean Main Kernel`,而是优先解决 renderer-main 边界、hot path -减耦、lifecycle owner 和可测试性问题。当前这些文档既描述实施路径,也记录 `phase5` 收口后的最终状态: - -| 位置 | 内容 | +| 文档 | 用途 | | --- | --- | -| [docs/architecture/main-kernel-refactor/spec.md](./architecture/main-kernel-refactor/spec.md) | 收敛后方案的目标、范围、非目标与成功标准 | -| [docs/architecture/main-kernel-refactor/plan.md](./architecture/main-kernel-refactor/plan.md) | 以边界稳定和热路径减耦为主的阶段计划 | -| [docs/architecture/main-kernel-refactor/tasks.md](./architecture/main-kernel-refactor/tasks.md) | 项目级任务清单与阶段状态 | -| [docs/architecture/main-kernel-refactor/acceptance.md](./architecture/main-kernel-refactor/acceptance.md) | 阶段验收口径与本轮最终收口标准 | -| [docs/architecture/main-kernel-refactor/test-plan.md](./architecture/main-kernel-refactor/test-plan.md) | 围绕 migrated path 的测试分层与 smoke matrix | -| [docs/architecture/main-kernel-refactor/migration-governance.md](./architecture/main-kernel-refactor/migration-governance.md) | 防止双轨失控的实施纪律、bridge 规则与 scoreboard | -| [docs/architecture/main-kernel-refactor/build-vs-buy.md](./architecture/main-kernel-refactor/build-vs-buy.md) | 本轮哪些能力引库、哪些能力保持本地实现 | -| [docs/architecture/main-kernel-refactor/ports-and-scheduler.md](./architecture/main-kernel-refactor/ports-and-scheduler.md) | 最小必要 port 集合与 `Scheduler` 的定位 | -| [docs/architecture/main-kernel-refactor/route-schema-catalog.md](./architecture/main-kernel-refactor/route-schema-catalog.md) | migrated path 的 route registry、schema 和 typed event 目录 | -| [docs/architecture/main-kernel-refactor/eventbus-migration.md](./architecture/main-kernel-refactor/eventbus-migration.md) | 本轮对 typed UI event 和 legacy EventBus 的收敛策略 | - -## Renderer-Main 单轨化计划记录 - -以下文档描述的是 `phase5` 收口之后的新执行规则:不再接受 renderer 业务层双轨并存,而是继续把 -`renderer/api + window.deepchat + shared contracts` 固化成唯一默认路径。 -`src/renderer/api/legacy/**` 是唯一允许暂存 legacy transport 的 quarantine 路径。 +| [architecture/baselines/dependency-report.md](./architecture/baselines/dependency-report.md) | 当前依赖与耦合基线 | +| [architecture/baselines/main-kernel-boundary-baseline.md](./architecture/baselines/main-kernel-boundary-baseline.md) | renderer-main 边界指标与 hot path 快照 | +| [architecture/baselines/main-kernel-bridge-register.md](./architecture/baselines/main-kernel-bridge-register.md) | legacy bridge 登记表 | +| [architecture/baselines/main-kernel-migration-scoreboard.md](./architecture/baselines/main-kernel-migration-scoreboard.md) | typed-boundary migration scoreboard | +| [architecture/baselines/test-failure-groups.md](./architecture/baselines/test-failure-groups.md) | 测试失败分组基线 | -| 位置 | 内容 | -| --- | --- | -| [docs/architecture/renderer-main-single-track/spec.md](./architecture/renderer-main-single-track/spec.md) | 为什么当前分支还不能在双轨状态下直接合并,以及 single-track 的验收标准 | -| [docs/architecture/renderer-main-single-track/plan.md](./architecture/renderer-main-single-track/plan.md) | 单轨化阶段划分、quarantine 模型、family 迁移顺序与最终 merge gate | -| [docs/architecture/renderer-main-single-track/tasks.md](./architecture/renderer-main-single-track/tasks.md) | 具体执行清单 | +这些基线由 `scripts/generate-architecture-baseline.mjs` 生成,`scripts/architecture-guard.mjs` +会读取其中的 JSON 文件。它们不是历史 SDD,不应随 completed feature 文档一起删除。 -## 活跃架构地图 +## 当前代码地图 ```text docs/ @@ -107,55 +57,37 @@ docs/ ├── FLOWS.md ├── architecture/ │ ├── agent-system.md -│ ├── baselines/ -│ ├── main-kernel-refactor/ -│ ├── renderer-main-single-track/ +│ ├── event-system.md │ ├── session-management.md │ ├── tool-system.md -│ ├── event-system.md -│ └── mcp-integration.md +│ └── baselines/ ├── features/ -│ └── / -│ ├── spec.md -│ ├── plan.md -│ └── tasks.md +│ └── / ├── issues/ -│ └── / -│ ├── spec.md -│ ├── plan.md -│ └── tasks.md +│ └── / ├── guides/ │ ├── getting-started.md │ ├── code-navigation.md -│ ├── plugin-packaging.md -│ └── debugging.md -└── archives/ - ├── specs-migration-2026-05-02.md - ├── legacy-agentpresenter-architecture.md - ├── legacy-agentpresenter-flows.md - ├── legacy-llm-provider-runtime.md - ├── thread-presenter-migration-plan.md - └── workspace-agent-refactoring-summary.md +│ ├── debugging.md +│ └── plugin-packaging.md +└── spec-driven-dev.md ``` -## 历史文档 +## SDD 保留规则 -以下文档只用于追溯 legacy runtime,不再描述当前实现: - -| 文档 | 说明 | -| --- | --- | -| [archives/legacy-agentpresenter-architecture.md](./archives/legacy-agentpresenter-architecture.md) | 旧 `AgentPresenter` 架构快照 | -| [archives/legacy-agentpresenter-flows.md](./archives/legacy-agentpresenter-flows.md) | 旧 `startStreamCompletion` / permission / loop 流程 | -| [archives/legacy-llm-provider-runtime.md](./archives/legacy-llm-provider-runtime.md) | 旧 provider runtime 的历史归档与提交锚点 | -| [archives/thread-presenter-migration-plan.md](./archives/thread-presenter-migration-plan.md) | 历史迁移设计 | -| [archives/workspace-agent-refactoring-summary.md](./archives/workspace-agent-refactoring-summary.md) | 历史工作区改造总结 | +- `docs/features/**`、`docs/issues/**`、`docs/architecture/**` 下的 goal folder 只表示活跃目标。 +- 已实现能力要把当前维护事实并入 `README.md`、`ARCHITECTURE.md`、`FLOWS.md` 或对应 guide, + 然后删除旧 SDD 文件夹。 +- bug 修复类 issue SDD 超过两周即清理;按当前日期 `2026-05-28`,本次清理 cutoff 为 + `2026-05-14` 之前。 +- 过期、未开工、只描述旧实现或旧分支的 SDD 直接删除。 ## 阅读建议 1. 先读 [ARCHITECTURE.md](./ARCHITECTURE.md) 建立当前主链路心智模型。 -2. 再读 [FLOWS.md](./FLOWS.md) 看发送消息、工具调用和 ACP 会话的时序。 +2. 再读 [FLOWS.md](./FLOWS.md) 看发送消息、工具调用、导入和远程控制时序。 3. 深入实现时,按模块进入: - - 聊天执行链路: [architecture/agent-system.md](./architecture/agent-system.md) - - 工具与权限: [architecture/tool-system.md](./architecture/tool-system.md) - - 会话与兼容边界: [architecture/session-management.md](./architecture/session-management.md) -4. 如果需要对照旧实现,再去看 `archives/` 历史文档,不要依赖已经移除的历史源码快照。 + - 聊天执行链路:[architecture/agent-system.md](./architecture/agent-system.md) + - 工具与权限:[architecture/tool-system.md](./architecture/tool-system.md) + - 会话与兼容边界:[architecture/session-management.md](./architecture/session-management.md) +4. 如果需要理解已退休设计,优先用 `git log` / `git show` 追历史提交,不再依赖仓库内长期归档文档。 diff --git a/docs/architecture/acp-client-runtime/plan.md b/docs/architecture/acp-client-runtime/plan.md deleted file mode 100644 index bb6a7d9d7..000000000 --- a/docs/architecture/acp-client-runtime/plan.md +++ /dev/null @@ -1,32 +0,0 @@ -# ACP Client Runtime Plan - -## Runtime Boundary - -Add `src/main/presenter/acpClientPresenter/` as the internal ACP runtime boundary. The first implementation keeps the current provider-facing API stable by making `AcpProvider` a compatibility adapter over the runtime. - -The runtime owns: - -- connection/process lifecycle and debug backlog; -- session lifecycle and MCP forwarding; -- prompt concurrency; -- client-side handlers for permissions, filesystem, terminal, and auth; -- event mapping from ACP updates to DeepChat stream/workspace events. - -## Implementation Sequence - -1. Create SDD docs and conformance checklist. -2. Add the ACP runtime facade and prompt controller. -3. Move provider construction to the runtime facade while keeping public behavior compatible. -4. Remove warmup `session/new` probing and let real session responses drive config/mode/model state. -5. Register persisted load-session handlers before `session/load` so replayed updates are not dropped. -6. Route debug actions through the initialized connection state and real MCP selections. -7. Harden fs and terminal workdir guards. -8. Refresh settings on ACP agent change events and release running processes before repair. -9. Add focused regression tests, then run formatting, i18n, and lint checks. - -## Compatibility - -- Existing ACP registry/manual agent configuration remains valid. -- Existing ACP conversation/session records remain valid. -- Provider id `acp` and model ids remain unchanged. -- No renderer IPC contract changes are required for this slice. diff --git a/docs/architecture/acp-client-runtime/spec.md b/docs/architecture/acp-client-runtime/spec.md deleted file mode 100644 index c03ef8a51..000000000 --- a/docs/architecture/acp-client-runtime/spec.md +++ /dev/null @@ -1,47 +0,0 @@ -# ACP Client Runtime - -## Problem - -DeepChat's ACP integration currently behaves like a provider helper instead of a full ACP client runtime. This causes protocol drift and product bugs: - -- warmup can create a temporary `session/new` with empty MCP servers, so agent configuration is probed with a different context than real turns; -- debug `initialize` can be invoked on a connection that was already initialized; -- settings updates for install/repair/enable are not always reflected automatically; -- ACP sessions, file-system access, terminal access, permissions, and session updates do not have a single runtime boundary. - -## Goals - -- Introduce an internal ACP client runtime boundary that owns connection, session, handler, mapping, workspace, registry, and debug concerns. -- Align DeepChat with ACP's client responsibilities: spawn stdio agents, initialize once, create/load sessions with the current cwd and MCP servers, forward prompts, handle client-side fs/terminal/permission calls, and map `session/update`. -- Preserve DeepChat's existing public model selector, Workspace, MCP, Skills, Remote Control, IPC, and renderer route surfaces. -- Match Zed's configuration boundary: DeepChat forwards cwd/env/MCP/model/mode/config options; ACP agents read their own native configuration directly. - -## Non-Goals - -- Do not add a Claude-specific or Codex-specific local configuration resolver. -- Do not reset or migrate already persisted ACP summary/session state beyond compatible schema additions. -- Do not redesign the model selector UI in this change. - -## Protocol Matrix - -| ACP area | DeepChat behavior | -| --- | --- | -| `initialize` | Sent once per process connection after stdio spawn. Capabilities and auth methods are cached on the connection handle. | -| `authenticate` | Routed through the ACP auth/terminal handler when required by the agent. | -| `session/new` | Uses the resolved conversation workdir and DeepChat MCP selections filtered by agent transport capabilities. | -| `session/load` | Used only when the agent declares load support and DeepChat has a persisted ACP session id. | -| `session/prompt` | One active prompt per ACP session; response `stopReason` is persisted as turn completion state. | -| `session/update` | Mapped to DeepChat message/content/tool/plan/diff/terminal/permission events. | -| `session/request_permission` | Routed through DeepChat permission UI/policy and remote-control-safe resolution. | -| `fs/read_text_file` and `fs/write_text_file` | Guarded by realpath validation against registered workdirs. | -| Terminal lifecycle | Bound to the session workdir and routed through DeepChat terminal management. | -| Debug | Shows lifecycle, request, response, notification, permission, stderr, and error entries without re-initializing an existing connection. | - -## Acceptance Criteria - -- Warmup never creates a throwaway session solely to fetch config state. -- Debug initialize starts or reports the initialized connection and does not send a second ACP initialize request. -- Real sessions are the source of models/modes/config options. -- Settings refresh after enable/install/repair/uninstall without requiring a manual reopen. -- ACP fs and terminal operations are scoped to the registered workdir. -- Non-ACP providers keep their existing context-budget behavior. diff --git a/docs/architecture/acp-client-runtime/tasks.md b/docs/architecture/acp-client-runtime/tasks.md deleted file mode 100644 index 008a4a71b..000000000 --- a/docs/architecture/acp-client-runtime/tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# ACP Client Runtime Tasks - -- [x] Add SDD spec, plan, and tasks documents. -- [x] Add internal ACP runtime facade and prompt controller. -- [x] Stop warmup from creating temporary sessions with empty MCP servers. -- [x] Use real MCP selections for debug `session/new` and `session/load`. -- [x] Make debug `initialize` report initialized connection state instead of sending a second initialize request. -- [x] Register load-session handlers before `session/load` replay can emit updates. -- [x] Enforce realpath workspace guards for ACP fs reads/writes. -- [x] Bind terminal cwd to the registered ACP session workdir. -- [x] Refresh ACP settings on agent-change events. -- [x] Release running ACP processes before repair. -- [x] Add targeted ACP regression coverage. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. diff --git a/docs/architecture/agent-provider-simplification/plan.md b/docs/architecture/agent-provider-simplification/plan.md deleted file mode 100644 index 86358985c..000000000 --- a/docs/architecture/agent-provider-simplification/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Agent Provider Simplification (ACP-only) Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented architecture change goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/architecture/agent-provider-simplification/spec.md b/docs/architecture/agent-provider-simplification/spec.md deleted file mode 100644 index 2c6aa6579..000000000 --- a/docs/architecture/agent-provider-simplification/spec.md +++ /dev/null @@ -1,43 +0,0 @@ -# Agent Provider Simplification (ACP-only) - -## Background - -DeepChat currently distinguishes between: - -- **LLM providers**: network-backed providers that implement `BaseLLMProvider` (OpenAI/Anthropic/etc). -- **Agent providers**: providers that manage local agent sessions/processes (currently only `acp` via `AcpProvider`). - -The codebase implements this distinction via a dedicated base class (`BaseAgentProvider`) and a runtime/type-detection API (`isAgentProvider`), which is then consumed from the renderer via IPC. - -## Problem - -- `BaseAgentProvider` is only used by `AcpProvider`, so the abstraction adds indirection without real reuse. -- Provider type detection is over-engineered (`isAgentConstructor` + prototype checks) and duplicates existing ACP-specific branching. -- The renderer calls `llmproviderPresenter.isAgentProvider(providerId)` over IPC, but the only “agent provider” is `providerId === 'acp'`. This creates unnecessary main↔renderer coupling and call complexity. - -## Goals - -- Treat **ACP as the only agent provider** and identify it **only by `providerId === 'acp'`**. -- Remove the generic “agent provider type detection” path and the renderer IPC dependency for this decision. -- Keep user-visible behavior unchanged: - - ACP agents still appear as selectable models when ACP is enabled. - - Non-ACP providers keep the standard model/custom-model refresh behavior. - - Shutdown and provider disable still clean up ACP resources. - -## Non-goals - -- Supporting multiple agent providers beyond ACP. -- Redesigning ACP model derivation (agents-as-models) or session/workspace semantics. -- Changing persisted provider IDs or stored settings schemas. - -## Acceptance Criteria - -- Renderer no longer calls `llmproviderPresenter.isAgentProvider(...)`; ACP decision is local (`providerId === 'acp'`). -- Main process no longer needs `isAgentConstructor` / prototype-based provider classification. -- No remaining runtime dependency on `BaseAgentProvider` for correctness (ACP cleanup remains correct). -- `pnpm run typecheck`, `pnpm test`, `pnpm run lint` pass. - -## Open Questions - -- None. - diff --git a/docs/architecture/agent-provider-simplification/tasks.md b/docs/architecture/agent-provider-simplification/tasks.md deleted file mode 100644 index 283fd4cf7..000000000 --- a/docs/architecture/agent-provider-simplification/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Agent Provider Simplification (ACP-only) Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/architecture/agent-refactor/plan.md b/docs/architecture/agent-refactor/plan.md deleted file mode 100644 index bd7654a3e..000000000 --- a/docs/architecture/agent-refactor/plan.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Refactor Plan - -## P0 Baseline - -1. Default DeepChat subagent config: - - Enable subagents by default. - - Add self-based `explorer`, `implementer`, and `reviewer` slots. -2. Prompt composition: - - Build a single system prompt string. - - Keep section order stable. - - Add permission and verification policy sections. -3. Tool execution: - - Parallelize all-read-only canonical Agent tool rounds. - - Preserve result writeback order. - - Keep writes, edits, commands, and process operations serialized. - -## P1 Stable Tool Profile - -1. Add a main-layer session tool-profile cache. -2. Select `code` profile when a project directory is present; otherwise use `general`. -3. Refresh the profile when project directory, model, disabled tools, or active skills change. -4. Keep schemas stable across ordinary user messages. - -## P2 Follow-Up - -1. Expand profile types for `research` and `analysis`. -2. Add UI affordances for weak agent capability where only legacy tool fallback is available. -3. Implement the documented tool-result envelope after review. -4. Extend subagent lifecycle commands for background management. diff --git a/docs/architecture/agent-refactor/spec.md b/docs/architecture/agent-refactor/spec.md deleted file mode 100644 index 86eee5e77..000000000 --- a/docs/architecture/agent-refactor/spec.md +++ /dev/null @@ -1,39 +0,0 @@ -# Agent Refactor Spec - -## Goal - -Raise the default DeepChat agent baseline without rewriting the presenter architecture. P0 must be useful on its own for code changes, requirement analysis, repository inspection, and structured research. - -## Requirements - -1. DeepChat agents default to subagents enabled unless explicitly disabled. -2. Default subagent slots include self-based `explorer`, `implementer`, and `reviewer` roles. -3. Provider requests receive one composed system message, not multiple system messages. -4. The composed system prompt order is stable: - - user/base prompt - - runtime capabilities - - environment and `AGENTS.md` - - skills metadata and pinned skills - - tooling rules - - permission rules - - verification policy -5. Read-only canonical Agent tools may execute in parallel when a tool-call round contains only read-only calls. -6. Mutating and runtime canonical tools remain serialized or permission-gated. -7. Tool schemas are loaded through a stable session profile and cache. Ordinary user messages must not by themselves change the tool profile. -8. Code-agent verification policy requires final answers to account for verification after code changes. - -## Non-Goals - -1. Do not implement a per-turn tool router. -2. Do not rewrite IPC, renderer message flow, or provider runtime architecture. -3. Do not change the tool-result envelope implementation before the protocol document is reviewed. -4. Do not remove legacy function-call fallback in this phase. - -## Acceptance - -1. New or resolved DeepChat agent configs expose enabled subagents and the three default self slots. -2. Agent runtime requests include at most one `system` role message. -3. Prompt section order follows the requirement above and includes `AGENTS.md` via the environment section. -4. A batch of only `read`/`ls`/`find`/`grep` Agent tool calls starts concurrently and writes tool results back in model call order. -5. Any batch containing `write`/`edit`/`exec`/`process` stays serialized. -6. Repeated ordinary messages in the same session reuse the stable tool profile unless project directory, disabled tools, model, or active skills change. diff --git a/docs/architecture/agent-refactor/tasks.md b/docs/architecture/agent-refactor/tasks.md deleted file mode 100644 index 516652deb..000000000 --- a/docs/architecture/agent-refactor/tasks.md +++ /dev/null @@ -1,14 +0,0 @@ -# Agent Refactor Tasks - -- [x] Add default self-based `explorer`, `implementer`, and `reviewer` subagent slots. -- [x] Enable subagents by default for DeepChat configs unless explicitly disabled. -- [x] Update DeepChat agent settings defaults to use the new slot set. -- [x] Compose one system prompt with stable section ordering. -- [x] Add permission and verification policy prompt sections. -- [x] Add a session tool-profile cache in the main runtime. -- [x] Parallelize all-read-only canonical Agent tool batches. -- [x] Keep mixed or mutating tool batches serialized. -- [x] Document the tool-result envelope before implementation. -- [x] Add UI labeling for weak legacy-only tool-calling models. -- [x] Implement the tool-result envelope after protocol review. -- [x] Extend background subagent lifecycle operations. diff --git a/docs/architecture/agent-refactor/tool-result-envelope.md b/docs/architecture/agent-refactor/tool-result-envelope.md deleted file mode 100644 index 2dd229de8..000000000 --- a/docs/architecture/agent-refactor/tool-result-envelope.md +++ /dev/null @@ -1,112 +0,0 @@ -# Tool Result Envelope - -This document defines the target tool-result protocol. The current implementation adds the envelope to Agent tool `rawData.toolResult` at the ToolPresenter boundary while keeping legacy `rawData.content` unchanged for provider-facing model context. - -## Shape - -```ts -type AgentToolResult = { - ok: boolean - summary: string - data?: unknown - meta?: { - truncated?: boolean - nextOffset?: number - offloadPath?: string - tokenEstimate?: number - resultCount?: number - } - error?: { - code: string - message: string - recoverable?: boolean - } -} -``` - -## Success - -Successful tools set `ok: true`, provide a short model-readable `summary`, and put structured payloads in `data`. - -Examples: - -```json -{ - "ok": true, - "summary": "Found 12 matches in 3 files.", - "data": { - "matches": [] - }, - "meta": { - "resultCount": 12, - "truncated": false - } -} -``` - -## Errors - -Failed tools set `ok: false`, provide a short summary, and include a stable `error.code`. - -```json -{ - "ok": false, - "summary": "Invalid arguments for edit.", - "error": { - "code": "INVALID_ARGUMENT", - "message": "oldText is required.", - "recoverable": true - } -} -``` - -## Truncation - -Tools that return partial data set `meta.truncated: true`. If the same tool can continue from a position, it also sets `meta.nextOffset`. - -Renderer display: - -- Show the `summary` in compact cards. -- Show truncation and next-page affordances from `meta`. -- Keep `data` available for rich views, but do not require the renderer to parse provider-facing prose. - -Model-readable behavior: - -- Always include the `summary`. -- Include enough `data` for the next likely model step. -- Prefer pagination over returning huge blobs. - -## Offload - -Large outputs may be written to an offload file and represented by `meta.offloadPath`. - -Rules: - -- `summary` must explain what was offloaded. -- `data` may contain a preview. -- The offload path must be readable by the canonical `read` tool when the session has access. - -## Batch Results - -When multiple tool calls are executed in one model round: - -- Preserve the model's original tool-call order in returned tool messages. -- Each tool message contains one envelope. -- Parallel execution must not reorder renderer updates or provider-facing tool result messages. - -## Renderer Contract - -The renderer should treat the envelope as the stable display protocol: - -- `ok` controls success/error styling. -- `summary` powers the collapsed text. -- `data` feeds rich tool-specific rendering. -- `meta` handles pagination, truncation, and offload UI. -- `error` provides retry hints and diagnostics. - -## Migration Notes - -1. Canonical Agent tools now receive an envelope through `rawData.toolResult` unless a tool already provides a specialized `toolResult`. -2. Legacy raw tool output remains available through `rawData.content`. -3. Renderer cards can migrate to the envelope without changing provider-facing tool messages. -4. MCP passthrough tools and external servers can be extended after renderer support lands. diff --git a/docs/architecture/agent-system.md b/docs/architecture/agent-system.md index 2a86dc472..fafe8bb3b 100644 --- a/docs/architecture/agent-system.md +++ b/docs/architecture/agent-system.md @@ -1,7 +1,7 @@ # Agent 系统架构详解 -本文档描述 retirement 后仍然有效的 agent system。旧 `AgentPresenter` 细节已归档到 -[../archives/legacy-agentpresenter-architecture.md](../archives/legacy-agentpresenter-architecture.md)。 +本文档描述 retirement 后仍然有效的 agent system。旧 `AgentPresenter` 细节不再作为仓库内 +长期文档保留;需要对照时用 `git log` / `git show` 查看历史提交。 ## 当前运行时所有权 @@ -81,6 +81,8 @@ agentRuntimePresenter/ | Tool dispatch | `src/main/presenter/agentRuntimePresenter/dispatch.ts` | 调用 `ToolPresenter`、暂停交互、生成 tool 结果 | | Context build | `src/main/presenter/agentRuntimePresenter/contextBuilder.ts` | 历史裁剪、resume context、token budget | | Persistence | `src/main/presenter/agentRuntimePresenter/messageStore.ts` | 消息持久化、分页读取、结构化内容重组与故障恢复 | +| Compaction | `src/main/presenter/agentRuntimePresenter/compactionService.ts` | 手动/自动上下文压缩与压缩状态消息 | +| Pending input | `src/main/presenter/agentRuntimePresenter/pendingInputStore.ts` | queued input、steer、重排与恢复 | ## 持久化热路径 @@ -96,6 +98,18 @@ agentRuntimePresenter/ - streaming 期间只增量更新 `deepchat_assistant_blocks` - 最终进入 `sent/error` 时才写回稳定的 `deepchat_messages.content` - 读路径优先从结构化表重组 `ChatMessageRecord.content`,缺行时再回退旧 JSON +- `sessions.restore` 默认只恢复最近一页消息,历史继续通过 `sessions.listMessagesPage` 翻页 +- `deepchat_search_documents` / `_fts` 提供历史搜索索引,FTS 不可用时回退 `LIKE` + +## 运行时能力 + +- Session generation settings 随 session 创建和更新持久化,覆盖 system prompt、temperature、 + topP、max tokens、reasoning effort、verbosity 等设置。 +- Message trace 独立落库,供消息工具栏查看运行时 trace。 +- Subagent 会话以 `sessionKind='subagent'` 进入同一套 session/message store,父会话通过 + tape merge/discard 吸收或丢弃子会话结果。 +- 本地录音转写、TTS、image generation、video generation 都复用 provider/model capability 判定, + 不再绕开 provider runtime。 ## 兼容边界 @@ -132,4 +146,4 @@ agentRuntimePresenter/ - `permissionHandler` - `startStreamCompletion` -需要对照旧实现时,只看归档文档,不再把历史源码快照当作活跃导航入口。 +需要对照旧实现时,从历史提交中查看旧源码快照,不再把已经删除的历史设计当作活跃导航入口。 diff --git a/docs/architecture/architecture-simplification/plan.md b/docs/architecture/architecture-simplification/plan.md deleted file mode 100644 index 7008d31e1..000000000 --- a/docs/architecture/architecture-simplification/plan.md +++ /dev/null @@ -1,37 +0,0 @@ -# Architecture Simplification Plan - -## Workstreams - -### 1. Quality Baseline - -- 添加 `scripts/generate-architecture-baseline.mjs`,把依赖环、零入边候选、归档引用报告落到 `docs/architecture/baselines/`。 -- 维护一份失败测试分组文档,区分真实行为回归、测试陈旧、环境问题。 -- 把这批基线纳入文档入口,后续每轮治理都基于同一套报告更新。 - -### 2. Main Composition / Lifecycle - -- 新增 `ConfigQueryPort`、`SessionRuntimePort`、`WindowRoutingPort` 等窄接口。 -- 让 `Presenter` 只在 composition root 组装 port;`agentSessionPresenter`、`agentRuntimePresenter` 只依赖 port。 -- 首批迁移目标: - - 会话 UI 刷新 - - 权限批准 / 清理 - - model catalog 查询 - -### 3. Renderer Transport / State - -- 增加 window context helper,避免各处各自读 `window.api`。 -- 增加 IPC subscription helper,把 `App.vue`、`stores/ui/session.ts`、`stores/ui/message.ts` 的监听注册收口。 -- 把流式状态拆到独立 `stream` store,消息缓存仍保留在 `message` store。 - -### 4. Archive / Docs - -- 首先把开发者导航从历史源码快照切到稳定历史文档。 -- 在文档脱钩和 guard 到位后删除历史源码实体。 - -## Validation - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- `pnpm run typecheck` -- 针对本次改动的主链路测试与 composable/store 测试 diff --git a/docs/architecture/architecture-simplification/spec.md b/docs/architecture/architecture-simplification/spec.md deleted file mode 100644 index e18998936..000000000 --- a/docs/architecture/architecture-simplification/spec.md +++ /dev/null @@ -1,40 +0,0 @@ -# Architecture Simplification - -## Summary - -本规格定义首期“整体减负治理”的目标:不引入新功能,优先降低 `main` / `renderer` 的心智负担,清理无用代码,减少隐式调用链,并把生命周期、边界与兼容层显式化。 - -首期只做四类工作: - -- 基线收敛 -- 分层与依赖收口 -- 历史归档脱钩 -- 测试可信度恢复 - -## Goals - -- 为 `main` 和 `renderer` 建立可持续更新的结构基线,而不是一次性口头结论。 -- 限制活跃路径继续依赖全局 `presenter`、分散 IPC 监听和历史归档代码。 -- 让 `agentSessionPresenter`、`agentRuntimePresenter`、`App.vue`、`stores/ui/*` 的职责边界更清楚。 -- 为后续删除历史源码归档目录和死文件清理建立前置条件。 - -## Non-Goals - -- 不做新功能。 -- 不在首期重命名对外 IPC channel。 -- 不在首期移除 `SessionPresenter`、legacy import、旧表结构这些兼容边界。 -- 不要求一次性解决全部失败测试,只要求把失败分类、责任边界和修复入口理清。 - -## Acceptance Criteria - -- 仓库存在一组可更新的基线报告,至少覆盖依赖环、零入边候选、归档引用、失败测试分组。 -- 活跃主链路新增 anti-regression guard,阻止历史源码重新进入运行时依赖,阻止首批主链路继续回跳全局 `presenter`。 -- `agentSessionPresenter` / `agentRuntimePresenter` 首批关键路径改为依赖窄接口 port,而不是直接 import `presenter/index.ts`。 -- `renderer` 至少将 `App`、`session`、`message` 这条活跃链路的 IPC 监听收口到 helper / subscription hub,并把流式状态独立成单独状态面。 -- 文档中存在本次治理的 `spec.md`、`plan.md`、`tasks.md`,并且 `docs/README.md` 能导航到这些内容。 - -## Constraints - -- 行为兼容优先,优先保留用户可见行为和现有 IPC surface。 -- 只有经过人工分类的“真正死文件”可以删除。 -- 任何 archive 清理都要先完成文档脱钩。 diff --git a/docs/architecture/architecture-simplification/tasks.md b/docs/architecture/architecture-simplification/tasks.md deleted file mode 100644 index 9c1421fad..000000000 --- a/docs/architecture/architecture-simplification/tasks.md +++ /dev/null @@ -1,31 +0,0 @@ -# Architecture Simplification Tasks - -## Baseline - -- [x] 建立 architecture simplification spec / plan / tasks -- [x] 新增依赖 / 死代码 / archive 引用基线生成脚本 -- [x] 新增失败测试分组基线文档 - -## Main - -- [x] 引入窄接口 ports -- [x] 将 `agentSessionPresenter` 的会话 UI 刷新与权限清理改成依赖 port -- [x] 将 `agentRuntimePresenter` 的权限批准与 env prompt model lookup 改成依赖 port -- [x] 让 `Presenter` 成为 port 的唯一组装入口 - -## Renderer - -- [x] 引入 window context helper -- [x] 引入 IPC subscription helper -- [x] 收口 `App.vue`、`session`、`message` 的活跃监听链路 -- [x] 拆出独立 stream state store -- [x] 修复 `session store` 对缺失 `sessionKind` 的兼容分类 -- [x] 补齐 `useSearchConfig` 与 search capability 组合逻辑 - -## Guardrails / Docs - -- [x] 新增 architecture guard -- [x] 将 lint 串上 architecture guard -- [x] 更新 `docs/README.md`、`guides/*`、架构文档的治理入口 -- [x] 清理剩余 specs 对历史源码快照的直接文件级引用 -- [x] 删除历史源码归档实体 diff --git a/docs/architecture/chat-store-zero-migration/plan.md b/docs/architecture/chat-store-zero-migration/plan.md deleted file mode 100644 index b845524c2..000000000 --- a/docs/architecture/chat-store-zero-migration/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Chat Store Zero Migration Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented architecture change goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/architecture/chat-store-zero-migration/spec.md b/docs/architecture/chat-store-zero-migration/spec.md deleted file mode 100644 index 2cfeeabfa..000000000 --- a/docs/architecture/chat-store-zero-migration/spec.md +++ /dev/null @@ -1,21 +0,0 @@ -# Chat Store Zero Migration - -## Goal - -Retire legacy renderer `chatStore` usage and switch runtime chat flow to `ui/*` stores only. - -## Decisions - -1. Renderer old chain components depending on `useChatStore` are removed. -2. New chat flow keeps `ui/session` + `ui/message` event listeners unchanged. -3. Backup import auto-detects DB file in zip: - - Prefer `database/agent.db` - - Fallback to `database/chat.db` -4. `chat.db` import behavior: - - `increment`: legacy migration import - - `overwrite`: clear new session/message domain tables, then migrate legacy data - -## Notes - -- Startup `legacyImportHook` remains enabled for local migration compatibility. -- Backup packaging format remains unchanged (`database/agent.db` as primary output). diff --git a/docs/architecture/chat-store-zero-migration/tasks.md b/docs/architecture/chat-store-zero-migration/tasks.md deleted file mode 100644 index b16d74043..000000000 --- a/docs/architecture/chat-store-zero-migration/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Chat Store Zero Migration Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/architecture/config-sqlite-storage/plan.md b/docs/architecture/config-sqlite-storage/plan.md deleted file mode 100644 index 76e8a19c7..000000000 --- a/docs/architecture/config-sqlite-storage/plan.md +++ /dev/null @@ -1,34 +0,0 @@ -# Config SQLite Storage Plan - -## Architecture - -- Add SQLite tables using direct business names: `providers`, `provider_models`, `model_status`, - `model_configs`, `mcp_servers`, `mcp_settings`, `agent_mcp_selections`, and `agent_settings`. -- Keep the existing `agents` table and `AgentRepository` flow. Only residual ACP settings move out - of the old `acp_agents` store. -- Add DB-backed store adapters that mimic the small ElectronStore surface used by existing helpers. - This lets `ProviderHelper`, `ProviderModelHelper`, `ModelStatusHelper`, `ModelConfigHelper`, - `McpConfHelper`, and `AcpConfHelper` keep most existing behavior. -- Attach SQLite-backed config after database and agent repository initialization. Until then, - ConfigPresenter uses JSON for startup-critical settings. - -## Migration - -- Read legacy provider/model/MCP/ACP data from current ElectronStore-backed helpers before switching - helpers to DB-backed stores. -- Import legacy data into the new tables inside an idempotent migration guarded by - `config_migrations`. -- Preserve legacy JSON files after successful import. - -## Compatibility - -- Keep existing route contracts and presenter method signatures unchanged. -- Return `LLM_PROVIDER[]`, `MODEL_META[]`, `MCPServerConfig`, and model config exports in their - current shapes. -- Keep old backup import support for legacy archives that still contain `mcp-settings.json`. - -## Backup - -- Continue backing up `agent.db`. -- Back up app settings as lightweight JSON with migrated provider/model keys filtered out. -- Continue backing up prompts and system prompts until their second-phase migration. diff --git a/docs/architecture/config-sqlite-storage/spec.md b/docs/architecture/config-sqlite-storage/spec.md deleted file mode 100644 index 52b8e68d8..000000000 --- a/docs/architecture/config-sqlite-storage/spec.md +++ /dev/null @@ -1,32 +0,0 @@ -# Config SQLite Storage - -## User Story - -DeepChat configuration has outgrown JSON storage. Provider, model, MCP, and agent-adjacent -configuration should move to SQLite so frequent updates and larger collections remain structured, -queryable, and backed up with the main application database. - -## Acceptance Criteria - -- Provider and model configuration reads/writes keep the existing presenter and route behavior. -- MCP server configuration keeps the existing presenter and route behavior. -- Existing `agents` table remains unchanged in name and remains the source for AgentRepository data. -- Lightweight startup settings remain in JSON so logging, proxy, language, theme, sync path, and - similar environment settings can be read before SQLite-backed config is attached. -- Legacy JSON/electron-store data is imported into SQLite exactly once and importing is idempotent. -- Sync backups include SQLite-backed configuration through `agent.db`; JSON backups exclude migrated - provider/model keys. - -## Non-Goals - -- Do not change renderer route contracts or legacy presenter method names. -- Do not rename existing SQLite tables. -- Do not migrate prompts, system prompts, knowledge configs, or Nowledge-mem settings in this - increment. -- Do not introduce secret encryption changes for API keys in this increment. - -## Constraints - -- The current startup sequence reads logging and proxy settings before the database is attached. -- The existing `ConfigPresenter` must continue to work as a compatibility facade. -- Old JSON files are retained after migration for rollback safety. diff --git a/docs/architecture/config-sqlite-storage/tasks.md b/docs/architecture/config-sqlite-storage/tasks.md deleted file mode 100644 index a9589d828..000000000 --- a/docs/architecture/config-sqlite-storage/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Config SQLite Storage Tasks - -- [x] Add SQLite schema and table helpers for provider/model/MCP/ACP residual config. -- [x] Add DB-backed store adapters for existing ConfigPresenter helpers. -- [x] Wire ConfigPresenter to attach SQLite-backed config after database initialization. -- [x] Add idempotent migration from legacy JSON/electron-store stores. -- [x] Update SyncPresenter backup filtering for migrated app-settings keys. -- [x] Add focused tests for repositories, migration, route compatibility, startup, and sync. -- [x] Run format, i18n, lint, and targeted tests. diff --git a/docs/architecture/event-system.md b/docs/architecture/event-system.md index b3a131279..23c937b92 100644 --- a/docs/architecture/event-system.md +++ b/docs/architecture/event-system.md @@ -6,7 +6,7 @@ - 当前 active renderer-main boundary 已经优先走 `renderer/api/*Client` + `window.deepchat` + typed contracts - 下文中涉及 `useLegacyPresenter()`、`window.api`、raw `window.electron` 的内容,应视为 legacy / compatibility 背景 -- `phase5` 之后的 single-track 规则见 `docs/architecture/renderer-main-single-track/` +- 当前 single-track 规则见 `docs/ARCHITECTURE.md` 的 renderer-main boundary 章节 ## 📋 核心组件 @@ -850,4 +850,3 @@ export const useChatStore = defineStore('chat', { - [Agent 系统详解](./agent-system.md) - [工具系统详解](./tool-system.md) - [核心流程](../FLOWS.md) - diff --git a/docs/architecture/main-kernel-refactor/acceptance.md b/docs/architecture/main-kernel-refactor/acceptance.md deleted file mode 100644 index be9c018a9..000000000 --- a/docs/architecture/main-kernel-refactor/acceptance.md +++ /dev/null @@ -1,216 +0,0 @@ -# Main Kernel Refactor Acceptance - -## Acceptance Model - -本项目采用双层验收: - -- 阶段验收:每个 phase 都必须独立通过 -- 最终验收:所有阶段完成后,再检查本轮“边界稳定化”目标是否达成 - -本轮验收重点不是“新目录是否漂亮”,而是: - -- migrated path 是否更稳定 -- owner 是否更清楚 -- 测试是否更容易写 - -## Phase Gate Rules - -每个阶段都必须同时满足以下条件: - -1. 该阶段定义的真实 slice 或主路径切换已经完成 -2. 该阶段引入的 bridge 已登记 `deleteByPhase` -3. 该阶段要求的自动化验证已通过 -4. 该阶段要求的 smoke 验证已通过 -5. legacy 指标相对上一阶段净下降,或至少没有反弹 - -## Governance Hard Requirements - -以下要求来自 [migration-governance.md](./migration-governance.md),属于硬门槛: - -- 同一条用户路径只能有一个 active owner -- bridge 只能单向 `old -> new` -- foundation 工作不能连续多轮脱离真实 slice -- 旧实现一旦进入迁移阶段即冻结 -- migrated path 完成后必须看到可证明的耦合净下降 - -## Phase Acceptance Criteria - -### Phase 0: Guardrails & Baseline - -- 能阻止新增 `useLegacyPresenter()`、新增 raw renderer IPC、新增 migrated path raw channel -- 能输出 renderer / preload / hot path 相关趋势基线 -- bridge register 和 scoreboard 模板可用 - -### Phase 1: Typed Boundary Foundation - -- 存在统一的 shared route registry -- 存在 typed event catalog,至少覆盖 settings / sessions / chat 首批事件 -- preload bridge 和 `renderer/api` client 已能驱动首批主路径 -- 新增功能不再依赖 `useLegacyPresenter()` 接入 - -### Phase 2: Settings Pilot Slice - -- settings 主读写链路走新 contract + client + handler -- settings 相关 renderer/store 不再依赖旧 presenter 作为主入口 -- settings 变更通知通过 typed event 可追踪 - -### Phase 3: Chat & Session Hot Path - -- session create / restore / activate 中至少主路径 owner 已明确 -- 发送消息、停止流主链路走显式 orchestration,而不是继续靠 presenter 互调 -- timeout / retry / cancel 通过 `Scheduler` 管理 -- migrated path 上的 cleanup 行为可测 - -### Phase 4: Provider / Tool Boundary - -- provider query / execution / session 配置边界具备明确 port 或 adapter -- permission / tool response 通过明确 contract 或 typed event 处理 -- migrated path 上 presenter 对 provider 的直接依赖已降到可解释范围 - -### Phase 5: Consolidation & Re-evaluation - -- 本轮新增 bridge 已删除 -- 文档、baseline、scoreboard、smoke 记录已同步 -- 已形成“是否继续做下一轮更彻底 kernel 重构”的结论 - -## Phase Status - -- [x] Phase 0 completed on `2026-04-19` -- Phase 0 evidence lives in `scripts/architecture-guard.mjs` and `scripts/generate-architecture-baseline.mjs` -- Phase 0 artifacts are `docs/architecture/baselines/main-kernel-boundary-baseline.md` -- Phase 0 artifacts are `docs/architecture/baselines/main-kernel-bridge-register.md` -- Phase 0 artifacts are `docs/architecture/baselines/main-kernel-migration-scoreboard.md` -- Phase 0 establishes the baseline checkpoint, so later phases compare against this snapshot instead of a prior phase delta -- [x] Phase 1 completed on `2026-04-19` -- Phase 1 evidence lives in `src/shared/contracts/`, `src/preload/createBridge.ts`, `src/main/routes/`, and `src/renderer/api/` -- Phase 1 automated verification covers `test/main/routes/contracts.test.ts` -- Phase 1 automated verification covers `test/main/routes/dispatcher.test.ts` -- Phase 1 automated verification covers `test/renderer/api/createBridge.test.ts` -- Phase 1 automated verification covers `test/renderer/api/clients.test.ts` -- Phase 1 artifacts are the refreshed `docs/architecture/baselines/main-kernel-*.{md,json}` reports at current phase `P1` -- Phase 2 implementation and automated verification completed on `2026-04-19` -- Phase 2 evidence lives in `src/main/routes/settings/`, `src/main/routes/index.ts`, `src/renderer/api/SettingsClient.ts`, and `src/renderer/src/stores/uiSettingsStore.ts` -- Phase 2 automated verification covers `test/main/routes/contracts.test.ts` -- Phase 2 automated verification covers `test/main/routes/dispatcher.test.ts` -- Phase 2 automated verification covers `test/main/routes/settingsHandler.test.ts` -- Phase 2 automated verification covers `test/renderer/api/clients.test.ts` -- Phase 2 automated verification covers `test/renderer/stores/uiSettingsStore.test.ts` -- Phase 2 artifacts are the refreshed `docs/architecture/baselines/main-kernel-*.{md,json}` reports at current phase `P2` -- Phase 2 manual smoke handoff is documented in `docs/architecture/main-kernel-refactor/test-plan.md` -- [x] Phase 3 completed on `2026-04-19` -- Phase 3 evidence lives in `src/main/routes/chat/`, `src/main/routes/sessions/`, `src/main/routes/hotPathPorts.ts`, `src/main/routes/scheduler.ts`, and the migrated renderer hot path under `src/renderer/src/stores/ui/` plus `src/renderer/src/pages/ChatPage.vue` -- Phase 3 migrated renderer boundary now uses `SessionClient` / `ChatClient` for `sessions.restore`, `sessions.activate`, `sessions.deactivate`, `sessions.getActive`, `chat.sendMessage`, and `chat.stopStream` -- Phase 3 automated verification covers `test/main/routes/contracts.test.ts` -- Phase 3 automated verification covers `test/main/routes/dispatcher.test.ts` -- Phase 3 automated verification covers `test/main/routes/sessionService.test.ts` -- Phase 3 automated verification covers `test/main/routes/chatService.test.ts` -- Phase 3 automated verification covers `test/main/routes/scheduler.test.ts` -- Phase 3 automated verification covers `test/renderer/api/clients.test.ts` -- Phase 3 automated verification covers `test/renderer/stores/pageRouter.test.ts` -- Phase 3 automated verification covers `test/renderer/stores/messageStore.test.ts` -- Phase 3 automated verification covers `test/renderer/stores/sessionStore.test.ts` -- Phase 3 follow-up verification on `2026-04-19` restored typed incremental stream delivery in `src/main/presenter/agentRuntimePresenter/echo.ts`, aligned rate-limit and deferred terminal-error branches with typed `chat.stream.*` events in `src/main/presenter/agentRuntimePresenter/index.ts`, and reintroduced renderer-side persisted refresh fallback in `src/renderer/src/stores/ui/messageIpc.ts` -- Phase 3 follow-up verification covers `test/main/presenter/agentRuntimePresenter/echo.test.ts` -- Phase 3 follow-up verification covers `test/renderer/components/ChatPage.test.ts` -- Phase 3 artifacts are the refreshed `docs/architecture/baselines/main-kernel-*.{md,json}` reports at current phase `P3` -- Phase 3 legacy metrics confirm net reduction versus the Phase 0 checkpoint: `renderer.usePresenter.count` `89 -> 87`, `renderer.windowApi.count` `34 -> 33`, `hotpath.presenterEdge.count` `11 -> 10`, and `bridge.active.count` remains `0` -- Phase 3 manual smoke handoff is documented in `docs/architecture/main-kernel-refactor/test-plan.md` -- [x] Phase 4 completed on `2026-04-20` -- Phase 4 evidence lives in `src/main/presenter/runtimePorts.ts`, `src/main/presenter/index.ts`, `src/main/presenter/agentRuntimePresenter/index.ts`, `src/main/presenter/agentSessionPresenter/index.ts`, `src/main/routes/providers/providerService.ts`, `src/main/routes/hotPathPorts.ts`, `src/main/routes/index.ts`, `src/shared/contracts/routes/providers.routes.ts`, `src/shared/contracts/routes/chat.routes.ts`, `src/renderer/api/ProviderClient.ts`, `src/renderer/api/ChatClient.ts`, `src/renderer/src/stores/providerStore.ts`, and `src/renderer/src/pages/ChatPage.vue` -- Phase 4 migrated renderer boundary now uses `ChatClient.respondToolInteraction` for permission/question responses and `ProviderClient.testConnection` for provider verification -- Phase 4 automated verification covers `test/main/routes/contracts.test.ts` -- Phase 4 automated verification covers `test/main/routes/dispatcher.test.ts` -- Phase 4 automated verification covers `test/main/routes/chatService.test.ts` -- Phase 4 automated verification covers `test/main/routes/providerService.test.ts` -- Phase 4 automated verification covers `test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts` -- Phase 4 automated verification covers `test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts` -- Phase 4 automated verification covers `test/main/presenter/agentSessionPresenter/integration.test.ts` -- Phase 4 automated verification covers `test/main/presenter/agentSessionPresenter/usageDashboard.test.ts` -- Phase 4 automated verification covers `test/renderer/api/clients.test.ts` -- Phase 4 automated verification covers `test/renderer/components/ChatPage.test.ts` -- Phase 4 automated verification covers `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck` -- Phase 4 automated verification covers `node scripts/architecture-guard.mjs` -- Phase 4 automated verification covers `pnpm run architecture:baseline` -- Phase 4 artifacts are the refreshed `docs/architecture/baselines/main-kernel-*.{md,json}` reports at current phase `P4` -- Phase 4 legacy metrics confirm no rebound versus the Phase 3 checkpoint while continuing the renderer boundary reduction: `renderer.usePresenter.count` `87 -> 86`, `renderer.windowElectron.count` `95 -> 95`, `renderer.windowApi.count` `33 -> 33`, `hotpath.presenterEdge.count` `10 -> 10`, and `bridge.active.count` remains `0` -- Phase 4 manual smoke handoff is documented in `docs/architecture/main-kernel-refactor/test-plan.md` -- [x] Phase 5 completed on `2026-04-20` -- Phase 5 audit confirmed the bridge register remains empty, so no main-kernel temporary bridge survived into `P5` -- Phase 5 automated verification covers the targeted migrated-path regression pack in `test/main/routes/*.test.ts`, `test/main/presenter/agentRuntimePresenter/*.test.ts`, `test/main/presenter/agentSessionPresenter/*.test.ts`, `test/renderer/api/*.test.ts`, `test/renderer/stores/*.test.ts`, and `test/renderer/components/ChatPage.test.ts` -- Phase 5 automated verification covers `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck` -- Phase 5 automated verification covers `pnpm run architecture:baseline` -- Phase 5 artifacts are the refreshed `docs/architecture/baselines/main-kernel-*.{md,json}` reports at current phase `P5` -- Phase 5 documentation refresh covers `docs/README.md`, `docs/ARCHITECTURE.md`, and `docs/guides/code-navigation.md` -- Phase 5 legacy metrics confirm the Phase 4 checkpoint held through final consolidation: `renderer.usePresenter.count` `86 -> 86`, `renderer.windowElectron.count` `95 -> 95`, `renderer.windowApi.count` `33 -> 33`, `hotpath.presenterEdge.count` `10 -> 10`, `migrated.rawChannel.count` `5 -> 5`, and `bridge.active.count` remains `0` -- Phase 5 final smoke handoff is documented in `docs/architecture/main-kernel-refactor/test-plan.md` -- Phase 5 conclusion: do not start a full `main kernel` rewrite now; keep using slice-driven typed boundary migrations only when a concrete hot path still justifies it - -## Final Acceptance Checklist - -结构性签收项已根据 `2026-04-20` 的自动化验证结果勾选;用户可见行为项保留到最终手工 smoke 后确认。 - -### Boundary - -- [x] migrated path 的 renderer 调用统一走 `renderer/api` + `window.deepchat` -- [x] migrated path 不再新增 `useLegacyPresenter()`、`window.electron`、`window.api` 依赖 -- [x] migrated path 的 route 和 typed event 都能在共享 registry / catalog 中追踪 -- [x] 组件和 store 不直接拼新的 raw channel 字符串 - -### Runtime Ownership - -- [x] settings、chat、session、provider 这些 migrated path 都有明确 owner -- [x] `AgentSessionPresenter -> AgentRuntimePresenter` 不再是 migrated chat path 的主 owner 链 -- [x] provider query / execution / permission 边界可解释,不依赖全局隐式协作 -- [x] session / stream / permission cleanup 有明确 owner - -### Lifecycle and Scheduling - -- [x] cancel、timeout、retry 在 migrated chat path 上走 `Scheduler` -- [x] window/session 相关 listener、subscription、abort controller 的清理可验证 -- [x] 现有 `LifecycleManager` 或等价 setup 模块中,migrated path 的装配关系是可读的 - -### Cleanup - -- [x] 本轮涉及的临时 bridge 已按计划删除 -- [x] 对应 slice 的旧 owner 已冻结,不再继续长新逻辑 -- [x] hot path 直连依赖相较基线净下降 -- [x] 本轮不要求 `src/main/presenter` 目录归零,但 migrated path 不再依赖它的旧协作方式 - -### Quality - -- [x] route / client / service / scheduler / provider boundary 具备对应测试 -- [x] `pnpm run format`、`pnpm run i18n`、`pnpm run lint`、`pnpm run typecheck` 通过 -- [x] baseline、tasks、test-plan、README 已同步更新 - -### User-Visible Behavior - -- [ ] 修改设置正常 -- [ ] 创建会话正常 -- [ ] 恢复会话正常 -- [ ] 发送消息正常 -- [ ] 流式回复正常 -- [ ] 停止流正常 -- [ ] provider 相关关键交互正常 -- [ ] 权限交互正常 - -## What Is Not Required For Sign-Off - -以下内容不属于本轮最终签收硬门槛: - -- `src/main/presenter` 整体删除 -- `ServiceLocator` / singleton 在全仓归零 -- 完整 clean architecture 目录搬迁 -- EventBus 全量重写 -- 明显内存下降 - -## Evidence Required Before Final Sign-Off - -- 最新基线报告 -- route registry 与 typed event catalog 摘要 -- bridge register -- migration scoreboard -- 自动化测试结果摘要 -- smoke 记录 -- 文档更新记录 -- 对“是否继续做更彻底 kernel 重构”的结论说明 - diff --git a/docs/architecture/main-kernel-refactor/build-vs-buy.md b/docs/architecture/main-kernel-refactor/build-vs-buy.md deleted file mode 100644 index 3cf9d9f71..000000000 --- a/docs/architecture/main-kernel-refactor/build-vs-buy.md +++ /dev/null @@ -1,136 +0,0 @@ -# Main Kernel Refactor Build vs Buy - -## Purpose - -本文件定义本轮“边界稳定化”工作的引库策略。 - -本轮原则不是尽量多造轮子,也不是尽量多引框架,而是: - -- 用成熟库解决通用问题 -- 把 DeepChat 的边界、生命周期语义和 Electron 安全面留在仓库内掌控 - -## Decision Rule - -采用以下判断标准: - -- 如果问题是通用基础设施问题,并且能明显减少重复劳动,优先考虑引库 -- 如果问题直接定义 renderer-main 能力边界、owner 关系或生命周期语义,优先自写 -- 新引入的第三方库必须能被本地接口或 facade 包裹,不能直接成为业务层心智模型 - -## Decisions - -| Area | Decision | Why | -| --- | --- | --- | -| schema/contracts | 继续使用 `zod` | 已有基础最好,统一性高 | -| preload bridge / route registry | 自写 | 属于产品能力边界和 Electron 安全边界 | -| renderer client | 自写 | 需要贴合现有 store / composable 用法 | -| 架构 guard/baseline | 先用现有脚本,必要时再引 `dependency-cruiser` | 当前已有脚本,先控制复杂度 | -| retry/backoff | `p-retry` 可批准 | 适合放在 `Scheduler.retry` 内部 | -| queue/backpressure | `p-queue` 仅在明确证据下评估 | 不是第一批刚需 | -| DI container | 本轮不引入 | 本轮重点是热路径 owner,而不是容器框架 | -| lifecycle helper / scope helper | 按需小自写 | 如果 cleanup 语义需要,再补最小 helper | -| EventBus 全量重写 | 本轮不作为前置要求 | 先解决 migrated path 的 typed event 与 owner | - -## Approved Third-Party Libraries - -默认可批准: - -- `zod` -- `p-retry` - -条件成立时再评估: - -- `dependency-cruiser` -- `p-queue` - -当前不作为第一批依赖: - -- `Awilix` -- `tsyringe` -- `Inversify` -- `Emittery` -- `neverthrow` - -## Self-Owned Components - -以下组件明确由仓库内自写并长期掌控: - -- shared route registry -- typed event catalog -- preload bridge builder -- `renderer/api/*Client` -- `Scheduler` interface -- provider / session / permission 的窄 port - -这些组件直接定义: - -- renderer-main 边界 -- 迁移期 owner 关系 -- 测试替身注入方式 - -## DI and Composition Policy - -本轮不单独引入 DI container。 - -默认做法: - -- 用显式 factory / setup function 装配依赖 -- 用局部 interface 或 port 缩窄依赖面 -- 只有当 session / window cleanup 真的需要一致 dispose 语义时,再补最小 helper - -一句话: - -- 先把 owner 讲清楚 -- 再决定是否真的需要 container - -## Event Policy - -本轮先做两件事: - -- 冻结 legacy `eventBus` 的错误扩张 -- 为 migrated path 建立 typed UI event - -本轮不要求: - -- 先把整个 EventBus 系统重写 -- 先把全部字符串事件搬迁完 - -如果未来确实需要 internal typed bus,再在下一轮评估。 - -## Scheduler Policy - -`Scheduler` 仍建议作为本地接口存在,原因是: - -- 业务层要表达 timeout / retry / sleep 的语义 -- 测试需要 fake scheduler -- cancel 行为需要统一 owner - -实施要求: - -- 业务层依赖 `Scheduler`,而不是直接依赖 `setTimeout` 或 `p-retry` -- `p-retry` 只允许出现在 scheduler adapter 内部 - -## Error Model Policy - -`neverthrow` 暂不进入本轮。 - -原因: - -- 本轮已经同时在切边界、owner 和测试结构 -- 现在再切错误模型,变量太多 - -建议: - -- 先把 migrated path 稳住 -- 等 hot path 稳定后,再评估是否值得试点 - -## Adoption Rules - -新增第三方库前必须回答: - -1. 它解决的是通用基础设施问题,还是 DeepChat 特有边界问题? -2. 它是否会改变 renderer-main 或 hot path owner 的心智模型? -3. 它能否被本地接口包住? -4. 它是否直接减少当前迁移成本? - -如果以上答案不够明确,本轮默认不引。 diff --git a/docs/architecture/main-kernel-refactor/eventbus-migration.md b/docs/architecture/main-kernel-refactor/eventbus-migration.md deleted file mode 100644 index 7bf026993..000000000 --- a/docs/architecture/main-kernel-refactor/eventbus-migration.md +++ /dev/null @@ -1,134 +0,0 @@ -# Main Kernel Refactor EventBus Migration - -## Purpose - -本文件定义本轮对事件系统的收敛策略。 - -关键点只有一句话: - -- 本轮不先重写整个 EventBus -- 本轮先为 migrated path 建立 typed UI event,并冻结 legacy event 的继续扩张 - -## Current State - -当前事件系统的核心问题: - -- `src/main/eventbus.ts` 同时承担 main 内部通知和 main -> renderer 广播 -- `eventBus` 直接依赖 `windowPresenter` -- `src/main/events.ts` 中字符串事件常量混合了多种语义 -- renderer 仍习惯直接监听 `window.electron.ipcRenderer.on(...)` - -这说明现在的问题是“职责混杂”,但并不意味着本轮必须把整套事件系统一次性改完。 - -## Target State For This Program - -本轮只要求达到以下状态: - -```text -shared/contracts/events # migrated path 的 typed UI event 定义 -preload bridge # 对 typed event 的统一订阅入口 -renderer/api clients # 吸收 bridge 细节 -main publisher/adapters # migrated path 通过明确 publisher 发事件 -legacy eventBus # 继续服务于未迁移路径,但冻结新增错误用法 -``` - -## Event Taxonomy For This Program - -### Typed UI Events - -本轮优先处理: - -- `settings.changed` -- `sessions.updated` -- `chat.stream.updated` -- `chat.stream.completed` -- `chat.stream.failed` - -这些事件直接服务于 migrated path 的 renderer 刷新。 - -### Legacy Internal Events - -现有 `eventBus` 仍可继续承接未迁移路径的内部通知,但: - -- 不再给 migrated path 新增依赖 -- 不再把新 UI 通知继续挂在 raw string event 上 - -## Old To New Mapping - -### Migrated Path Mapping - -| Old pattern | New pattern | -| --- | --- | -| `eventBus.sendToRenderer(...)` | typed UI event publisher | -| `window.electron.ipcRenderer.on(...)` | preload bridge subscription + renderer client/store adapter | -| renderer 直接监听 raw event 名 | renderer 通过 typed event client / helper 订阅 | - -### Legacy Path Policy - -对于未迁移路径: - -- 保持现状可接受 -- 但禁止继续扩张错误用法 - -## Migration Strategy - -### Phase 1 - -- 定义 typed UI event catalog -- 冻结 migrated path 上新增 raw renderer event 监听 -- 为 preload bridge 准备统一订阅面 - -### Phase 2 - -- settings slice 改为 typed `settings.changed` -- settings store 不再依赖 raw IPC 监听 - -### Phase 3 - -- chat/session migrated path 改为 typed `sessions.updated`、`chat.stream.*` -- 新的 stop / restore / send 主链路不再直接依赖 `eventBus.sendToRenderer` - -### Phase 4 - -- provider / permission 相关 migrated path 如需通知 renderer,优先走 typed event -- 若发现 legacy eventBus 成为 hot path 阻塞点,再评估是否值得拆 internal bus - -### Phase 5 - -- 回顾本轮迁移范围内是否还存在 legacy raw event 依赖 -- 决定下一轮是否真的需要全量 EventBus 重写 - -## Proposed Publisher Shape - -本轮只需要一个足够简单的发布能力: - -```ts -export interface WindowEventPort { - publish(eventName: string, payload: TPayload): Promise - publishToWindow?(windowId: number, eventName: string, payload: TPayload): Promise - publishToWebContents?( - webContentsId: number, - eventName: string, - payload: TPayload - ): Promise -} -``` - -重点不是接口有多复杂,而是: - -- migrated path 不再隐式依赖全局 `eventBus` -- renderer 收到的是 typed contract,而不是内部常量泄漏 - -## Completion Criteria - -本轮在事件系统上的完成标准是: - -- migrated path 的 UI 通知具备 typed event -- renderer 不再为 migrated path 新增 raw event listener -- 新主路径不再继续依赖 `eventBus.sendToRenderer` - -本轮不要求: - -- 删除 `src/main/eventbus.ts` -- 删除 `src/main/events.ts` -- 完成全量事件分类重写 diff --git a/docs/architecture/main-kernel-refactor/migration-governance.md b/docs/architecture/main-kernel-refactor/migration-governance.md deleted file mode 100644 index 89666e609..000000000 --- a/docs/architecture/main-kernel-refactor/migration-governance.md +++ /dev/null @@ -1,189 +0,0 @@ -# Main Kernel Refactor Migration Governance - -## Purpose - -本文件定义本轮“边界稳定化”实施纪律。 - -它解决的不是“方案是否足够理想”,而是: - -- 如何避免一边加新层,一边让旧耦合继续长大 -- 如何确保每一轮迁移都真的减少复杂度 - -## Core Risk - -最大风险不是技术写不出来,而是出现下面这种失控状态: - -- 新 contract、新 client、新 service 都建了 -- 旧 presenter 仍在承载同一条主路径 -- bridge 没写删除时点 -- 基线数字没有下降 - -一旦如此,项目就会同时承担两套心智模型,维护成本比现在更高。 - -## Operating Principles - -### 1. Single Active Owner - -同一条用户路径在任一时刻只能有一个主 owner。 - -例子: - -- settings 主读写只能由新 typed boundary 或旧 presenter 之一承载 -- send / stop / restore 这类 chat/session 主链路不能同时由新 orchestration 和旧 presenter 都算主入口 - -### 2. One-Way Bridge Only - -迁移期间允许 bridge,但 bridge 只能是: - -```text -old entry -> new implementation -``` - -禁止: - -```text -new client -> legacy direct IPC -new service -> old presenter -new typed route -> presenter reflection dispatcher -``` - -### 3. Boundary Before Expansion - -本轮新增功能如果涉及 renderer-main 能力,必须先进入 typed boundary。 - -不允许一边写“未来会迁移”,一边继续新增 `useLegacyPresenter()` 或 raw IPC。 - -### 4. One Real Slice Per Phase - -每个 phase 都必须切一个真实 slice。 - -不合格的 phase 例子: - -- 只建 route registry,不迁任何页面或 store -- 只建 service,不替换任何主路径 owner -- 只加 helper,不删任何旧桥接 - -### 5. Legacy Freeze For Migrated Slices - -某个 slice 一旦进入迁移阶段,旧实现立即冻结。 - -冻结含义: - -- 可以修阻塞性 bug -- 可以做最小限度转发 -- 不允许继续长新主逻辑 -- 不允许继续扩张 API surface - -### 6. Net Reduction Over Net Addition - -阶段是否完成,要看 legacy 指标是否净下降。 - -至少观察: - -- `renderer.usePresenter.count` -- `renderer.windowElectron.count` -- `renderer.windowApi.count` -- hot path direct dependency 数量 -- `bridge.active.count` - -如果这些数字没有下降,通常说明只是多加了一层新实现。 - -## Hard Rules - -### Red Lines - -以下行为在本轮实施期间视为红线: - -1. 在 renderer 新增 `useLegacyPresenter()` -2. 在 renderer 新增 `window.electron.ipcRenderer.*` -3. 在 migrated path 新增 raw channel 字符串 -4. 新 orchestration 反向依赖旧 presenter -5. 同一条用户路径长期保持双 owner - -### Phase Hard Stops - -出现以下任一情况时,该 phase 不得标记为完成: - -1. 该 phase 没切真实 slice -2. 新增 bridge 没写 `deleteByPhase` -3. smoke 或自动化验证未通过 -4. legacy 指标没有下降或明显反弹 -5. 超期 bridge 仍存在 - -## Bridge Register - -所有临时 bridge 都必须登记。每条 bridge 至少包含: - -| Field | Meaning | -| --- | --- | -| `id` | bridge 唯一标识 | -| `owner` | 负责人 | -| `legacyEntry` | 旧入口 | -| `newTarget` | 新目标 | -| `introducedIn` | 引入 PR / 提交 | -| `deleteByPhase` | 最晚删除阶段 | -| `notes` | 限制和风险 | - -默认规则: - -- bridge 最多存活 1 个 phase -- bridge 只允许单向转发 -- bridge 不承载新增业务逻辑 - -## PR Policy - -实施期间优先允许三类 PR: - -1. `guardrail PR` -2. `boundary foundation + immediate slice PR` -3. `slice cutover + legacy cleanup PR` - -每个 PR 应明确回答: - -- 这次切的是哪条真实路径? -- 谁是新的 active owner? -- 删掉了什么旧入口或旧桥接? - -如果答案全部是“没有”,通常不应合并。 - -## Review Checklist - -每个迁移 PR 至少检查以下问题: - -1. 这条路径现在谁是唯一 active owner? -2. renderer 是否仍直接感知 presenter 或 raw IPC? -3. 新代码是否反向依赖旧 presenter? -4. 有没有新增 bridge?是否写明 `deleteByPhase`? -5. 这次是否真的降低了 hot path 耦合? -6. cleanup / cancel / timeout 的 owner 是否清楚? -7. 对应测试和 smoke 是否覆盖了切换点? - -## Migration Scoreboard - -每个 phase 结束时至少追踪: - -| Metric | Meaning | -| --- | --- | -| `renderer.usePresenter.count` | renderer 直接依赖 presenter 的数量 | -| `renderer.windowElectron.count` | renderer 对旧 Electron bridge 的依赖 | -| `renderer.windowApi.count` | renderer 对旧 preload 多入口的依赖 | -| `hotpath.presenterEdge.count` | hot path presenter 直接依赖数量 | -| `runtime.rawTimer.count` | raw timer 数量 | -| `bridge.active.count` | 当前存活 bridge 数量 | -| `bridge.expired.count` | 已超期未删 bridge 数量 | - -Scoreboard 目标不是绝对精确,而是持续证明: - -- 旧耦合在收缩 -- 双轨没有扩散 -- bridge 没有失控 - -## Completion Rule - -本轮完成标准不是“新框架搭好了”,而是: - -- migrated path 的 owner 已切清楚 -- renderer 边界稳定了 -- hot path 耦合净下降 -- bridge register 对本轮范围归零 - diff --git a/docs/architecture/main-kernel-refactor/plan.md b/docs/architecture/main-kernel-refactor/plan.md deleted file mode 100644 index 977885a7e..000000000 --- a/docs/architecture/main-kernel-refactor/plan.md +++ /dev/null @@ -1,236 +0,0 @@ -# Main Kernel Refactor Plan - -## Planning Goal - -本计划将原来的“大而全内核重建”收敛为一条更实际的执行路线: - -- 先稳住 renderer-main 边界 -- 再切一个低风险 pilot slice -- 再拆 chat/session/provider 热路径 owner -- 最后清理迁移过程中产生的桥接和回流 - -核心原则不是“目录先重排”,而是“每一步都减少真实耦合”。 - -## Planning Assumptions - -- 当前运行时代码仍然是唯一生产实现。 -- 现有 `LifecycleManager` 保留,不单独开一个“重写生命周期系统”的 phase。 -- 第一轮默认不用 DI container;优先用显式 factory、setup function 和窄 port。 -- 只有当 session / window cleanup 真的需要时,才引入最小生命周期 helper。 -- 每个 phase 必须伴随真实 slice 切换,避免连续 foundation-only PR。 - -## Current Hotspots - -当前优先级最高的热点: - -- `src/renderer/api/legacy/presenters.ts` 使 renderer 直接感知 presenter 名称与反射调用协议 -- `src/preload/index.ts` 同时承载多入口桥接和旧兼容 API -- `src/main/presenter/index.ts` 同时承担 composition root、service locator、IPC dispatcher -- `SessionPresenter` 仍直接回拿全局 `presenter` -- `AgentSessionPresenter`、`AgentRuntimePresenter`、`LLMProviderPresenter` 之间仍存在主路径直连 -- `eventBus.ts` 同时承担 main 内部通知和 main -> renderer 广播 - -## Target State For This Program - -本轮结束时,目标不是完整 clean architecture,而是让主链路至少具备以下形态: - -```text -renderer component / store - -> renderer/api/*Client - -> window.deepchat - -> shared/contracts/routes + events - -> main handlers / orchestrators for migrated slices - -> narrow ports / adapters around provider, session, permission, scheduler -``` - -允许保留: - -- `src/main/presenter/` 目录 -- 现有 `LifecycleManager` -- 非 migrated path 的 legacy presenter - -但不允许: - -- 在 migrated path 上继续新增 `useLegacyPresenter()` / raw renderer IPC -- 在新 hot path 上继续通过 presenter 互相找彼此 -- 继续把 cleanup / cancel / timeout 塞进匿名回调和散落 timer - -## Migration Rules - -### Allowed - -- 先定义 contract / client / typed event,再迁一条真实调用链 -- 保留旧 owner 作为过渡入口,但只允许单向转发到新实现 -- 为测试目的先引入 fake scheduler、fake provider adapter、fake route registry -- 用现有 presenter 包住旧依赖,只要新逻辑不反向依赖它 - -### Forbidden - -- 为了“未来可能会用”而提前大搬目录 -- 引入完整 DI container 再慢慢找 slice 迁移 -- 用新的 IPC 包装 main 内部互调 -- 在 hot path 上继续新增 presenter-to-presenter 直接依赖 -- 同一条用户路径长期保留新旧双 owner - -## Phase Map - -```text -P0 Guardrails & Baseline - -> P1 Typed Boundary Foundation - -> P2 Settings Pilot Slice - -> P3 Chat & Session Hot Path - -> P4 Provider / Tool Boundary - -> P5 Consolidation & Re-evaluation -``` - -## Phase Details - -### Phase 0: Guardrails & Baseline - -目标: - -- 冻结错误方向 -- 让后续阶段能证明“legacy 指标净下降” - -交付物: - -- 扩展 `architecture-guard` -- 扩展 baseline 脚本 -- 追踪 legacy presenter helper(metric id 仍为 `renderer.usePresenter.count`)、`window.electron`、`window.api`、hot path direct dependency、raw timer、raw channel -- 定义 bridge register 和轻量 scoreboard - -退出条件: - -- 能阻止新增 `useLegacyPresenter()`、新增 raw renderer IPC、新增 migrated path raw channel -- 能重复生成基线 -- 能看出 hot path direct dependency 是否下降 - -### Phase 1: Typed Boundary Foundation - -目标: - -- 让 renderer 调用 main 的方式先稳定下来 - -交付物: - -- `shared/contracts/routes` -- `shared/contracts/events` -- preload bridge builder 或统一 bridge registration 机制 -- `renderer/api/*Client` -- 第一批 typed route:settings、sessions/chat 必需入口、系统窗口入口 - -退出条件: - -- 新增功能不再通过 `useLegacyPresenter()` 接入 -- migrated path 的 renderer 调用能从 route registry 追踪 -- typed event 能承接 settings / sessions / chat 的首批 UI 通知 - -### Phase 2: Settings Pilot Slice - -目标: - -- 选择低风险、行为可观察的 slice 验证新边界是否真的好用 - -交付物: - -- `SettingsClient` -- settings contract / handler / adapter -- settings store 调整为 client-first -- typed `settings.changed` event - -退出条件: - -- settings 主读写链路不再依赖 `useLegacyPresenter()` 或 raw IPC -- 设置持久化和变更通知具备独立测试 -- 对应 bridge 无超期残留 - -### Phase 3: Chat & Session Hot Path - -目标: - -- 拆掉最关键的 owner 混杂点 -- 让发送消息、停止流、恢复会话的执行链更可解释 - -交付物: - -- `ChatService` / orchestrator -- `SessionService` 或等价 session orchestration 层 -- 最小必要 port:`SessionRepository`、`MessageRepository`、`ProviderExecutionPort`、 - `ProviderCatalogPort`、`SessionPermissionPort`、`WindowEventPort`、`Scheduler` -- typed chat/session events -- cancel / timeout / retry 通过 `Scheduler` 管理 - -退出条件: - -- 发送消息、停止流、恢复会话的 migrated path owner 明确 -- `AgentSessionPresenter -> AgentRuntimePresenter` 不再是 migrated path 的主 owner 链 -- migrated path 的 cleanup / cancel 行为可测试 - -### Phase 4: Provider / Tool Boundary - -目标: - -- 把 provider/tool 相关主协作从 presenter 互调中抽出来 - -交付物: - -- `ProviderExecutionPort` -- `ProviderCatalogPort` -- `ProviderSessionPort` 或仅限 ACP/会话配置所需的 provider session adapter -- `SessionPermissionPort` -- tool / permission 的明确响应 contract - -退出条件: - -- migrated path 上 `SessionPresenter` / `AgentSessionPresenter` / `AgentRuntimePresenter` - 不再直接把 provider 当作全局协作者乱用 -- provider query / execution / permission 边界可单测 -- 相关 renderer 交互走 typed route / typed event - -### Phase 5: Consolidation & Re-evaluation - -目标: - -- 清理本轮新增 bridge -- 更新基线和文档 -- 判断是否还需要下一轮更彻底的 kernel 重构 - -交付物: - -- 删除 migrated path 临时桥接 -- 收敛 docs、baseline、scoreboard、smoke 记录 -- 形成“是否继续做全量 kernel 重构”的结论 - -退出条件: - -- 本轮涉及 slice 的 bridge register 归零 -- migrated path 的 legacy 指标较起点净下降 -- 本轮目标路径具备稳定测试和 smoke 证据 - -## Cross-Cutting Streams - -所有阶段都同步处理: - -- guardrail 更新 -- baseline 更新 -- 文档同步 -- bridge register / scoreboard 维护 -- 针对 migrated path 的测试补齐 -- 对旧 owner 的冻结和清理 - -## Recommended PR Sequence - -1. `chore(architecture): tighten guards and baselines for boundary stabilization` -2. `refactor(ipc): add typed route registry and renderer clients` -3. `refactor(settings): migrate settings to typed boundary` -4. `refactor(chat): introduce chat/session orchestration and scheduler` -5. `refactor(providers): narrow provider and permission boundaries` -6. `refactor(architecture): remove temporary bridges and refresh docs` - -## Risk Notes - -- 最大风险仍然是“多搭一层新框架,但旧 owner 一点没退”。 -- 另一类风险是把 phase 2 之前的基础工作拖太久,导致迟迟不切真实 slice。 -- `Chat / Session / Provider` 三块最耦合,不应并行乱切;建议按 owner 顺序推进。 -- 如果某个阶段不得不引入桥接,必须在下一阶段优先删除,而不是留到“最后统一收尾”。 - diff --git a/docs/architecture/main-kernel-refactor/ports-and-scheduler.md b/docs/architecture/main-kernel-refactor/ports-and-scheduler.md deleted file mode 100644 index b0c857700..000000000 --- a/docs/architecture/main-kernel-refactor/ports-and-scheduler.md +++ /dev/null @@ -1,221 +0,0 @@ -# Main Kernel Refactor Ports and Scheduler - -## Purpose - -本文件回答三个问题: - -1. 本轮到底需要哪些 port -2. 哪些能力现在不要急着抽象 -3. `Scheduler` 为什么是本轮少数值得提前引入的接口 - -本轮原则不是“把一切都 port 化”,而是只抽出能直接减少 hot path 耦合的最小集合。 - -## Decision Rule - -只有同时满足以下条件时,能力才进入 port: - -1. 它是 orchestration 层依赖的外部能力或跨 slice 能力 -2. 现有直接依赖已经造成 owner 不清、测试困难或 presenter 互调 -3. 抽出来后能立刻替换一条真实主路径 - -如果只是为了“以后可能更优雅”,本轮不要抽。 - -## Minimum Port Set - -### 1. SessionRepository - -责任: - -- session 元数据读取、创建、恢复、状态更新 - -为什么现在就需要: - -- chat/session orchestration 不能继续直接掺着 presenter 和 sqlite 管理 session owner - -### 2. MessageRepository - -责任: - -- message append、查询、状态更新、重试相关读取 - -为什么现在就需要: - -- send / stop / retry 这些路径要有可替换的数据面,方便做单测和集成测试 - -### 3. ProviderCatalogPort - -责任: - -- provider、model、custom model 查询 - -为什么现在就需要: - -- 这是 `ConfigQueryPort` 中最容易外溢的一部分,也是 session/chat 在选 provider、校验 model 时的明确依赖 - -### 4. ProviderExecutionPort - -责任: - -- completion、stream、provider 级执行入口 - -为什么现在就需要: - -- migrated chat path 不应该继续直接知道 `LLMProviderPresenter` 的完整接口面 - -### 5. ProviderSessionPort - -责任: - -- 仅限会话级 provider runtime 配置,如 ACP session / workdir / mode - -注意: - -- 只有当 Phase 4 真切到这些路径时才落地 -- 不要求在本轮一开始就完整抽出 - -### 6. SessionPermissionPort - -责任: - -- 权限请求响应、权限批准、权限清理 - -为什么现在就需要: - -- `SessionRuntimePort` 当前把 UI 刷新、权限清理、权限批准混在一起,边界过胖 - -### 7. WindowEventPort - -责任: - -- 向 renderer 发布 typed UI event - -为什么现在就需要: - -- migrated path 不能继续依赖 `eventBus.sendToRenderer()` 这种混合语义 - -### 8. Scheduler - -责任: - -- `sleep` -- `timeout` -- `retry` - -为什么现在就需要: - -- send / stop / provider failure 这些路径的可测性和 cleanup 可靠性都强依赖统一时序语义 - -## What We Intentionally Do Not Port Yet - -以下能力本轮不急着抽: - -- browser / yoBrowser 全量能力 -- workspace 全量能力 -- exporter -- remote control -- dialog/file picker 的全量系统能力 -- 完整事件总线抽象 - -这些能力不是不重要,而是当前不是 hot path 的最大耦合来源。 - -## Legacy To Port Mapping - -### `ConfigQueryPort` - -本轮只拆最必要的部分: - -| Legacy capability | New target | -| --- | --- | -| `getProviderModels` | `ProviderCatalogPort` | -| `getCustomModels` | `ProviderCatalogPort` | -| `getAgentType` | 先保留在现有 adapter 或单独 `AgentCatalogPort`,仅在 chat/session 需要时再抽 | - -### `SessionRuntimePort` - -本轮拆成: - -| Legacy capability | New target | -| --- | --- | -| `refreshSessionUi` | `WindowEventPort` | -| `clearSessionPermissions` | `SessionPermissionPort` | -| `approvePermission` | `SessionPermissionPort` | - -### Direct `LLMProviderPresenter` Usage - -本轮拆法: - -| Current usage | New target | -| --- | --- | -| model / custom model query | `ProviderCatalogPort` | -| completion / stream / rate-limit-wrapped execution | `ProviderExecutionPort` | -| ACP session / workdir / mode | `ProviderSessionPort` | -| permission-related collaboration | `SessionPermissionPort` | - -## Scheduler - -### Suggested Interface - -```ts -export interface Scheduler { - sleep(input: SleepInput): Promise - timeout(input: TimeoutInput): Promise - retry(input: RetryInput): Promise -} - -export interface SleepInput { - ms: number - reason: string - signal?: AbortSignal -} - -export interface TimeoutInput { - task: Promise - ms: number - reason: string - signal?: AbortSignal -} - -export interface RetryInput { - task: () => Promise - maxAttempts: number - initialDelayMs: number - backoff: number - reason: string - signal?: AbortSignal -} -``` - -### Why It Must Exist - -当前仓库里的 raw timer 混杂了: - -- 业务等待 -- 超时保护 -- 重试退避 -- 清理延迟 -- watcher / polling - -如果继续散落使用,会带来三个直接问题: - -1. 无法稳定测试 -2. 无法统一取消 -3. cleanup owner 不清楚 - -因此,本轮虽然不追求全量新架构,但 `Scheduler` 仍值得提前抽出来。 - -### Implementation Policy - -- 接口自写 -- `retry` 内部可用 `p-retry` -- 业务层不直接依赖任何第三方重试库 - -## Port Design Guardrails - -实施时必须检查: - -1. 这个 port 是否直接替换了一个真实 hard dependency? -2. 它是否缩小了 orchestration 层需要知道的实现细节? -3. 它是否有明确的 fake / stub 测试价值? -4. 它是否按能力分组,而不是按旧 presenter 名称分组? - -如果答案不明确,本轮就不要抽。 diff --git a/docs/architecture/main-kernel-refactor/route-schema-catalog.md b/docs/architecture/main-kernel-refactor/route-schema-catalog.md deleted file mode 100644 index 3ec5aa6ce..000000000 --- a/docs/architecture/main-kernel-refactor/route-schema-catalog.md +++ /dev/null @@ -1,208 +0,0 @@ -# Main Kernel Refactor Route Schema Catalog - -## Purpose - -本文件锁定本轮真正需要的两类契约: - -1. renderer 发起的 typed route -2. main -> renderer 的 typed event - -目标不是一次性冻结所有未来 route,而是先把 migrated path 的边界稳定下来。 - -## Design Rules - -### 1. Route Only Solves Renderer-Main Boundary - -route registry 只收 renderer 发起、必须跨 renderer-main 边界的能力。 - -main 内部协作不进 route registry。 - -### 2. Capability Naming - -route 名称按用户意图命名,不按内部类命名: - -- `settings.getSnapshot` -- `settings.update` -- `sessions.create` -- `chat.sendMessage` -- `chat.stopStream` - -不要出现: - -- `configPresenter.get` -- `llmProviderPresenter.generate` -- `sessionRepository.insert` - -### 3. Coarse-Grained Surface - -renderer 调用 use case,不调用内部编排步骤。 - -合理: - -- `chat.sendMessage` -- `chat.stopStream` -- `providers.listModels` - -不合理: - -- `messages.insertUserMessage` -- `provider.executeWithRateLimit` -- `sessionRuntime.refreshUi` - -### 4. Registry Is The Source Of Truth - -同一份 registry 负责: - -- route 名称 -- input/output schema -- preload bridge 类型来源 -- renderer client 类型来源 - -### 5. Typed Events For Migrated Paths - -只要 migrated path 需要 main -> renderer 通知,就必须定义 typed event。 - -不再允许为 migrated path 新增随意字符串事件。 - -## Target Layout - -```text -src/shared/contracts/ - routes/ - settings.routes.ts - sessions.routes.ts - chat.routes.ts - providers.routes.ts - system.routes.ts - events/ - settings.events.ts - sessions.events.ts - chat.events.ts - routes.ts - events.ts -``` - -本轮不要求一开始就有更多分类文件。 - -## Common Schemas - -第一批建议统一这些基础 schema: - -```ts -import { z } from 'zod' - -export const EntityIdSchema = z.string().min(1) -export const TimestampMsSchema = z.number().int().nonnegative() - -export const JsonValueSchema: z.ZodType = z.lazy(() => - z.union([ - z.string(), - z.number(), - z.boolean(), - z.null(), - z.array(JsonValueSchema), - z.record(JsonValueSchema) - ]) -) - -export const AppErrorSchema = z.object({ - code: z.string(), - message: z.string(), - retriable: z.boolean().default(false), - details: z.record(JsonValueSchema).optional() -}) -``` - -## Initial Route Catalog - -### Phase 1 and Phase 2 - -| Route | Input summary | Output summary | Notes | -| --- | --- | --- | --- | -| `settings.getSnapshot` | optional key filter | settings snapshot | settings pilot 主入口 | -| `settings.listSystemFonts` | none | normalized system font list | settings pilot 的字体选择辅助入口 | -| `settings.update` | array of key/value changes | version + changed keys | 取代散落设置调用 | -| `system.openSettings` | optional section | `{ windowId }` | 取代直接窗口调用 | - -### Phase 3 - -| Route | Input summary | Output summary | Notes | -| --- | --- | --- | --- | -| `sessions.create` | title / provider / model / agent / projectDir | session summary | 会话创建主入口 | -| `sessions.restore` | `{ sessionId, limit? }` | `{ session, messages, nextCursor, hasMore }` | 恢复最新一页消息 | -| `sessions.listMessagesPage` | `{ sessionId, cursor?, limit? }` | `{ messages, nextCursor, hasMore }` | 向更老消息翻页 | -| `sessions.list` | optional filter | session summaries | 列表主入口 | -| `sessions.activate` | `{ sessionId }` | `{ activated: true }` | 当前窗口激活会话 | -| `sessions.deactivate` | none | `{ deactivated: true }` | 当前窗口关闭活跃会话 | -| `sessions.getActive` | none | `{ session }` | 当前窗口读取活跃会话 | -| `chat.sendMessage` | `sessionId`, content, attachments | `{ accepted, requestId, messageId }` | 聊天发送主入口 | -| `chat.stopStream` | `sessionId` or `requestId` | `{ stopped: boolean }` | 停止流语义固定 | - -### Phase 4 - -| Route | Input summary | Output summary | Notes | -| --- | --- | --- | --- | -| `providers.listModels` | `{ providerId }` | provider + custom model summaries | provider 查询边界 | -| `providers.testConnection` | `{ providerId, modelId? }` | `{ isOk, errorMsg }` | 当前配置页验证入口 | -| `chat.respondToolInteraction` | session / message / tool / typed response | `{ accepted, resumed?, waitingForUserMessage? }` | 统一权限/问题响应入口 | - -## Initial Typed Event Catalog - -本轮最先冻结以下事件: - -| Event | Payload summary | Why | -| --- | --- | --- | -| `settings.changed` | changed keys + version | settings store 刷新 | -| `sessions.updated` | affected session ids + reason | 会话列表与当前会话刷新 | -| `chat.stream.updated` | requestId/sessionId + delta/tool/update | 流式主路径 | -| `chat.stream.completed` | requestId/sessionId/messageId | 结束信号 | -| `chat.stream.failed` | requestId/sessionId/error | 错误信号 | - -### Example Stream Event Schema - -```ts -export const ChatStreamEventSchema = z.discriminatedUnion('kind', [ - z.object({ - kind: z.literal('delta'), - requestId: EntityIdSchema, - sessionId: EntityIdSchema, - messageId: EntityIdSchema, - textDelta: z.string() - }), - z.object({ - kind: z.literal('tool-call'), - requestId: EntityIdSchema, - sessionId: EntityIdSchema, - toolCallId: EntityIdSchema, - toolName: z.string(), - status: z.enum(['start', 'running', 'end', 'error']) - }), - z.object({ - kind: z.literal('permission-required'), - requestId: EntityIdSchema, - sessionId: EntityIdSchema, - permissionRequestId: EntityIdSchema, - permissionType: z.enum(['read', 'write', 'all', 'command']) - }) -]) -``` - -## What We Intentionally Do Not Freeze Yet - -为了避免过度设计,以下内容本轮不提前固定: - -- main 内部 provider orchestration protocol -- repository-level CRUD route -- file / browser / workspace 的全量系统 route -- plugin 相关 route -- 低频页面的边角能力 - -## Implementation Guardrails - -新增 route 或 event 时必须回答: - -1. 这是否真的是 renderer-main 边界? -2. 名称是否表达用户意图,而不是内部实现细节? -3. 是否已经有更粗粒度的 route 可承接? -4. 是否能通过 schema 清楚描述? -5. 是否会替代一条现有 raw IPC 或 presenter 直调? diff --git a/docs/architecture/main-kernel-refactor/spec.md b/docs/architecture/main-kernel-refactor/spec.md deleted file mode 100644 index 511974ef8..000000000 --- a/docs/architecture/main-kernel-refactor/spec.md +++ /dev/null @@ -1,216 +0,0 @@ -# Main Kernel Refactor - -## Summary - -本轮重构在原计划基础上收敛为一套“稳定性优先”的方案。 - -目标不是一次性把 `src/main` 改造成完整的 `main/app + domain + infra` 新世界,也不是在本轮强行把 -`src/main/presenter/` 清零;目标是先解决当前最影响稳定性、可维护性和可测试性的几类问题: - -- renderer 通过 `useLegacyPresenter()`、`window.electron`、`window.api` 直接知道 main 内部实现 -- main hot path 通过 presenter-to-presenter 直接协作,owner 不清楚 -- session / window 生命周期和 stream / timer cleanup 缺少明确 owner -- 新代码继续回流到旧边界,导致耦合只增不减 - -这是一轮“边界稳定化 + 热路径减耦”工程,而不是全量目录重建工程。 - -## Program Reset - -原方案中保留的高收益部分: - -- typed renderer-main boundary -- hot path presenter coupling 拆解 -- lifecycle owner 明确化 -- scheduler / cancel / timeout / retry 收口 -- 分阶段迁移与净减 legacy 指标 - -本轮主动砍掉的目标: - -- 不要求一次性交付完整 clean architecture 目录骨架 -- 不要求本轮删除整个 `src/main/presenter/` -- 不要求先引入完整 DI container 或自写大 Scope 系统 -- 不要求先重写整个 EventBus 体系 -- 不把“内存下降”作为主成功指标 - -## Baseline - -以下基线来自 `2026-04-19` 的最新扫描,用于说明问题集中区域: - -| Signal | Baseline | Why it matters | -| --- | --- | --- | -| `src/main/presenter/index.ts` 行数 | 769 | 组合根过重,依赖装配和运行时 owner 混在一起 | -| main dependency cycles | 30 | presenter 互相引用和全局入口回流仍明显 | -| renderer `useLegacyPresenter(` 命中 | 90 | renderer 仍依赖 presenter naming | -| renderer `window.electron*` 命中 | 111 | renderer 仍深度感知 Electron / IPC 实现 | -| renderer `window.api` 命中 | 34 | preload 仍是多入口兼容面 | -| `setTimeout` / `setInterval` 命中 | 123 | cleanup、超时和轮询语义散落 | - -这些数字说明,真正的问题是: - -- 边界不稳定 -- owner 不清楚 -- 热路径依赖过粗 -- 可测性差 - -而不是“目录名字不够先进”。 - -## Detailed Design Docs - -本轮仍保留以下设计文档,但都按“最小必要抽象”执行: - -- [ports-and-scheduler.md](./ports-and-scheduler.md) -- [route-schema-catalog.md](./route-schema-catalog.md) -- [eventbus-migration.md](./eventbus-migration.md) - -## Goals - -- 让 renderer-main 主边界收敛到 typed route registry、typed event 和 `renderer/api` client。 -- 阻止新增 `useLegacyPresenter()`、新增 raw renderer IPC、新增旧桥接依赖。 -- 拆掉 chat / session / provider hot path 上最深的 presenter-to-presenter 直接耦合。 -- 让 session / window / stream cleanup 的 owner 明确且可测试。 -- 让关键流程可以通过 fake port、stub provider、fake scheduler 做稳定测试。 -- 每个阶段结束时,legacy 指标净下降,而不是只新增第二套实现。 - -## Non-Goals - -- 不交付完整 clean architecture 目录重排。 -- 不替换现有 `LifecycleManager`。 -- 不要求删除所有 presenter 或所有 singleton。 -- 不要求一次性清理所有历史事件常量和所有旧桥接。 -- 不把内存或启动速度优化作为本轮核心目标。 - -## User Stories - -- 作为维护者,我可以看清 renderer 究竟通过哪些能力边界调用 main,而不是继续猜测 presenter 方法名。 -- 作为功能开发者,我可以在 hot path 上新增逻辑,而不用沿着全局 `Presenter` 和隐式事件链继续扩散耦合。 -- 作为测试编写者,我可以对发送消息、停止流、provider 查询、设置读写分别做独立测试。 -- 作为 QA,我可以验证迁移后的主路径行为没有回退,而不是只验证“代码还能跑”。 - -## In Scope - -- `renderer/api` client、typed route registry、typed preload bridge -- `settings` 作为第一个 pilot slice -- `chat/session/provider` 热路径 owner 收口 -- `Scheduler` 接口与 cancel / timeout / retry 语义统一 -- session / window 相关 lifecycle cleanup 明确化 -- 迁移治理、guardrail、baseline、smoke 和测试方案 - -## Out of Scope - -- 全量 presenter 退役 -- 全量 EventBus 重写 -- 全量 repository / domain model 重命名或搬目录 -- 新增插件系统 -- 以性能优化名义大规模重写运行时 - -## Constraints - -- 现有用户可见行为优先保持兼容,尤其是会话创建、恢复、发送消息、流式回复、停止流、provider 切换和权限交互。 -- 新抽象必须替代真实耦合点,不能只增加一层新名字。 -- renderer-main 边界和 main 内部协作边界必须分开设计,不能混为一谈。 -- 每个阶段都必须切一个真实路径,不接受连续多轮纯基础设施 PR。 - -## Architectural Principles - -### 1. Boundary First - -先稳住 renderer-main 边界,再谈内部结构继续细化。 - -如果 renderer 仍直接依赖 presenter 名称,main 内部怎么拆都很难真正稳定。 - -### 2. Hot Path First - -优先处理最深的主链路耦合: - -- `useLegacyPresenter()` / raw IPC -- `AgentSessionPresenter -> AgentRuntimePresenter` -- `SessionPresenter` / `AgentSessionPresenter` / `AgentRuntimePresenter` 对 provider 运行时的直接索取 - -不先从低频边角模块开始。 - -### 3. Explicit Owner - -每条主路径都必须能回答: - -- 谁创建资源 -- 谁发起执行 -- 谁负责取消 -- 谁负责 cleanup - -如果回答仍然是“某个 presenter 顺手处理一下”,说明边界还不够清楚。 - -### 4. Minimal New Infrastructure - -不为“看起来更像架构”而先造完整 container、完整 event framework、完整 domain taxonomy。 - -只有当抽象能直接替换一个现有 hard dependency,或者显著提升测试稳定性时,才进入本轮。 - -### 5. Keep Working Code, Freeze Bad Growth - -现有生命周期、presenter 和兼容层允许继续存在,但迁移覆盖到的 slice 上: - -- 旧 owner 冻结 -- 只允许 `old -> new` 单向转发 -- 不允许继续长新逻辑 - -## Target Snapshot - -本轮目标不是完整新目录,而是把活跃热路径调整成以下形态: - -```text -renderer - -> renderer/api/*Client - -> window.deepchat - -> shared/contracts/routes + shared/contracts/events - -> migrated main handlers / services / adapters - -> existing presenters or managers only behind explicit ports on non-migrated paths -``` - -main 侧允许在过渡期保持混合结构,但必须满足: - -- migrated path 不再由 renderer 直呼 presenter 名称 -- migrated path 不再依赖 presenter-to-presenter 直接 owner 链 -- lifecycle cleanup 和 scheduler 语义可单独测试 - -## Success Metrics - -本轮重点看以下指标是否下降: - -- `renderer.usePresenter.count` -- `renderer.windowElectron.count` -- `renderer.windowApi.count` -- hot path 上的 presenter direct dependency 数量 -- migrated path 的 raw channel / raw timer 数量 -- 为 migrated path 建立的 contract / unit / integration 测试数量 - -## Memory Position - -内存不是本轮主成功指标。 - -这轮工作只有在以下情况才可能带来可观的内存收益: - -- session / stream / permission 状态在结束时能被更可靠地释放 -- window / webContents listener 能按 owner 清理 -- 不再长期保留无主的 abort controller、timer、subscription - -如果只是把 presenter 换成 service 或 kernel 命名,而对象生命周期并未变化,则不应期待明显内存下降。 - -## Acceptance Direction - -最终验收以以下三类结果为核心: - -- renderer 边界更稳定 -- hot path 更容易解释和测试 -- cleanup / cancel / timeout 行为更可靠 - -不以“新目录是否看起来更纯粹”作为主要标准。 - -## Open Questions - -本轮不阻塞实施的决策如下: - -- 现有 `LifecycleManager` 保留,除非后续 hot path 证明它成为阻塞点。 -- `Presenter` 可作为过渡期 composition shell 保留,但不再继续吞新主链路。 -- `EventBus` 先冻结新增错误用法,优先给 migrated path 建 typed UI event;是否全量重写,等本轮收敛后再评估。 -- 本轮结束后再决定是否还有必要推进更彻底的 `main kernel` 目录重构。 - diff --git a/docs/architecture/main-kernel-refactor/tasks.md b/docs/architecture/main-kernel-refactor/tasks.md deleted file mode 100644 index dc2ba0d0f..000000000 --- a/docs/architecture/main-kernel-refactor/tasks.md +++ /dev/null @@ -1,84 +0,0 @@ -# Main Kernel Refactor Tasks - -## Program Reset - -- [x] 保留 `docs/architecture/main-kernel-refactor/` 目录作为本轮主规格入口 -- [x] 将原“大而全 kernel 重构”收敛为“边界稳定 + 热路径减耦 + 可测试性提升”方案 -- [x] 重写 `spec.md` -- [x] 重写 `plan.md` -- [x] 重写 `acceptance.md` -- [x] 重写 `tasks.md` -- [x] 重写 `test-plan.md` -- [x] 重写 `migration-governance.md` -- [x] 重写 `build-vs-buy.md` -- [x] 重写 `ports-and-scheduler.md` -- [x] 重写 `route-schema-catalog.md` -- [x] 重写 `eventbus-migration.md` -- [x] 更新 `docs/README.md` 中对本计划的描述 - -## Phase 0: Guardrails & Baseline - -- [x] 扩展 `scripts/architecture-guard.mjs`,阻止新增 `useLegacyPresenter()` 调用点 -- [x] 扩展 `scripts/architecture-guard.mjs`,阻止新增 `window.electron.ipcRenderer.*` 监听 -- [x] 扩展 `scripts/architecture-guard.mjs`,阻止在 migrated path 中新增 raw channel 字符串 -- [x] 为 hot path direct dependency 增加趋势检查 -- [x] 扩展 baseline 脚本,输出 legacy presenter helper(metric id 保持 `renderer.usePresenter.count`)/ `window.electron` / `window.api` / raw timer / bridge 数量 -- [x] 建立 bridge register -- [x] 建立轻量 migration scoreboard - -## Phase 1: Typed Boundary Foundation - -- [x] 新建或收敛 `src/shared/contracts/` 目录 -- [x] 定义 route registry,覆盖 settings 与 chat/session 首批入口 -- [x] 定义 typed event catalog,覆盖 `settings.changed`、`sessions.updated`、`chat.stream.*` -- [x] 新建 `src/preload/createBridge.ts` 或统一 bridge builder -- [x] 新建 `src/renderer/api/SettingsClient.ts` -- [x] 新建 `src/renderer/api/SessionClient.ts` -- [x] 新建 `src/renderer/api/ChatClient.ts` -- [x] 约束后续新增 renderer-main 能力必须先落 route registry - -## Phase 2: Settings Pilot Slice - -- [x] 设计 settings contract / handler / adapter -- [x] 将 settings renderer/store 迁移到 `SettingsClient` -- [x] 清理 settings 主路径上的 `useLegacyPresenter()` / raw IPC 依赖 -- [x] 补齐 settings 单测、集成测试和 smoke -- [x] 删除 settings 迁移过程中的临时桥接 - -## Phase 3: Chat & Session Hot Path - -- [x] 设计 `ChatService` 或等价 orchestration 层 -- [x] 设计 `SessionService` 或等价 session orchestration 层 -- [x] 抽出最小必要 port:`SessionRepository`、`MessageRepository`、`ProviderExecutionPort` -- [x] 抽出最小必要 port:`ProviderCatalogPort`、`SessionPermissionPort`、`WindowEventPort` -- [x] 引入 `Scheduler` 接口并承接 cancel / timeout / retry -- [x] 迁移发送消息、停止流、恢复会话主链路 -- [x] 清理 `AgentSessionPresenter -> AgentRuntimePresenter` 在 migrated path 上的主 owner 角色 -- [x] 补齐 chat/session 主路径测试 - -## Phase 4: Provider / Tool Boundary - -- [x] 明确 provider query / execution / session 配置的边界 -- [x] 将 migrated path 上 presenter 对 provider 的直接调用改为 port / adapter -- [x] 收口 permission response、tool response 的 contract -- [x] 如确有需要,再补 `ProviderSessionPort` -- [x] 为 provider / permission / tool 关键交互补测试 -- [x] 删除本阶段引入的临时兼容桥 - -## Phase 5: Consolidation & Re-evaluation - -- [x] 删除本轮仍存活的 bridge -- [x] 重跑 baseline 并更新 scoreboard -- [x] 更新 active docs 和实现说明 -- [x] 记录最终 smoke 结果 -- [x] 形成“是否继续推进更彻底 kernel 重构”的结论 - -## Phase Exit Discipline - -- [x] 每个阶段完成时更新本文件状态 -- [x] 每个阶段完成时更新 `acceptance.md` -- [x] 每个阶段完成时更新 `test-plan.md` -- [x] 每个阶段完成时更新 `docs/architecture/baselines/*` -- [x] 每个阶段完成时更新 bridge register 与 scoreboard -- [x] 每个阶段完成时确认 legacy 指标净下降 - diff --git a/docs/architecture/main-kernel-refactor/test-plan.md b/docs/architecture/main-kernel-refactor/test-plan.md deleted file mode 100644 index 4ca862f46..000000000 --- a/docs/architecture/main-kernel-refactor/test-plan.md +++ /dev/null @@ -1,264 +0,0 @@ -# Main Kernel Refactor Test Plan - -## Test Goal - -本测试方案服务于“边界稳定化 + 热路径减耦”。 - -测试目标不是一轮内把整个仓库历史问题清零,而是确保: - -- migrated path 的新边界可独立验证 -- owner 切换点有明确测试 -- cleanup / cancel / timeout 行为可证明 -- 旧路径在被替换时不会静默回退 - -## Test Layers - -### 1. Static Guard Tests - -作用: - -- 阻止错误依赖方向继续进入仓库 -- 让结构回退尽早失败 - -覆盖内容: - -- `architecture-guard` -- baseline 生成脚本 -- grep 型 guard:`useLegacyPresenter`、`window.electron`、`window.api` -- migrated path 的 raw channel / raw timer 检查 -- hot path direct dependency 趋势检查 -- bridge register / scoreboard 一致性检查 - -### 2. Contract Tests - -作用: - -- 验证 route registry、schema、typed event、preload bridge、renderer client 的一致性 - -建议对象: - -- `shared/contracts/routes` -- `shared/contracts/events` -- `createBridge` -- `renderer/api/*Client` - -### 3. Orchestration Unit Tests - -作用: - -- 验证新引入的 orchestration 层与 port 协作逻辑 - -建议对象: - -- `SettingsService` -- `SessionService` -- `ChatService` -- `Scheduler` -- provider / permission adapter - -### 4. Main Integration Tests - -作用: - -- 验证 `route -> handler -> orchestration -> adapter` 这一整段主链路 - -建议对象: - -- settings 读写 -- session create / restore -- chat send / stop -- provider query / permission response - -### 5. Renderer Integration Tests - -作用: - -- 验证 store / composable 在切换到 `renderer/api` 后状态仍保持一致 - -建议对象: - -- settings store -- session store -- message / stream store -- provider store - -### 6. Electron Smoke Tests - -作用: - -- 验证最终用户可见的核心行为未回退 - -建议路径: - -- 启动应用 -- 修改设置 -- 创建会话 -- 发送消息 -- 观察 stream -- 停止流 -- 恢复会话 -- 切换 provider 或完成一次 provider 相关交互 - -## Required Harnesses - -随着阶段推进,逐步补齐: - -- fake `Scheduler` -- route registry fixture -- typed event fixture -- fake provider adapter -- fake permission adapter -- preload bridge test double - -## Phase-by-Phase Test Matrix - -| Phase | Automated Coverage | Manual Smoke | -| --- | --- | --- | -| P0 | guard script、baseline script | 确认脚本输出可复现 | -| P1 | route registry tests、typed event tests、bridge/client tests | 通过新 client 调 settings / session / chat 首批 route | -| P2 | settings contract/integration/store tests | 修改设置并重启确认持久化 | -| P3 | chat/session orchestration tests、scheduler tests | 创建会话、发消息、停止流、恢复会话 | -| P4 | provider/permission/tool boundary tests | 完成 provider 相关交互与权限响应 | -| P5 | full regression pack for migrated paths | 冷启动、重启恢复、一轮完整主路径 smoke | - -## Current Phase Evidence - -- Phase 0 completed on `2026-04-19` -- Automated verification for Phase 0 uses `node scripts/architecture-guard.mjs` -- Automated verification for Phase 0 uses `pnpm run architecture:baseline` -- Automated verification for Phase 0 uses `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck` -- Repro smoke for Phase 0 is rerunning guard and baseline scripts and confirming committed reports regenerate without drift -- Phase 0 artifacts are `docs/architecture/baselines/main-kernel-boundary-baseline.md` -- Phase 0 artifacts are `docs/architecture/baselines/main-kernel-bridge-register.md` -- Phase 0 artifacts are `docs/architecture/baselines/main-kernel-migration-scoreboard.md` -- Phase 1 completed on `2026-04-19` -- Automated verification for Phase 1 uses `pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts` -- Automated verification for Phase 1 uses `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/createBridge.test.ts test/renderer/api/clients.test.ts` -- Automated verification for Phase 1 uses `pnpm run typecheck` -- Automated verification for Phase 1 uses `pnpm run format`, `pnpm run i18n`, `pnpm run lint` -- Repro smoke for Phase 1 is invoking `window.deepchat` routes through the new bridge and confirming typed event subscriptions receive `settings.changed`, `sessions.updated`, and `chat.stream.*` -- Phase 1 artifacts are `src/shared/contracts/`, `src/preload/createBridge.ts`, `src/main/routes/`, and `src/renderer/api/` -- Phase 2 implementation and automated verification completed on `2026-04-19` -- Automated verification for Phase 2 uses `pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts test/main/routes/settingsHandler.test.ts` -- Automated verification for Phase 2 uses `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/clients.test.ts test/renderer/stores/uiSettingsStore.test.ts` -- Automated verification for Phase 2 uses `pnpm run typecheck` -- Automated verification for Phase 2 uses `pnpm run format`, `pnpm run i18n`, `pnpm run lint` -- Automated verification for Phase 2 uses `pnpm run architecture:baseline` -- Repro smoke for Phase 2 is modifying font size, notifications, and font family through the settings UI, then restarting the app to confirm persistence and cross-window refresh -- Phase 2 artifacts are `src/main/routes/settings/`, `src/main/routes/index.ts`, `src/renderer/api/SettingsClient.ts`, and `src/renderer/src/stores/uiSettingsStore.ts` -- Phase 3 implementation and automated verification completed on `2026-04-19` -- Automated verification for Phase 3 uses `pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts test/main/routes/sessionService.test.ts test/main/routes/chatService.test.ts test/main/routes/scheduler.test.ts` -- Automated verification for Phase 3 uses `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/clients.test.ts test/renderer/stores/pageRouter.test.ts test/renderer/stores/messageStore.test.ts test/renderer/stores/sessionStore.test.ts` -- Automated verification for Phase 3 follow-up uses `pnpm exec vitest --config vitest.config.ts test/main/presenter/agentRuntimePresenter/echo.test.ts` -- Automated verification for Phase 3 follow-up uses `pnpm exec vitest --config vitest.config.ts test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts` -- Automated verification for Phase 3 follow-up uses `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/components/ChatPage.test.ts test/renderer/stores/messageStore.test.ts test/renderer/stores/sessionStore.test.ts` -- Automated verification for Phase 3 uses `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck` -- Automated verification for Phase 3 uses `node scripts/architecture-guard.mjs` -- Automated verification for Phase 3 uses `pnpm run architecture:baseline` -- Repro smoke for Phase 3 is: cold start the app, create a regular session, send one message, confirm incremental stream updates arrive before completion, send a second message and confirm user/assistant ordering stays correct, stop the stream before completion, reopen the session, and confirm the active session is restored after switching away and back -- Phase 3 artifacts are `src/main/routes/chat/`, `src/main/routes/sessions/`, `src/main/routes/hotPathPorts.ts`, `src/main/routes/scheduler.ts`, `src/renderer/api/ChatClient.ts`, `src/renderer/api/SessionClient.ts`, `src/renderer/src/stores/ui/session.ts`, `src/renderer/src/stores/ui/message.ts`, and `src/renderer/src/stores/ui/pageRouter.ts` -- Phase 4 implementation and automated verification completed on `2026-04-20` -- Automated verification for Phase 4 uses `pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts test/main/routes/chatService.test.ts test/main/routes/providerService.test.ts` -- Automated verification for Phase 4 uses `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/clients.test.ts test/renderer/components/ChatPage.test.ts` -- Automated verification for Phase 4 uses `pnpm exec vitest --config vitest.config.ts test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts` -- Automated verification for Phase 4 uses `pnpm exec vitest --config vitest.config.ts test/main/presenter/agentSessionPresenter/integration.test.ts test/main/presenter/agentSessionPresenter/usageDashboard.test.ts` -- Automated verification for Phase 4 uses `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck` -- Automated verification for Phase 4 uses `node scripts/architecture-guard.mjs` -- Automated verification for Phase 4 uses `pnpm run architecture:baseline` -- Repro smoke for Phase 4 is: cold start the app, open provider settings and run one provider verification, create or open a session that triggers a permission/question overlay, approve or answer it through the overlay, confirm the overlay disappears and the message list refreshes, then stop any in-flight stream and confirm the session remains usable -- Phase 4 artifacts are `src/main/presenter/runtimePorts.ts`, `src/main/presenter/index.ts`, `src/main/presenter/agentRuntimePresenter/index.ts`, `src/main/presenter/agentSessionPresenter/index.ts`, `src/main/routes/providers/providerService.ts`, `src/main/routes/hotPathPorts.ts`, `src/main/routes/index.ts`, `src/shared/contracts/routes/providers.routes.ts`, `src/shared/contracts/routes/chat.routes.ts`, `src/renderer/api/ProviderClient.ts`, `src/renderer/api/ChatClient.ts`, `src/renderer/src/stores/providerStore.ts`, and `src/renderer/src/pages/ChatPage.vue` -- Phase 5 implementation and automated verification completed on `2026-04-20` -- Automated verification for Phase 5 uses `pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts test/main/routes/settingsHandler.test.ts test/main/routes/sessionService.test.ts test/main/routes/chatService.test.ts test/main/routes/providerService.test.ts test/main/routes/scheduler.test.ts test/main/presenter/agentRuntimePresenter/echo.test.ts test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts test/main/presenter/agentSessionPresenter/integration.test.ts test/main/presenter/agentSessionPresenter/usageDashboard.test.ts` -- Automated verification for Phase 5 uses `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/createBridge.test.ts test/renderer/api/clients.test.ts test/renderer/stores/uiSettingsStore.test.ts test/renderer/stores/pageRouter.test.ts test/renderer/stores/messageStore.test.ts test/renderer/stores/sessionStore.test.ts test/renderer/components/ChatPage.test.ts` -- The Phase 5 migrated-path regression pack passed with `19` test files and `269` tests -- Automated verification for Phase 5 uses `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck` -- Automated verification for Phase 5 uses `pnpm run architecture:baseline` -- Repro smoke for Phase 5 is: cold start the app, change one persisted setting, create a regular session, send one message, confirm stream updates render before completion, stop one in-flight reply, restart the app, restore the same session, run one provider verification, and complete one permission or question interaction -- Phase 5 final smoke handoff result is `pending manual validation before commit`; use the runbook below to record pass/fail per step -- Phase 5 artifacts are the refreshed `docs/architecture/baselines/main-kernel-*.{md,json}` reports at current phase `P5`, plus the updated `docs/README.md`, `docs/ARCHITECTURE.md`, and `docs/guides/code-navigation.md` - -## Standard Verification Commands - -每个阶段结束至少执行: - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- `pnpm run typecheck` - -按改动范围补充: - -- `pnpm run test:main` -- `pnpm run test:renderer` -- 受影响模块的 targeted Vitest suites -- baseline / guard 相关脚本 - -## Slice-Specific Test Notes - -### Settings Slice - -- 验证读、写、变更通知、重启持久化 -- 验证 settings store 不再依赖 `useLegacyPresenter()` 主入口 -- 验证 settings 通知走 typed event - -### Chat & Session Slice - -- 验证 session create / restore / activate -- 验证发送消息成功路径 -- 验证停止流 -- 验证 provider 失败路径和超时路径 -- 验证 cancel / timeout / retry 的 owner 清楚且不会残留无主状态 - -### Provider / Permission Slice - -- 验证 provider query 与执行能力边界 -- 验证 permission request / response contract -- 验证 migrated path 不再通过 presenter 直接协商 provider 行为 - -## Manual Smoke Matrix - -每轮阶段验收至少执行以下手工验证: - -1. 冷启动应用,确认主窗口正常打开。 -2. 修改一个设置并确认界面状态更新。 -3. 创建一个新会话。 -4. 发送一条普通消息。 -5. 在 stream 进行中执行一次 stop/cancel。 -6. 关闭并重新打开相关页面或重启应用,确认会话与设置可恢复。 -7. 完成一次 provider 相关交互,确认结果正常返回。 - -### Phase 5 Final Smoke Handoff - -1. 冷启动应用,确认主窗口打开且没有启动报错。 -2. 打开设置页,修改一个可持久化项,例如字体大小或通知开关,确认界面立即刷新。 -3. 完全退出应用后重新启动,确认刚才的设置仍然保留。 -4. 创建一个普通会话,发送一条短消息,确认用户消息和助手消息顺序正确。 -5. 在回答流式生成过程中确认增量内容持续追加,然后执行一次 stop/cancel。 -6. 重新打开刚才的会话,确认会话列表、消息历史和当前活跃会话恢复正常。 -7. 打开 provider 设置并执行一次 `test connection`,确认成功或失败信息能返回到界面。 -8. 触发一次权限或问题交互,确认通过 overlay 响应后 overlay 消失,消息列表继续刷新。 - -## Exit Evidence Per Phase - -每个阶段完成时,应附带: - -- 通过的命令列表 -- 新增或修改的测试列表 -- 手工 smoke 结论 -- 更新后的 baseline 摘要 -- bridge register 更新结果 -- scoreboard 更新结果 - -## Final Regression Gate - -在 Phase 5 之前,必须至少完成一次覆盖以下能力的综合回归: - -- settings read/write -- create session -- restore session -- send message -- stream reply -- stop stream -- provider or permission interaction -- This regression gate was satisfied on `2026-04-20` by the Phase 5 targeted migrated-path suites listed above. - diff --git a/docs/architecture/new-ui-implementation-plan.md b/docs/architecture/new-ui-implementation-plan.md deleted file mode 100644 index b03c83387..000000000 --- a/docs/architecture/new-ui-implementation-plan.md +++ /dev/null @@ -1,619 +0,0 @@ -# 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. - ---- - -## 1. Architecture Overview - -### 1.1 Target Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ ChatTabView (Entry) │ -│ ┌─────────────────────────────────────────────────────────┐│ -│ │ WelcomePage │ Displayed when no Provider config ││ -│ ├─────────────────────────────────────────────────────────┤│ -│ │ NewThreadPage │ Displayed when creating new session││ -│ ├─────────────────────────────────────────────────────────┤│ -│ │ ChatPage │ Displayed during active session ││ -│ └─────────────────────────────────────────────────────────┘│ -│ ┌─────────────────────────────────────────────────────────┐│ -│ │ WindowSideBar │ Agent filter + Session list ││ -│ └─────────────────────────────────────────────────────────┘│ -└─────────────────────────────────────────────────────────────┘ -``` - -### 1.2 Core Principles - -1. **Unidirectional Data Flow**: Store → Composable → Component -2. **State Machine Driven**: Page transitions controlled by PageState state machine -3. **Presenter Pattern**: All business logic handled through Main process Presenters -4. **Reactive Design**: Components only handle UI rendering, state managed by Stores - ---- - -## 2. Directory Structure - -``` -src/renderer/src/ -├── views/ -│ └── ChatTabView.vue # Page entry, state machine logic -├── pages/ # Page components (new directory) -│ ├── WelcomePage.vue # Welcome page -│ ├── NewThreadPage.vue # New thread page -│ └── ChatPage.vue # Chat page -├── components/ -│ ├── sidebar/ # Sidebar components (new directory) -│ │ ├── AgentFilter.vue # Agent filter -│ │ ├── SessionGroup.vue # Session group -│ │ ├── SessionItem.vue # Session item -│ │ └── SidebarActions.vue # Sidebar action buttons -│ ├── chat/ # Chat related components (new directory) -│ │ ├── ChatTopBar.vue # Chat top bar -│ │ ├── MessageList.vue # Message list -│ │ ├── MessageItem.vue # Message item -│ │ ├── ChatInput.vue # Input box -│ │ ├── InputToolbar.vue # Input toolbar -│ │ └── ChatStatusBar.vue # Status bar -│ └── common/ # Common components -│ ├── ProjectSelector.vue # Project selector -│ ├── ModelSelector.vue # Model selector -│ └── PermissionSelector.vue # Permission selector -├── stores/ -│ ├── ui/ # UI related Stores (new directory) -│ │ ├── pageState.ts # Page state machine -│ │ ├── agent.ts # Agent management -│ │ ├── session.ts # Session management -│ │ └── project.ts # Project management -│ └── chat.ts # Retained, message related logic -├── composables/ -│ ├── usePageState.ts # Page state management -│ ├── useAgentFilter.ts # Agent filter logic -│ ├── useSessionGroup.ts # Session grouping logic -│ └── useProjectRecent.ts # Recent projects logic -└── types/ - └── ui.ts # UI type definitions -``` - ---- - -## 3. State Management Layer - -### 3.1 Page State Machine (pageState.ts) - -**Responsibility**: Manage global page state, control page transition logic - -**State Definition**: -```typescript -type PageState = - | { type: 'welcome' } - | { type: 'newThread' } - | { type: 'chat'; sessionId: string } -``` - -**State Transition Triggers**: -- On startup, check Provider config → welcome / newThread -- Create session → chat -- Switch session → chat (update sessionId) -- Close session → newThread -- Delete last Provider → welcome - -**Implementation Points**: -- Use Pinia store to manage state -- Provide `transitionTo(state)` method -- Provide `initialize()` method for startup initialization - -### 3.2 Agent Store (agent.ts) - -**Responsibility**: Manage Agent list and Agent filter state - -**Data Structure**: -```typescript -interface Agent { - id: string - name: string - type: 'deepchat' | 'acp' - enabled: boolean - icon?: string -} - -interface AgentState { - agents: Agent[] - selectedAgentId: string | null // null means "All Agents" - loading: boolean -} -``` - -**Data Sources**: -- DeepChat Agent: Always exists -- ACP Agents: Get from `configPresenter.getAcpAgents()` - -### 3.3 Session Store (session.ts) - -**Responsibility**: Manage session list and session grouping - -**Data Structure**: -```typescript -interface Session { - id: string - title: string - agentId: string - status: 'completed' | 'working' | 'error' | 'none' - projectDir: string - providerId?: string - modelId?: string - activeSkills?: string[] - createdAt: number - updatedAt: number -} - -type SessionGroup = - | { type: 'time'; label: string; sessions: Session[] } - | { type: 'project'; project: Project; sessions: Session[] } - -interface SessionState { - sessions: Session[] - activeSessionId: string | null - groupByProject: boolean - loading: boolean -} -``` - -**Key Actions**: -- `fetchSessions()`: Get session list from sessionPresenter -- `createSession(settings)`: Create new session -- `selectSession(id)`: Select session -- `closeSession()`: Close current session -- `toggleGroupMode()`: Toggle grouping mode - -### 3.4 Project Store (project.ts) - -**Responsibility**: Manage recent project list - -**Data Structure**: -```typescript -interface Project { - path: string - name: string - lastAccessedAt: number - sessionCount: number -} - -interface ProjectState { - projects: Project[] - loading: boolean -} -``` - -**Data Sources**: -- Aggregated from session list -- Support manual addition via folder picker - ---- - -## 4. Page Component Layer - -### 4.1 ChatTabView.vue - -**Responsibility**: Page entry, render corresponding page based on PageState - -**Implementation**: -```vue - -``` - -**Initialization Flow**: -1. Call `pageState.initialize()` on component mount -2. Check Provider configuration -3. Check if there's an active session -4. Determine initial page state - -### 4.2 WelcomePage.vue - -**Responsibility**: Guide user to configure first Provider - -**Data Dependencies**: -- ProviderStore: Get recommended Provider list - -**Interactions**: -- Click Provider → Call `windowPresenter.openOrFocusSettingsTab()` -- Click ACP Agent entry → Same as above - -### 4.3 NewThreadPage.vue - -**Responsibility**: New session entry - -**Data Dependencies**: -- ProjectStore: Recent project list -- AgentStore: Agent selection -- ModelStore: Model selection (DeepChat Agent only) - -**Subcomponents**: -- ProjectSelector: Project/folder selection -- ChatInput: Message input -- ChatStatusBar: Model/permission configuration - -**Interactions**: -- Send message → Call `sessionStore.createSession()` → Page transitions to ChatPage - -### 4.4 ChatPage.vue - -**Responsibility**: Main interface during active session - -**Data Dependencies**: -- SessionStore: Current session info -- ChatStore: Message list - -**Subcomponents**: -- ChatTopBar: Title, project, share, more actions -- MessageList: Message rendering -- ChatInput: Message input -- ChatStatusBar: Current configuration display - ---- - -## 5. Sidebar Component Layer - -### 5.1 WindowSideBar.vue Refactoring - -**Structure**: -``` -┌─────────────────────────────────┐ -│ AgentFilter (icon column) │ -│ - All Agents │ -│ - DeepChat │ -│ - Claude Code │ -│ - ... │ -├─────────────────────────────────┤ -│ SessionList │ -│ - SessionGroup (Today) │ -│ - SessionItem │ -│ - SessionItem │ -│ - SessionGroup (Yesterday) │ -│ - SessionItem │ -├─────────────────────────────────┤ -│ SidebarActions │ -│ - New Chat │ -│ - Toggle Group Mode │ -│ - Collapse │ -│ - Browser │ -│ - Settings │ -└─────────────────────────────────┘ -``` - -**Implementation**: -- Split into independent subcomponents -- Share state via composables -- Support collapsed/expanded modes - -### 5.2 AgentFilter.vue - -**Responsibility**: Agent icon list, filter sessions - -**Props**: None (get data from AgentStore) - -**Events**: -- `@select`: Triggered when Agent is selected - -### 5.3 SessionGroup.vue - -**Responsibility**: Session group display - -**Props**: -- `group: SessionGroup`: Group data - -**Slots**: -- `default`: SessionItem rendering - -### 5.4 SessionItem.vue - -**Responsibility**: Single session item rendering - -**Props**: -- `session: Session`: Session data -- `active: boolean`: Whether active - -**Events**: -- `@click`: Click session - ---- - -## 6. Chat Component Layer - -### 6.1 ChatTopBar.vue - -**Responsibility**: Chat top info bar - -**Props**: -- `title: string`: Session title -- `projectPath: string`: Project path - -**Slots**: -- Right action buttons area - -### 6.2 MessageList.vue - -**Responsibility**: Message list rendering - -**Key Features**: -- Virtual scrolling support (large message count optimization) -- Scroll to specific message -- Message preloading - -**Implementation Points**: -- Reuse core logic from existing `MessageList.vue` -- Adapt to new data structures - -### 6.3 ChatInput.vue - -**Responsibility**: Message input area - -**Key Features**: -- @ mention files -- / commands -- Multi-line input -- Attachment upload - -**Implementation Points**: -- Reuse existing `ChatInput.vue` component -- Adapt to new page structure - ---- - -## 7. Development Phases - -### Phase 1: Basic Framework (1-2 weeks) - -**Goal**: Build page skeleton and state management - -**Tasks**: -1. Create directory structure -2. Implement `pageState.ts` state machine -3. Implement `agent.ts` Store -4. Implement basic `session.ts` Store structure -5. Refactor `ChatTabView.vue` page switching logic -6. Create `WelcomePage.vue` static page - -**Acceptance Criteria**: -- Pages correctly display Welcome / NewThread / Chat states -- State transition logic is correct - -### Phase 2: Sidebar Functionality (1-2 weeks) - -**Goal**: Complete sidebar interaction - -**Tasks**: -1. Refactor `WindowSideBar.vue` -2. Implement `AgentFilter.vue` -3. Implement `SessionGroup.vue` / `SessionItem.vue` -4. Implement `useSessionGroup.ts` grouping logic -5. Implement `project.ts` Store -6. Implement `SidebarActions.vue` - -**Acceptance Criteria**: -- Sidebar correctly displays Agent filter -- Session list grouped by time/project -- Clicking session correctly switches page - -### Phase 3: NewThread Page (1 week) - -**Goal**: Complete new session functionality - -**Tasks**: -1. Implement `ProjectSelector.vue` -2. Implement permission selector -3. Integrate ChatInput component -4. Implement session creation logic - -**Acceptance Criteria**: -- Can select project/folder -- Can select Agent and model -- Session is created and navigates correctly after sending message - -### Phase 4: Chat Page (1-2 weeks) - -**Goal**: Complete session interaction - -**Tasks**: -1. Implement `ChatTopBar.vue` -2. Integrate existing `MessageList.vue` -3. Integrate existing `ChatInput.vue` -4. Implement `ChatStatusBar.vue` -5. Implement message send/receive logic - -**Acceptance Criteria**: -- Chat page correctly displays title and project -- Message list renders correctly -- Can send and receive messages - -### Phase 5: Optimization & Polish (1 week) - -**Goal**: Performance optimization and detail refinement - -**Tasks**: -1. Performance optimization (virtual scrolling, lazy loading) -2. Animation transition effects -3. Error handling and edge cases -4. Internationalization support - ---- - -## 8. Data Flow Design - -### 8.1 Initialization Flow - -``` -App Mounted - │ - ▼ -pageState.initialize() - │ - ├── providerStore.hasEnabledProviders()? - │ │ - │ ├── No → transitionTo('welcome') - │ │ - │ └── Yes ↓ - │ - ├── sessionStore.hasActiveSession()? - │ │ - │ ├── Yes → transitionTo('chat', sessionId) - │ │ - │ └── No → transitionTo('newThread') - │ - └── agentStore.fetchAgents() - projectStore.fetchProjects() -``` - -### 8.2 Create Session Flow - -``` -NewThreadPage: User sends message - │ - ▼ -sessionStore.createSession(settings) - │ - ├── Call sessionPresenter.createConversation() - │ - ├── Update local sessions list - │ - └── pageState.transitionTo('chat', newSessionId) - │ - ▼ - ChatPage renders - │ - ▼ - agentPresenter.sendMessage() -``` - -### 8.3 Session Switch Flow - -``` -Sidebar: Click session item - │ - ▼ -sessionStore.selectSession(id) - │ - ├── Call sessionPresenter.setActiveConversation() - │ - └── pageState.transitionTo('chat', id) -``` - ---- - -## 9. Relationship with Existing Code - -### 9.1 Reusable Components - -| Component | Reuse Level | Notes | -|-----------|-------------|-------| -| ChatInput.vue | High reuse | Core input logic unchanged, adapt to new structure | -| MessageList.vue | High reuse | Message rendering logic unchanged | -| MessageItem*.vue | High reuse | Message item components unchanged | -| MarkdownRenderer.vue | Full reuse | No modification needed | -| Artifact*.vue | Full reuse | No modification needed | - -### 9.2 Reusable Stores - -| Store | Reuse Level | Notes | -|-------|-------------|-------| -| chat.ts | Partial reuse | Message logic retained, session management migrated to new session.ts | -| providerStore.ts | Full reuse | No modification needed | -| modelStore.ts | Full reuse | No modification needed | - -### 9.3 New vs Modified - -**New**: -- `stores/ui/pageState.ts` -- `stores/ui/agent.ts` -- `stores/ui/session.ts` -- `stores/ui/project.ts` -- `pages/*.vue` -- `components/sidebar/*.vue` -- `components/chat/*.vue` (partial) - -**Modified**: -- `ChatTabView.vue`: Refactor page switching logic -- `WindowSideBar.vue`: Refactor sidebar structure -- `chat.ts`: Remove session management logic, keep message logic - -**Deprecated**: -- `components/mock/*.vue`: Removed after the new UI rollout, only historical docs remain -- `composables/useMockViewState.ts`: Removed after stores took over the state flow - ---- - -## 10. Testing Strategy - -### 10.1 Unit Tests - -**Store Tests**: -- `pageState.ts`: State transition logic -- `session.ts`: Session CRUD operations -- `agent.ts`: Agent filter logic - -**Composable Tests**: -- `useSessionGroup.ts`: Grouping calculation logic - -### 10.2 Component Tests - -- `WelcomePage.vue`: Snapshot test -- `NewThreadPage.vue`: Interaction test -- `ChatPage.vue`: Interaction test -- `SessionItem.vue`: Render test - -### 10.3 Integration Tests - -- Complete session creation flow -- Session switch flow -- Page state transition flow - ---- - -## 11. Risks and Considerations - -### 11.1 Risk Points - -| Risk | Impact | Mitigation | -|------|--------|------------| -| Increased state management complexity | Medium | Use Pinia devtools for debugging | -| Page switching performance | Low | Use keep-alive to cache page state | -| Conflict with existing code | Medium | Create new files, gradual migration | -| Missing i18n | Low | Add i18n keys during development | - -### 11.2 Considerations - -1. **Progressive Migration**: Build new structure first, then gradually migrate functionality -2. **Maintain Compatibility**: Keep old UI available until new UI is complete -3. **Performance First**: Use virtual scrolling for large lists -4. **Type Safety**: Use TypeScript strict mode for all new code - ---- - -## 12. Summary - -This plan is based on the product architecture defined in `ui-architecture.md`, using a clear layered design: - -1. **State Layer**: 4 core Stores managing page, Agent, session, and project states -2. **Page Layer**: 3 page states, driven by state machine transitions -3. **Component Layer**: Fine-grained component splitting for sidebar and chat areas - -Development cycle estimated at 6-8 weeks, implemented progressively in 5 phases. diff --git a/docs/architecture/remove-rebrand-tool/plan.md b/docs/architecture/remove-rebrand-tool/plan.md deleted file mode 100644 index 7a42049ec..000000000 --- a/docs/architecture/remove-rebrand-tool/plan.md +++ /dev/null @@ -1,20 +0,0 @@ -# Remove Rebrand Tool Plan - -## Approach - -- Keep this SDD folder as the decision record for removing the deprecated tooling. -- Delete the orphaned rebrand script, brand config template, example brand config, and brand asset - placeholder. -- Leave `package.json`, Electron builder configuration, runtime resources, and application metadata - untouched because no package script currently exposes the rebrand path. - -## Compatibility - -- The only removed interface is direct ad hoc execution of `node scripts/rebrand.js`. -- No stored user data, app configuration, IPC contract, or build artifact schema changes. - -## Validation - -- Verify no references remain outside this SDD record with a repository search for `rebrand`, - `brand-assets`, and `brand-config`. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/architecture/remove-rebrand-tool/spec.md b/docs/architecture/remove-rebrand-tool/spec.md deleted file mode 100644 index bcef04271..000000000 --- a/docs/architecture/remove-rebrand-tool/spec.md +++ /dev/null @@ -1,27 +0,0 @@ -# Remove Rebrand Tool - -## Goal - -Remove the deprecated repository-local rebrand tooling so the project no longer carries an -unmaintained brand replacement path. - -## Acceptance Criteria - -- `scripts/rebrand.js` is removed. -- `brand-config.template.json` and `brand-config.example-banana.json` are removed. -- `scripts/brand-assets/` no longer has a tracked placeholder file. -- No code, build, package script, or documentation outside this SDD record references the removed - rebrand assets. -- Runtime app behavior, branding, build configuration, IPC, config storage, and i18n output remain - unchanged. - -## Non-Goals - -- Do not replace the rebrand tool with a new white-labeling mechanism. -- Do not alter current DeepChat product metadata, icons, logos, updater settings, or application - resources. -- Do not add migrations or compatibility shims for direct `node scripts/rebrand.js` use. - -## Open Questions - -None. diff --git a/docs/architecture/remove-rebrand-tool/tasks.md b/docs/architecture/remove-rebrand-tool/tasks.md deleted file mode 100644 index f5ed76e92..000000000 --- a/docs/architecture/remove-rebrand-tool/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Remove Rebrand Tool Tasks - -- [x] Confirm current references are limited to the rebrand assets. -- [x] Add minimal SDD documentation for the removal. -- [x] Remove the rebrand script, brand configs, and brand asset placeholder. -- [x] Verify no non-SDD references remain. -- [x] Run required formatting, i18n, and lint checks. diff --git a/docs/architecture/renderer-main-single-track/plan.md b/docs/architecture/renderer-main-single-track/plan.md deleted file mode 100644 index 246ddaba0..000000000 --- a/docs/architecture/renderer-main-single-track/plan.md +++ /dev/null @@ -1,468 +0,0 @@ -# Renderer-Main Single Track Plan - -## Planning Goal - -本计划解决的是一个非常具体的问题: - -`main kernel refactor` 已经把 renderer-main 主边界做成了可迁移、可测试、可扩展的 typed path, -但 renderer 业务层还没有真正切到单轨。 - -因此,这一轮计划的目标不是继续“重构 main”,而是: - -- 先把 renderer 业务层的 transport 心智模型收成一条 -- 再按 capability family 分批迁掉剩余 legacy 调用 -- 最后用 guard + docs + merge gate 固化规则 - -## Baseline Snapshot - -以下基线来自 `2026-04-20` 当前仓库扫描: - -| Metric | Value | Meaning | -| --- | --- | --- | -| `renderer.usePresenter.count` | `86` | 业务代码仍直接知道 presenter naming | -| `renderer.windowElectron.count` | `95` | 业务代码仍直接知道 Electron IPC | -| `renderer.windowApi.count` | `33` | 业务代码仍直接知道 legacy preload multi-entry | -| `hotpath.presenterEdge.count` | `10` | main hot path 已明显收口,但 renderer 入口仍不单轨 | -| `bridge.active.count` | `0` | `main kernel refactor` 已经没有过渡 bridge 残留 | - -这说明当前分支的主要风险已经不在 main hot path,而在 renderer 开发入口仍然模糊。 - -说明:`renderer.usePresenter.count` 保留旧 metric id,用于连续追踪当前 -legacy presenter helper 的剩余触点。 - -## Current Presenter Hotspots - -按 `useLegacyPresenter()` 名称分布,当前主要热点为: - -| Legacy Surface | Current Hits | Target Single-Track Surface | Priority | -| --- | --- | --- | --- | -| `configPresenter` | `26` | 扩展 `SettingsClient`,并补 `ConfigClient` / provider-model typed contracts | P2 | -| `agentSessionPresenter` | `13` | 扩展 `SessionClient` 覆盖 session action / pending-input / export 等能力 | P4 | -| `windowPresenter` | `9` | `WindowClient` + typed window/system events | P3 | -| `devicePresenter` | `8` | `DeviceClient` + runtime wrappers | P3 | -| `workspacePresenter` | `5` | `WorkspaceClient` | P3 | -| `llmproviderPresenter` | `4` | 扩展 `ProviderClient` 或补 `ModelClient` | P2 / P4 | -| `skillPresenter` | `4` | `SkillClient` | P4 | -| `filePresenter` | `2` | `FileClient` | P3 | -| `mcpPresenter` | `2` | `McpClient` + typed events | P4 | -| `projectPresenter` | `2` | `ProjectClient` | P3 | -| `tabPresenter` | `2` | `TabClient` 或并入 window runtime layer | P3 | -| `yoBrowserPresenter` | `2` | `BrowserClient` | P3 | -| others | `1` each | 对应 typed client / runtime wrapper | P4 | - -这决定了迁移顺序不应该是“86 个点逐个改”,而应该按 capability family 收口。 - -## Handoff Decision - -本计划的关键决策如下: - -### 1. Merge Gate Before Branch Merge - -当前双轨状态不作为最终可合并状态。 - -合并前必须先完成 renderer 业务层单轨化,而不是把“后面继续慢慢迁”当作默认路径。 - -### 2. `useLegacyPresenter()` Downgraded To Internal Compatibility Utility - -`useLegacyPresenter()` 不再是新功能入口。 - -在计划完成前,它最多只能存在于 quarantine adapter 内部,不能再被 `src/renderer/src/**` 业务模块直接 import。 - -### 3. Business Layer Must Not See Raw Electron IPC - -`window.electron` 和 `window.api` 只能存在于极少数 bridge / runtime wrapper。 - -业务模块只允许看到: - -- typed client -- typed event subscription helper -- 明确命名的 runtime service - -### 4. No Mixed Transport Per Module - -如果某个模块已经开始用 typed client,就不能再保留 presenter / raw IPC 调用。 - -允许短期 mixed mode 的唯一位置,是显式 quarantine adapter。 - -## Target State - -目标态如下: - -```text -renderer component / store / page / composable - -> domain-level client or runtime wrapper - -> src/renderer/api/*Client - -> window.deepchat - -> shared/contracts/routes + shared/contracts/events - -> src/main/routes/* - -> hot path ports / presenters / runtime internals -``` - -legacy transport 的唯一允许形态: - -```text -temporary quarantine adapter - -> useLegacyPresenter() or raw window.electron / window.api -``` - -并且 quarantine adapter 不允许被视为“长期公共 API”。 - -## Allowed Public Surfaces - -计划完成后,renderer 对 main 的公开入口只允许是: - -- `src/renderer/api/*Client` -- typed event subscription API -- 明确命名的 runtime wrapper,例如 window context / device context / shell integration wrapper - -以下实现细节只能留在 wrapper / adapter 层: - -- `window.deepchat` -- `window.electron` -- `window.api` -- presenter reflection transport - -## Forbidden Surfaces - -以下行为在本计划中视为禁止: - -1. 在 `src/renderer/src/**` 新增 `useLegacyPresenter()` import -2. 在 `src/renderer/src/**` 新增 `window.electron.*` -3. 在 `src/renderer/src/**` 新增 `window.api.*` -4. 在同一个业务模块内混用 typed client 与 legacy transport -5. 用新的 generic helper 再包一层 presenter reflection,表面看像 typed helper,实质仍走旧协议 - -## Quarantine Model - -为了避免“一边迁,一边到处混用”,本计划要求先建立 quarantine 规则: - -- 业务层:`src/renderer/src/**` -- typed boundary 层:`src/renderer/api/**` -- temporary quarantine 层:固定为 `src/renderer/api/legacy/**` - -规则: - -- 业务层只能 import typed boundary 层 -- quarantine 层可以暂时调用 legacy transport -- 任何 legacy transport 都不得继续散落在业务层 - -补充规则: - -- 不允许再创建第二个 quarantine 目录,例如 `compat/`、`legacy2/`、`v1/` -- 任何 quarantine 文件都必须是 capability adapter,不允许在其中继续长业务状态管理逻辑 -- `P0` 完成前不进入 `P1` - -## Client Boundary Decision Rules - -为避免后续围绕“该扩展旧 client 还是新建 client”反复争论,本计划定义以下规则: - -### Rule 1: Same Capability Family, Same Owner, Same Event Domain -> Extend - -如果某能力同时满足: - -- 属于同一 capability family -- 主要由同一 main route/service owner 承载 -- 使用同一组 typed route / typed event 契约 - -则优先扩展现有 client。 - -例子: - -- `SessionClient` 扩展 session rename / delete / export / pending-input -- `ProviderClient` 扩展 provider query / validation / provider-level mutations - -### Rule 2: New Capability Family Or Different Event Semantics -> New Client - -如果某能力满足以下任一条件,则优先新建 client: - -- 与现有 client 不属于同一 capability family -- 需要独立的 typed event contract family -- 生命周期、权限模型或调用频率与现有 client 明显不同 -- 将其塞进现有 client 会让该 client 同时承担多个不相干 owner - -例子: - -- window/system navigation 与 provider config 不应共用同一个 client -- workspace file tree 与 chat/session action 不应共用同一个 client - -### Rule 3: Config Is Not A Dumping Ground - -`SettingsClient` 只承载真正属于 settings/settings snapshot 的能力。 - -如果某能力虽然历史上挂在 `configPresenter` 上,但语义上属于 provider/model/agent/workspace 域, -应迁移到对应 domain client,而不是继续往 `SettingsClient` 塞。 - -### Rule 4: Transport Wrapper Is Not A Client - -如果某模块只是为了封装 `window.deepchat` / `window.electron` / `window.api` 的调用细节, -但没有 capability-level 语义,就应视为 runtime wrapper 或 adapter,不应伪装成 domain client。 - -## Typed Event Ownership - -typed event contract 的建立不是隐含工作,而是每个 phase 的显式交付物。 - -规则如下: - -- 迁移哪个 capability family,哪个 phase 就负责定义该 family 的 typed events -- 不允许先删 raw listener,再把 typed event 留给“后面顺手补” -- client、event contract、store subscription 改造必须同 phase 收口 - -归属划分: - -| Phase | Typed Event Ownership | -| --- | --- | -| `P2` | config / provider / model family typed events | -| `P3` | window / device / workspace / browser / project family typed events | -| `P4` | session residual / skill / mcp / sync / upgrade / dialog family typed events | - -## Rollback / Fallback Rule - -如果某个 capability family 在迁移中被证明无法在当前 phase 完成 cutover: - -1. 不允许把半完成 mixed transport 直接留在业务模块 -2. 必须回退到: - - 原有单一 legacy 路径,或 - - quarantine adapter 中的单一路径 -3. 必须在 `tasks.md` 中记录 blocked reason 和下一阶段 owner -4. 不允许以“先保留双轨,后面再说”作为默认 fallback - -## Phase Map - -```text -P0 Rules & Guard Hardening - -> P1 Transport Consolidation - -> P2 Config / Provider / Model Family - -> P3 Window / Device / Workspace Family - -> P4 Session Residual / MCP / Skill / Misc Family - -> P5 Retirement, Docs, Merge Gate -``` - -## Phase Details - -### P0: Rules & Guard Hardening - -目标: - -- 把“单轨化”从目标口号变成强约束 -- 让后续改动不能再回流到业务层 legacy transport - -交付物: - -- single-track spec / plan / tasks -- 更新 `docs/README.md`、`docs/ARCHITECTURE.md`、`docs/spec-driven-dev.md`、`docs/guides/getting-started.md` -- `architecture-guard` 从“防增长”升级为“业务层禁用 + quarantine 白名单” -- baseline 报告增加 business-layer / quarantine-layer 维度 -- 固定唯一 quarantine 目录:`src/renderer/api/legacy/**` - -退出条件: - -- 新功能无法再在业务层直接新增 `useLegacyPresenter()` 或 raw IPC -- 入口文档已经明确 single-track 规则 -- `P1` 所需的 quarantine 输出已经存在且路径固定 - -### P1: Transport Consolidation - -目标: - -- 先把 transport helper 自己做成单轨 -- 移除“helper 名字变了,但底层还是 presenter reflection”的伪单轨 - -交付物: - -- `useLegacyPresenter()` 迁入 internal compatibility transport,或降级为 quarantine-only utility -- `src/renderer/api/legacy/` 目录实际建立并承接 legacy transport -- `useIpcQuery` / `useIpcMutation` 改为: - - 面向 typed client 的 helper,或 - - 直接退役 -- `window.api` / `window.electron` 相关 runtime access 收口到专用 wrapper -- 业务层停止直接 import transport primitive - -退出条件: - -- `src/renderer/src/**` 不再 direct import `@api/legacy/presenters` -- mixed transport module 被消除 - -### P2: Config / Provider / Model Family - -目标: - -- 先清掉最大头的 `configPresenter` 系列调用 -- 把 provider / model / config 相关能力全部收成 typed client - -当前状态(`2026-04-20`): - -- 已完成 -- `src/renderer/src/**` 中 `configPresenter` / `llmproviderPresenter` 的 P2 业务命中已清零 -- provider / model / config family 已补齐 typed contracts、typed events、typed clients,并完成相关 store / page / component 迁移 -- 相关 renderer/main 定向测试,以及 `pnpm run typecheck`、`pnpm run format`、`pnpm run i18n`、`pnpm run lint` 已通过 -- 验收补丁已对齐 `config.resolveDeepChatAgentConfig` 的 nullable agent config contract,避免 legacy persisted config 在单轨 route 出口被误拒绝 - -交付物: - -- 扩展 `SettingsClient` -- 补 `ConfigClient`、`ProviderClient`、必要时补 `ModelClient` -- provider / model / theme / language / system prompt / floating button / shortcut 相关 typed contracts 和 typed events -- 清理 `providerStore`、`modelStore`、`modelConfigStore`、`systemPromptStore`、`themeStore`、`languageStore`、`shortcutKey`、`floatingButton`、`agentModelStore` - -退出条件: - -- `configPresenter` 和 `llmproviderPresenter` 不再出现在 `src/renderer/src/**` 业务代码 -- provider family 的事件监听改为 typed event subscription - -### P3: Window / Device / Workspace Family - -目标: - -- 清掉第二批最容易让开发者“顺手继续写 raw IPC”的能力 - -当前状态(`2026-04-20`): - -- 已完成 -- 已补齐 `WindowClient`、`DeviceClient`、`WorkspaceClient`、`ProjectClient`、`FileClient`、`BrowserClient`、`TabClient`,并通过 `src/renderer/api/runtime.ts` 收口唯一 `window.api` typed runtime wrapper -- 已补齐 window / workspace / browser family typed events,以及对应 shared route / event contracts 和 main route runtime 分发 -- `App.vue`、`AppBar.vue`、`stores/ui/project.ts`、workspace / browser / project / file / device 相关组件与 composables 已完成 cutover -- `WelcomePage.vue` 与 `NewThreadPage.vue` 已完成 P3 范围审计:前者无需新增改动,后者仅残留 `agentSessionPresenter` 的 P4 scope 调用,不再包含 P3 family transport -- 业务层 `windowPresenter` / `devicePresenter` / `workspacePresenter` / `projectPresenter` / `filePresenter` / `yoBrowserPresenter` / `tabPresenter` 命中已清零,`src/renderer/src/**` 中不再 direct 使用 window/window-tab 相关 raw IPC -- P3 定向 main / renderer 自动回归,以及 `pnpm run format`、`pnpm run i18n`、`pnpm run lint`、`pnpm run typecheck` 已通过;后续仅剩 P4 residual family 清理 - -交付物: - -- `WindowClient` -- `DeviceClient` -- `WorkspaceClient` -- `ProjectClient` -- `FileClient` -- `BrowserClient` -- 必要时补 `TabClient` 或 window runtime adapter -- window / device / workspace family typed events -- 清理 `App.vue`、`AppBar.vue`、`WelcomePage.vue`、`NewThreadPage.vue`、workspace / browser / project 相关 stores 与组件 - -退出条件: - -- `windowPresenter`、`devicePresenter`、`workspacePresenter`、`projectPresenter`、`filePresenter`、`yoBrowserPresenter`、`tabPresenter` - 不再出现在业务代码 -- `src/renderer/src/**` 不再 direct 使用 window/window-tab 相关 raw IPC - -### P4: Session Residual / MCP / Skill / Misc Family - -目标: - -- 收掉剩余 presenter family -- 把“已经有 typed session/chat 主路径,但残余动作还走 presenter”这种半迁移状态补齐 - -当前状态(`2026-04-20`): - -- 已完成 -- 已扩展 `SessionClient` 覆盖 rename / delete / export / pending-input / search / translate / session setting / ACP session config 等 residual 动作,并补齐 `SkillClient`、`McpClient`、`SyncClient`、`UpgradeClient`、`DialogClient`、`ToolClient` -- 已补齐 skill / mcp / sync / upgrade / dialog / session residual family 的 typed route、typed event、main dispatcher/runtime 接线,以及 direct `sendToRenderer` 源的 typed event publish -- `stores/ui/session.ts`、`stores/ui/pendingInput.ts`、`stores/skillsStore.ts`、`stores/mcp.ts`、`stores/mcpSampling.ts`、`stores/sync.ts`、`stores/upgrade.ts`、`stores/dialog.ts`、`stores/ollamaStore.ts` 与相关 pages/components/composables 已完成 cutover -- 业务层 `agentSessionPresenter`、`skillPresenter`、`mcpPresenter`、`syncPresenter`、`upgradePresenter`、`dialogPresenter`、`toolPresenter` 命中已清零,P4 family raw listeners 已改为 typed event subscription -- `pnpm run format`、`pnpm run i18n`、`pnpm run lint`、`pnpm run typecheck` 与 P4 定向 main/renderer 自动回归已通过 - -交付物: - -- 扩展 `SessionClient` 覆盖 rename / delete / export / session settings / pending input 等残余动作 -- `SkillClient` -- `McpClient` -- `SyncClient` -- `UpgradeClient` -- `DialogClient` -- 其他低频 capability 的 typed route / typed event -- 清理 `skillsStore`、`mcp.ts`、`mcpSampling.ts`、`sync.ts`、`upgrade.ts`、`dialog.ts`、`ollamaStore` 及相关组件 - -退出条件: - -- `agentSessionPresenter`、`skillPresenter`、`mcpPresenter`、`syncPresenter`、`upgradePresenter`、 - `dialogPresenter`、`toolPresenter` 等残余业务层调用清零 - -### P5: Retirement, Docs, Merge Gate - -目标: - -- 从“迁移进行中”切换到“规则落地完成” - -当前状态(`2026-04-20`): - -- 已完成 -- 旧的通用 `usePresenter()` naming 已退役;remaining legacy presenter entry 仅保留在 `src/renderer/api/legacy/presenters.ts` -- settings compatibility surfaces 继续从 quarantine adapter import;`src/renderer/src/**` 业务层已改为通过明确命名的 runtime wrapper 使用 residual legacy capability,不再直接 import `@api/legacy/presenters` -- `scripts/architecture-guard.mjs` 已补 `renderer-retired-legacy-entry` 与 quarantine `<= 3` source files gate,稳定阻止 shim 回流和 quarantine 膨胀 -- 未使用的 convenience exports 已从 `src/renderer/api/legacy/presenters.ts` 删除,剩余 legacy presenter helper metric 已降到 `renderer.usePresenter = renderer.quarantine.usePresenter = 1` -- architecture baseline / scoreboard 已刷新,`P5` gate 现为 `ready`:business legacy signal `0/0/0`,quarantine source files `3/3` -- active docs 已补充 `P5` 最终状态与 quarantine 导航入口;剩余 quarantine 仅保留 `presenters.ts`、`presenterTransport.ts`、`runtime.ts` - -交付物: - -- `useLegacyPresenter()` internal-only 或完全删除 -- `window.electron` / `window.api` 只存在于文档明确列出的 bridge / runtime wrapper -- 刷新基线、任务状态、代码导航和 onboarding 文档 -- 最终 merge gate checklist - -退出条件: - -- 业务层 single-track 达成 -- quarantine 范围可审计且足够小,或已经清零 -- reviewer 可以不靠口头说明,只看文档和 guard 就判定合规性 - -## Verification Strategy - -所有 phase 都同步执行: - -- guard 校验 -- baseline 报告刷新 -- typed client / typed event 单测 -- 关键 store / page 回归测试 -- 迁移 slice 的 smoke 验证 - -重点不是“迁了多少文件”,而是: - -- 有没有把错误入口真正封死 -- 有没有把业务层 transport 真的收成一条 - -## Parallel Work Policy - -`P0` 与 `P1` 必须串行执行。 - -原因很简单: - -- `P0` 固定规则、guard 和 quarantine 目录 -- `P1` 固定 transport helper 与 quarantine 收口方式 - -这两步没锁定之前,并行切 family 只会制造第二套约定。 - -`P2`、`P3`、`P4` 允许并行,但只能按 capability family 并行,且必须满足: - -1. 每个 family 有唯一 owner -2. 并行 PR 不得同时修改 `architecture-guard`、baseline schema、shared transport primitive -3. 并行 PR 不得争用同一个 typed route / typed event contract 文件 -4. 每个 PR 必须声明自己负责的 capability family 和禁止触碰范围 - -推荐并行粒度: - -- 一条 PR = 一个 capability family -- 一条 PR 同时完成 routes/contracts + client + store cutover + raw listener cleanup - -## Final Merge Gate - -该分支进入主线前,至少满足: - -1. `src/renderer/src/**` direct legacy presenter helper import = `0` -2. `src/renderer/src/**` direct `window.electron` access = `0` -3. `src/renderer/src/**` direct `window.api` access = `0` -4. `useIpcQuery` / `useIpcMutation` 不再依赖 presenter reflection -5. active docs 已经把 typed client / typed event 写成默认路径 -6. `architecture-guard` 可以稳定阻止回退 - -## Risk Notes - -- 最大风险不是迁不完,而是“表面建了 client,业务层还是继续碰 legacy transport”。 -- 第二类风险是把 single-track 做成“把 86 个点一个个改名”,却没有建立 quarantine 和 merge gate。 -- 第三类风险是过于强调一次性清零,反而不先建立 guard,导致迁移中途继续长新 legacy 点。 - -因此,本计划的顺序是: - -1. 先锁规则 -2. 再锁 transport helper -3. 再按 family 清理 -4. 最后再 merge - diff --git a/docs/architecture/renderer-main-single-track/spec.md b/docs/architecture/renderer-main-single-track/spec.md deleted file mode 100644 index d6af0e21f..000000000 --- a/docs/architecture/renderer-main-single-track/spec.md +++ /dev/null @@ -1,198 +0,0 @@ -# Renderer-Main Single Track - -## Summary - -`2026-04-20` main kernel refactor `phase5` 收口之后,项目已经完成了“边界稳定化 + 热路径减耦”的第一轮目标, -但 renderer 侧仍然处于明显的双轨状态: - -- 一部分能力已经通过 `renderer/api/*Client` + `window.deepchat` + shared contracts 进入 main -- 另一部分能力仍然通过 `useLegacyPresenter()`、`window.electron`、`window.api` 直接触达旧兼容面 -- 同一个 store / page / composable 内同时混用两种 transport 和两套 owner 语义 - -这会直接导致后续开发者在实现功能时继续复制“typed client + legacy presenter + raw IPC”混搭模式, -从而让这次重构的收益逐步回退。 - -本计划的目标不是再发起一次全量 `main kernel rewrite`,而是把 renderer-main 边界正式收束成单轨模型, -让后续开发只有一条默认正确路径可走。 - -## Why This Program Exists - -`2026-04-20` 的当前基线仍然显示: - -| Signal | Baseline | Meaning | -| --- | --- | --- | -| `renderer.usePresenter.count` | `86` | renderer 业务代码仍直接依赖 presenter naming | -| `renderer.windowElectron.count` | `95` | renderer 业务代码仍直接感知 Electron IPC | -| `renderer.windowApi.count` | `33` | preload legacy multi-entry surface 仍暴露在业务层 | - -这些数字说明当前分支已经“可运行”,但还没有“可长期维护”。 - -说明:`renderer.usePresenter.count` 保留旧 metric id,用于连续追踪当前 -`useLegacyPresenter()` 与其他 legacy presenter helper 的剩余触点。 - -如果现在在双轨状态下直接合并,后续新功能大概率会继续沿着旧路径长,最终形成: - -- typed client 继续存在 -- `useLegacyPresenter()` 继续存在 -- raw `window.electron` / `window.api` 继续存在 -- 文档、测试和代码审查标准继续模糊 - -这正是本计划要阻止的结果。 - -## Goals - -- 让 `src/renderer/src/**` 的业务代码只面对单一、明确、可追踪的 renderer-main 能力入口。 -- 把 renderer-main 能力边界定义为: - - typed route contracts - - typed event contracts - - `renderer/api/*Client` - - 明确命名的 runtime wrapper -- 把 `useLegacyPresenter()` 从“通用开发入口”降级为“迁移期间的内部兼容工具”,并最终退出业务层。 -- 把 `window.electron` / `window.api` 从业务层清退到极小、可审计、可删除的 wrapper / adapter 范围。 -- 把“新功能如何接入 main”这件事写成强规则,而不是口头共识。 - -## Non-Goals - -- 不再发起一次新的全量 `main kernel` 目录重写。 -- 不要求本轮删除所有 main presenters。 -- 不要求本轮重写全部 EventBus 或全部 preload 兼容层。 -- 不要求所有 legacy 能力在第一阶段立刻消失。 -- 不以“单纯把指标数字降到 0”作为唯一目标;更关键的是消除业务层双轨。 - -## User Stories - -- 作为后续功能开发者,我只需要沿着一条默认边界接入 main,而不是先猜“这个能力该走 client、presenter 还是 raw IPC”。 -- 作为 reviewer,我可以明确判断某个 PR 是否引入了错误 transport,而不是接受“先这样,后面再迁”。 -- 作为维护者,我可以从文档和 guard 直接看出哪些路径已经是 single-track,哪些还处于 quarantine。 -- 作为测试编写者,我可以围绕 typed client、typed event 和 domain adapter 写测试,而不是围绕 presenter 名称反射写测试。 - -## In Scope - -- `src/renderer/src/**` 的 renderer-main 调用方式 -- `renderer/api/*Client` -- typed route / event contract 的继续扩展 -- `useLegacyPresenter()` / `window.electron` / `window.api` 的 quarantine 与退役路径 -- guard、baseline、文档和 merge gate -- 现有 store / page / composable 的 transport 单轨化 - -## Out of Scope - -- main 内部 presenter 是否全部退役 -- main 内部所有 service / port / adapter 的再次大改名 -- 全量 EventBus 重写 -- 不相关的 UI redesign -- 与单轨化无关的性能优化 - -## Definition Of Single Track - -单轨化在本计划中有明确含义,不是“尽量多用 typed client”。 - -### 1. Single Public Surface Per Capability - -同一个 renderer capability 只能有一个公开入口。 - -例子: - -- provider 查询 / 校验统一走 `ProviderClient` -- session 创建 / 激活 / 删除 / 导出统一走 `SessionClient` 的扩展能力或同域 typed client -- window / device / workspace 相关能力统一走对应 typed client 或 runtime wrapper - -### 2. No Mixed Transport In Business Modules - -同一个 `src/renderer/src/**` 模块内,不允许同时出现: - -- typed client -- `useLegacyPresenter()` -- raw `window.electron` -- raw `window.api` - -如果某个能力还没迁完,也必须先收口到单独的 adapter,而不是继续在业务模块里混用。 - -### 3. Legacy Quarantine Only - -legacy transport 只允许存在于显式 quarantine 区域。 - -唯一允许的 quarantine 路径固定为: - -- `src/renderer/api/legacy/**` - -业务代码不能直接 import / 调用 legacy transport helper。 - -### 4. Typed-First For New Work - -后续新增 renderer-main 能力时,必须先定义: - -- route contract 或 event contract -- 对应 client / runtime wrapper -- 对应测试 - -不允许新增 “先走 `useLegacyPresenter()`,以后再迁” 的临时实现。 - -## Acceptance Criteria - -- `src/renderer/src/**` 业务模块中直接 import `@api/legacy/presenters` 的数量降为 `0`。 -- `src/renderer/src/**` 业务模块中 direct `window.electron` 的数量降为 `0`,仅允许文档明确列出的 bridge / runtime wrapper 保留。 -- `src/renderer/src/**` 业务模块中 direct `window.api` 的数量降为 `0`,仅允许文档明确列出的 bridge / runtime wrapper 保留。 -- `useIpcQuery` / `useIpcMutation` 不再建立在 presenter-name / method-name reflection 之上,或被更明确的 typed helper 替代。 -- 已迁移 slice 的业务模块中不再混用 typed client 和 legacy transport。 -- `scripts/architecture-guard.mjs` 能按“业务层禁用 + quarantine 白名单”而不是单纯“防增长”执行检查。 -- `docs/README.md`、`docs/ARCHITECTURE.md`、`docs/spec-driven-dev.md`、`docs/guides/getting-started.md` 等高频入口明确声明 single-track 规则。 -- 合并前存在一份清晰的 merge gate,能让 reviewer 判定“是否允许进入主线”。 - -## Phase Gates - -为了避免 merge gate 只剩最终口号,本计划为每个阶段定义中间达标线: - -| Phase | Gate | -| --- | --- | -| `P0` | quarantine 路径、guard 规则、baseline 维度和 merge gate 已固定成文档与脚本任务 | -| `P1` | `src/renderer/src/**` direct import `@api/legacy/presenters` = `0`,业务层 direct `window.electron` / `window.api` 新增点 = `0`,legacy transport 已收口到 `src/renderer/api/legacy/**` 或 typed runtime wrapper | -| `P2` | business layer `configPresenter` hits = `0`,business layer `llmproviderPresenter` hits = `0`,config/provider/model family 的 raw event listeners 清零 | -| `P3` | business layer `windowPresenter` / `devicePresenter` / `workspacePresenter` / `projectPresenter` / `filePresenter` / `yoBrowserPresenter` / `tabPresenter` hits = `0` | -| `P4` | business layer remaining presenter family hits = `0`,包括 `agentSessionPresenter` / `skillPresenter` / `mcpPresenter` / `syncPresenter` / `upgradePresenter` / `dialogPresenter` / `toolPresenter` 等 | -| `P5` | `src/renderer/src/**` business layer direct legacy presenter helper / direct `window.electron` / direct `window.api` 全部为 `0`,quarantine 目录为空或满足量化退出标准 | - -`2026-04-20` 进度更新:P3 已完成。window / device / workspace / project / file / browser / tab family 已完成 typed contract、typed event、typed client cutover;业务层 P3 presenter hits 已清零,window/window-tab raw IPC 业务直连已清零,相关定向测试与 `format/i18n/lint/typecheck` 已通过。 -`2026-04-20` 审计备注:`WelcomePage.vue` 与 `NewThreadPage.vue` 已在 P3 范围内完成复核,前者无需变更,后者仅保留 `agentSessionPresenter` 的 P4 residual 调用,不再阻塞 P3 gate。 -`2026-04-20` 进度更新:P4 已完成。session residual / skill / mcp / sync / upgrade / dialog / tool family 已完成 typed contract、typed event、typed client cutover;业务层 P4 presenter hits 已清零,相关 raw listeners 已切换到 typed event subscription,且定向 main/renderer 自动回归与 `format/i18n/lint/typecheck` 已通过。 -`2026-04-20` 进度更新:P5 已完成。旧的通用 `usePresenter()` naming 已退役;remaining legacy presenter entry 仅保留在 `src/renderer/api/legacy/presenters.ts`,并通过明确命名的 quarantine helper / runtime wrapper 暴露给兼容路径。`2026-04-20` 补充清理后,未使用的 convenience exports 已从该入口移除,baseline 现显示 `renderer.business.usePresenter/windowElectron/windowApi = 0/0/0`、`renderer.usePresenter = renderer.quarantine.usePresenter = 1`,且 quarantine source files 满足 `3/3` 退出标准,`P5` gate 已转为 `ready`。 - -## Success Metrics - -重点不只是总数下降,而是“业务层清零,兼容层收口”。 - -至少跟踪: - -- `renderer.usePresenter.count` -- `renderer.windowElectron.count` -- `renderer.windowApi.count` -- business-layer direct import / direct access count -- quarantine-layer count -- typed client / typed event 覆盖的 capability 数量 - -## Quarantine Exit Standard - -本计划的理想终点是 quarantine 清零。 - -如果在 `P5` 合并前仍因阻塞性兼容约束需要保留 quarantine,则必须同时满足: - -- `src/renderer/src/**` 业务层 direct legacy access 仍为 `0` -- `src/renderer/api/legacy/**` 文件数 `<= 3` -- 剩余 quarantine 只允许覆盖 `<= 1` 个 capability family -- 每个剩余文件都必须在 `tasks.md` 或后续 follow-up 规格中写明 owner、删除条件和最晚退出阶段 - -如果做不到以上条件,则不满足 single-track merge gate。 - -`2026-04-20` P5 审计结果: - -- 剩余 quarantine source files 为 `presenters.ts`、`presenterTransport.ts`、`runtime.ts` -- 剩余 quarantine capability family 为单一的 `renderer legacy transport` -- 删除条件:settings compatibility surfaces 不再 import `@api/legacy/presenters` / `@api/legacy/runtime` - -## Open Questions - -当前无阻塞性 `[NEEDS CLARIFICATION]` 项。 - -实现层面的命名差异,例如某些能力最终是扩展现有 `Client` 还是拆成新 `Client`, -由 `plan.md` 在不破坏 single-track 原则的前提下决定。 - diff --git a/docs/architecture/renderer-main-single-track/tasks.md b/docs/architecture/renderer-main-single-track/tasks.md deleted file mode 100644 index 7ae271b6d..000000000 --- a/docs/architecture/renderer-main-single-track/tasks.md +++ /dev/null @@ -1,122 +0,0 @@ -# Renderer-Main Single Track Tasks - -## Program Setup - -- [x] 新建 `docs/architecture/renderer-main-single-track/spec.md` -- [x] 新建 `docs/architecture/renderer-main-single-track/plan.md` -- [x] 新建 `docs/architecture/renderer-main-single-track/tasks.md` -- [x] 在 `docs/README.md` 增加 single-track 计划入口 -- [x] 在 `docs/ARCHITECTURE.md` 增加 `phase5` 之后的执行规则 -- [x] 更新 `docs/spec-driven-dev.md`,把 renderer-main 推荐模式从 `useLegacyPresenter()` 改为 typed client -- [x] 更新 `docs/guides/getting-started.md`,把 onboarding 心智模型改为 typed boundary first - -## P0: Rules & Guard Hardening - -- [x] 定义业务层 / typed boundary / quarantine 三层目录规则 -- [x] 固定唯一 quarantine 目录为 `src/renderer/api/legacy/**` -- [x] 在仓库中实际创建 `src/renderer/api/legacy/` 目录与说明文件或首个 adapter -- [x] 为 `scripts/architecture-guard.mjs` 增加 business-layer direct legacy presenter helper 禁止规则 -- [x] 为 `scripts/architecture-guard.mjs` 增加 business-layer direct `window.electron` 禁止规则 -- [x] 为 `scripts/architecture-guard.mjs` 增加 business-layer direct `window.api` 禁止规则 -- [x] 为 `scripts/generate-architecture-baseline.mjs` 增加 business-layer / quarantine-layer 分维度统计 -- [x] 定义 single-track merge gate -- [x] 定义阶段性 phase gate 指标并写入 baseline / guard 说明 - -## P1: Transport Consolidation - -- [x] 依赖 P0 已固定 `src/renderer/api/legacy/**` 后再开始本阶段 -- [x] 把 `useLegacyPresenter()` 降级为 quarantine-only utility -- [x] 在 renderer 层建立显式 legacy quarantine adapter 目录 -- [x] 重写或退役 `useIpcQuery` -- [x] 重写或退役 `useIpcMutation` -- [x] 收口 `window.electron` / `window.api` 的 runtime wrapper -- [x] 清理 `src/renderer/src/**` 中对 transport primitive 的直接 import -- [x] 为 transport consolidation 补验证:业务层 direct legacy presenter helper import = `0` -- [x] 为 transport consolidation 补验证:业务层 mixed transport module = `0` - -## P2: Config / Provider / Model Family - -- [x] 扩展 `SettingsClient` 覆盖仍属于 settings/config 域的基础读写 -- [x] 为 provider / model / config 能力补 typed contracts -- [x] 为 provider / model / config family 补 typed event contracts -- [x] 为 provider / model / config 能力补 typed clients -- [x] 迁移 `providerStore.ts` -- [x] 迁移 `modelStore.ts` -- [x] 迁移 `modelConfigStore.ts` -- [x] 迁移 `systemPromptStore.ts` -- [x] 迁移 `theme.ts` -- [x] 迁移 `language.ts` -- [x] 迁移 `floatingButton.ts` -- [x] 迁移 `shortcutKey.ts` -- [x] 迁移 `agentModelStore.ts` -- [x] 清理 config/provider/model family 的 raw event listeners - -`2026-04-20` 进度更新:P2 已完成,相关 renderer/main 定向测试、`typecheck`、`format`、`i18n`、`lint` 已通过。 -`2026-04-20` 验收修复:补齐 `config.resolveDeepChatAgentConfig` 的 nullable agent config contract 兼容,自动回归已重新通过。 - -## P3: Window / Device / Workspace Family - -- [x] 为 window / device / workspace / project / file / browser / tab 能力补 typed clients 或 runtime wrappers -- [x] 为 window / device / workspace / project / file / browser / tab family 补 typed event contracts -- [x] 迁移 `App.vue` -- [x] 迁移 `AppBar.vue` -- [x] 迁移 `WelcomePage.vue` -- [x] 迁移 `NewThreadPage.vue` -- [x] 迁移 `stores/ui/project.ts` -- [x] 迁移 workspace/browser 相关组件与 composables -- [x] 清理 window/device/workspace family 的 raw event listeners - -`2026-04-20` 进度更新:P3 已完成。新增 `WindowClient`、`DeviceClient`、`WorkspaceClient`、`ProjectClient`、`FileClient`、`BrowserClient`、`TabClient` 与 `src/renderer/api/runtime.ts`,并完成 window / workspace / browser typed event cutover。 -`2026-04-20` 范围备注:`WelcomePage.vue` 为已满足 P3 gate 的既有 typed path,无需改动;`NewThreadPage.vue` 的 P3 范围已完成审计,剩余 legacy 调用仅属于 P4 的 `agentSessionPresenter` residual。 -`2026-04-20` 自动回归:`pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts test/main/presenter/workspacePresenter.test.ts` 与 `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/clients.test.ts test/renderer/stores/projectStore.test.ts test/renderer/components/WorkspacePanel.test.ts test/renderer/components/WindowSideBar.test.ts test/renderer/stores/spotlight.test.ts test/renderer/components/SvgArtifact.test.ts test/renderer/components/AgentWelcomePage.test.ts test/renderer/components/WelcomePage.test.ts test/renderer/components/NewThreadPage.test.ts test/renderer/pages/NewThreadPage.test.ts` 已通过。 -`2026-04-20` 静态检查:`pnpm run format`、`pnpm run i18n`、`pnpm run lint`、`pnpm run typecheck` 已通过。 - -## P4: Session Residual / MCP / Skill / Misc Family - -- [x] 扩展 `SessionClient` 覆盖 rename / delete / export / pending input / session setting 类动作 -- [x] 为 skill / mcp / sync / upgrade / dialog 等能力补 typed contracts -- [x] 为 skill / mcp / sync / upgrade / dialog 等 family 补 typed event contracts -- [x] 为 skill / mcp / sync / upgrade / dialog 等能力补 typed clients -- [x] 迁移 `stores/ui/session.ts` 的 residual presenter calls -- [x] 迁移 `stores/ui/pendingInput.ts` -- [x] 迁移 `stores/skillsStore.ts` -- [x] 迁移 `stores/mcp.ts` -- [x] 迁移 `stores/mcpSampling.ts` -- [x] 迁移 `stores/sync.ts` -- [x] 迁移 `stores/upgrade.ts` -- [x] 迁移 `stores/dialog.ts` -- [x] 迁移 `stores/ollamaStore.ts` -- [x] 清理 residual family 的 raw event listeners - -`2026-04-20` 进度更新:P4 已完成。已补齐 session residual / skill / mcp / sync / upgrade / dialog / tool family 的 typed route 与 typed event contracts、typed clients,以及 main route dispatcher/runtime 接线。 -`2026-04-20` 迁移结果:`src/renderer/src/**` 内 `agentSessionPresenter` / `skillPresenter` / `mcpPresenter` / `syncPresenter` / `upgradePresenter` / `dialogPresenter` / `toolPresenter` 的 P4 业务命中已清零,相关 raw event listeners 已切到 typed subscriptions。 -`2026-04-20` 自动回归:`pnpm run format`、`pnpm run i18n`、`pnpm run lint`、`pnpm run typecheck` 已通过;`pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts test/main/presenter/upgradePresenter.test.ts test/main/presenter/mcpPresenter.test.ts test/main/presenter/mcpPresenter/toolManager.test.ts test/main/presenter/agentRuntimePresenter/pendingInputStore.test.ts test/main/routes/sessionService.test.ts` 与 `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/api/clients.test.ts test/renderer/stores/mcpStore.test.ts test/renderer/stores/mcpSampling.test.ts test/renderer/stores/upgradeStore.test.ts test/renderer/stores/pendingInputStore.test.ts test/renderer/stores/sessionStore.test.ts test/renderer/stores/ollamaStore.test.ts test/renderer/pages/NewThreadPage.test.ts test/renderer/components/ChatStatusBar.test.ts test/renderer/components/message/MessageBlockContent.test.ts test/renderer/components/MarkdownRenderer.test.ts test/renderer/components/TranslatePopup.test.ts test/renderer/components/trace/TraceDialog.test.ts test/renderer/components/McpIndicator.test.ts test/renderer/components/ChatPage.test.ts` 已通过。 - -## P5: Retirement & Merge Gate - -- [x] 清理 `src/renderer/src/**` 剩余 direct legacy presenter helper import -- [x] 清理 `src/renderer/src/**` 剩余 direct `window.electron` access -- [x] 清理 `src/renderer/src/**` 剩余 direct `window.api` access -- [x] 将 `useLegacyPresenter()` internal-only 或删除 -- [x] 更新 `docs/README.md` / `docs/ARCHITECTURE.md` / `docs/guides/code-navigation.md` 的最终状态 -- [x] 修正文档 / 测试 / guard / baseline 中残留的旧 `usePresenter` 命名与扫描规则 -- [x] 刷新 architecture baseline / scoreboard -- [x] 运行 `pnpm run format` -- [x] 运行 `pnpm run i18n` -- [x] 运行 `pnpm run lint` -- [x] 跑针对性 renderer/main 测试并记录结果 - -`2026-04-20` 进度更新:P5 已完成。旧的通用 `usePresenter()` naming 已删除;settings compatibility surfaces 继续从 `src/renderer/api/legacy/presenters.ts` 获取 quarantine-only legacy presenter entry,业务层 residual capability 则改为通过显式 runtime wrapper 访问。 -`2026-04-20` merge gate 更新:`scripts/architecture-guard.mjs` 已新增 retired shim guard 与 quarantine `<= 3` source files 限制;`docs/architecture/baselines/main-kernel-boundary-baseline.md` 现显示 `P5` gate = `ready`,business legacy signal = `0/0/0`,quarantine source files = `3/3`。 -`2026-04-20` 文档刷新:已移除 `src/renderer/api/legacy/presenters.ts` 中未使用的 convenience exports,并重跑 `pnpm run architecture:baseline`;当前 baseline 已更新为 `renderer.usePresenter = renderer.quarantine.usePresenter = 1`。 -`2026-04-20` quarantine 审计:剩余文件固定为 `src/renderer/api/legacy/presenters.ts`、`src/renderer/api/legacy/presenterTransport.ts`、`src/renderer/api/legacy/runtime.ts`;owner 为 renderer legacy transport quarantine,删除条件为 settings compatibility surfaces 不再 import `@api/legacy/presenters` / `@api/legacy/runtime`。 -`2026-04-20` 自动回归:`pnpm run format`、`pnpm run i18n`、`pnpm run lint`、`pnpm run typecheck` 已通过;`pnpm exec vitest --config vitest.config.renderer.ts test/renderer/composables/useLegacyPresenter.test.ts test/renderer/components/SettingsApp.test.ts test/renderer/components/SettingsApp.providerDeeplink.test.ts test/renderer/components/RemoteSettings.test.ts` 与 `pnpm exec vitest --config vitest.config.ts test/main/routes/contracts.test.ts test/main/routes/dispatcher.test.ts` 已通过。 -`2026-04-20` 全量套件尝试:`pnpm test` 当前仍失败,失败面为既有 renderer 测试问题,包含聚合运行中的 `window.deepchat is not available` 测试环境/串扰错误,以及独立存在的 `test/renderer/components/BrowserPanel.test.ts` 断言失败;单独重跑 `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/pages/NewThreadPage.test.ts` 已通过,说明至少 `NewThreadPage` 这类 `window.deepchat` 失败并非本次 P5 改动引入。 - -## Final Checklist - -- [x] renderer 业务层 single-track 达成 -- [x] quarantine 范围明确、可审计 -- [x] 新功能接入规则写入 active docs -- [x] reviewer 无需口头背景即可判定是否合规 - diff --git a/docs/architecture/session-management.md b/docs/architecture/session-management.md index 375a2a961..69e7dd4cc 100644 --- a/docs/architecture/session-management.md +++ b/docs/architecture/session-management.md @@ -80,3 +80,14 @@ sequenceDiagram - renderer `messageStore` 首屏只加载第一页,`ChatPage` 在接近顶部时再拉旧历史 这样可以让大会话恢复保持稳定首屏时间,也把“历史很长”和“首屏可用”两个目标解耦开。 + +## 当前会话能力 + +- 会话列表支持 lightweight 分页、按 agent/project/subagent 过滤、固定字母排序和置顶优先。 +- `generationSettings` 保存在 session runtime 中,renderer 可通过 `sessions.getGenerationSettings` + / `sessions.updateGenerationSettings` 读取和更新。 +- `sessions.compact` 提供手动上下文压缩;自动压缩默认值来自 agent/settings 配置。 +- `sessions.listMessageTraces` 提供消息 trace 查询,不再把 trace 混在消息正文里。 +- `sessions.searchHistory` 通过结构化搜索文档表优先走 FTS5,失败时回退 `LIKE`。 +- Subagent session 与普通 session 共用表结构,但通过 `sessionKind`、`parentSessionId`、 + `subagentMeta` 区分生命周期和展示。 diff --git a/docs/architecture/skill-runtime-hardening/plan.md b/docs/architecture/skill-runtime-hardening/plan.md deleted file mode 100644 index 1d227471c..000000000 --- a/docs/architecture/skill-runtime-hardening/plan.md +++ /dev/null @@ -1,32 +0,0 @@ -# Skill Runtime Hardening Plan - -## Data Model -- Add `SkillExtensionConfig` with `version`, `env`, `runtimePolicy`, and `scriptOverrides`. -- Add `SkillScriptDescriptor` generated from `scripts/**/*.{py,js,mjs,cjs,sh}` and merged with sidecar overrides. -- Store sidecars in `/.deepchat-meta/.json`. - -## Runtime Flow -- `SkillPresenter` owns sidecar read/write and script discovery. -- `SkillExecutionService` validates active skill access, resolves scripts, merges env, selects runtime, and executes scripts. -- `skill_run` becomes the preferred skill-local execution entrypoint. - -## Read Guardrails -- Add shared binary-read helpers for ACP and main agent reads. -- ACP rejects non-text files through `fs/read_text_file`. -- Main agent keeps image OCR fallback, but rejects unsupported binary reads with guidance. - -## Process Output Reliability -- Move foreground and background completion semantics from `exit` to `close`. -- Await output flush before returning completed process results. -- Offload large foreground output to session files when possible. - -## UI -- Extend the skill editor with runtime policy, env rows, and discovered scripts. -- Show script/env/runtime summary badges on skill cards. - -## Tests -- Presenter: sidecar lifecycle, script discovery, overwrite/uninstall behavior. -- Runtime: active skill enforcement, runtime fallback, script path validation. -- Agent tooling: prompt injection, `skill_run`, binary read rejection. -- ACP: `read_text_file` binary rejection. -- Process handling: `close`-based flush behavior. diff --git a/docs/architecture/skill-runtime-hardening/spec.md b/docs/architecture/skill-runtime-hardening/spec.md deleted file mode 100644 index fc6f156a2..000000000 --- a/docs/architecture/skill-runtime-hardening/spec.md +++ /dev/null @@ -1,48 +0,0 @@ -# Skill Runtime Hardening - -## Summary - -Add runtime-aware skill extensions without changing external `SKILL.md` formats. DeepChat stores skill-only runtime settings in a sidecar directory and uses them to execute bundled scripts safely and predictably. - -## User Stories - -### US-1: Configure skill environment variables -- As a user, I can define env vars for a skill in Settings. -- Acceptance: - - Env vars are stored outside the skill folder as plaintext sidecar metadata. - - Editing a skill does not rewrite external `SKILL.md` frontmatter to persist env vars. - -### US-2: Run skill scripts reliably -- As an agent, I can run scripts bundled in an active skill without guessing relative paths. -- Acceptance: - - Only scripts under `/scripts/` can be executed. - - The runtime picks system `uv`/`node` first, then falls back to DeepChat bundled runtimes. - - Skill scripts execute from the current session workdir when available, while Python still honors `pyproject.toml` via `uv run --project `. - -### US-3: Prevent prompt pollution from binary file reads -- As an agent, reading an image or binary file through text-file APIs does not inject raw bytes into prompt context. -- Acceptance: - - ACP `fs/read_text_file` rejects image/PDF/common binary files with remediation guidance. - - Main agent `read` keeps image OCR fallback, but rejects unsupported binary formats instead of returning raw bytes. - -### US-4: Guide the model toward stable skill execution -- As an agent, active skill instructions clearly include absolute paths, script inventory, and the preferred execution tool. -- Acceptance: - - Active skill prompt includes `skillRoot`, skill root env vars, recommended `base_directory`, runnable scripts, and explicit guardrails against inline `python -c` / `node -e`. - -### US-5: Keep process output after process exit -- As an agent, command output is still available when a child process writes a large payload right before exiting. -- Acceptance: - - Foreground exec waits for child `close`. - - Background exec sessions are considered complete only after `close` and log flush. - - Large foreground output is offloaded to a session log file instead of being silently truncated away. - -## Non-Goals -- Secret encryption or OS keychain storage for skill env vars. -- Extending external skill formats with DeepChat-only frontmatter fields. -- General workflow orchestration across multiple skills. - -## Constraints -- Keep existing skills compatible. -- Ignore `.deepchat-meta` in skill discovery and sync/export flows. -- Reuse the existing `process` tool for background session management. diff --git a/docs/architecture/skill-runtime-hardening/tasks.md b/docs/architecture/skill-runtime-hardening/tasks.md deleted file mode 100644 index 2beb3394a..000000000 --- a/docs/architecture/skill-runtime-hardening/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Skill Runtime Hardening Tasks - -1. Add shared types for skill runtime config and script descriptors. -2. Extend `SkillPresenter` with sidecar persistence and script discovery. -3. Add `SkillExecutionService` and wire `skill_run` into agent tools. -4. Harden binary read behavior in ACP and main agent reads. -5. Switch exec completion logic from `exit` to `close` and await output flush. -6. Extend skills settings UI to edit runtime config and show summaries. -7. Add or update tests for presenter, ACP, agent tooling, and process output behavior. diff --git a/docs/architecture/sqlite-database-encryption/plan.md b/docs/architecture/sqlite-database-encryption/plan.md deleted file mode 100644 index 0cfd732c4..000000000 --- a/docs/architecture/sqlite-database-encryption/plan.md +++ /dev/null @@ -1,247 +0,0 @@ -# SQLite Database Encryption Plan - -## Architecture - -- Add a main-process database security layer responsible for encryption metadata, safeStorage - wrapping, unlock, password validation, migration orchestration, and status reporting. -- Store non-secret encryption metadata outside SQLite in a small ElectronStore file, for example - `database-security.json`. -- Keep `ConfigPresenter` startup behavior unchanged until SQLite is attached. After attach, route - sensitive config keys through SQLite-backed storage. -- Extend `ConfigTables` with generic app configuration storage for sensitive settings that do not - need dedicated relational tables. -- Keep splash as the only startup unlock UI. The main window is created only after DB unlock and DB - initialization succeed. -- Expose settings operations through typed route contracts and `SettingsClient` or a dedicated - `DatabaseSecurityClient`. - -## Data Model - -Unencrypted metadata store: - -```ts -type DatabaseSecurityMetadata = { - version: 1 - enabled: boolean - cipher: 'sqlcipher' - passwordStorage: 'safeStorage' | 'manual' | 'none' - wrappedPassword?: string - safeStorageBackend?: string - lastMigrationAt?: number - lastMigrationDirection?: 'enable' | 'change-password' | 'disable' -} -``` - -SQLite additions: - -- `app_settings`: `key TEXT PRIMARY KEY`, `value_json TEXT NOT NULL`, `sensitive INTEGER NOT NULL`, - `updated_at INTEGER NOT NULL`. -- Store migrated values under existing logical keys: `remoteControl`, `mcprouterApiKey`, - `nowledgeMemConfig`, `hooksNotifications`, `knowledgeConfigs`, `customPrompts`, and - `systemPrompts`. -- Keep existing provider/model/MCP/agent SQLite tables unchanged. - -Password handling: - -- The user-provided SQLite password is the database password for v1. -- If `safeStorage.isEncryptionAvailable()` is true, store only `safeStorage.encryptString(password)` - as base64 in metadata. This uses the OS credential store, such as macOS Keychain, Windows - Credential Vault, or a Linux secret store, through Electron. -- If safeStorage is unavailable, do not persist the password; metadata records `passwordStorage: - 'manual'`. -- If a manual unlock succeeds later on a system where safeStorage is available, re-wrap the password - for future launches. This covers restored/imported data where the encrypted database exists but - the local OS credential store does not have a usable wrapped password. - -## Startup Unlock Flow - -1. `configInitHook` creates `ConfigPresenter` and applies startup settings as today. -2. `databaseInitHook` creates or retrieves the database security service before `DatabaseInitializer`. -3. If metadata says encryption is disabled, initialize SQLite without a password. -4. If encryption is enabled and a wrapped password exists, try `safeStorage.decryptString()`. -5. Validate the decrypted password by opening the database with the normal SQLite open helper and a - lightweight schema query. -6. If validation fails, if the local OS credential store entry is missing, or if safeStorage is - unavailable, ask `SplashWindowManager` to enter unlock mode and await a password submission. -7. On wrong password, return an unlock error to splash and keep waiting. -8. On cancel, quit startup before main window creation. -9. On success, pass the password into `DatabaseInitializer({ password })`. - -Splash unlock UI: - -```text -+----------------------------------------+ -| DeepChat | -| Local database is encrypted | -| | -| SQLite password | -| [ ******************************** ] | -| | -| Wrong password. Try again. | -| | -| [ Unlock ] [ Quit ] | -| | -| System unlock is unavailable on this | -| device, so manual unlock is required. | -+----------------------------------------+ -``` - -System unlock progress state: - -```text -+----------------------------------------+ -| DeepChat | -| Unlocking local database | -| | -| Use the system unlock prompt if one | -| appears. | -| | -| Opening local database... | -+----------------------------------------+ -``` - -## SQLite Open And Validation - -- Keep `cipher='sqlcipher'` and `legacy=4` configured before applying the key so newly encrypted - databases use SQLCipher 4 compatibility mode and can be opened by tools such as DB Browser for - SQLite with SQLCipher 4 defaults. -- Replace SQL string key interpolation with the native `db.key(Buffer.from(password, 'utf8'))` API - or an equivalent parameter-safe binding path. -- Validate encrypted opens with a read against `sqlite_master`, then use the existing transaction - validation in `DatabaseInitializer`. -- Ensure migration and unlock errors are normalized before reaching logs or renderer state. - -## Migration Flow - -Primary migration primitive: - -- Use one source connection plus an attached temporary target database, then copy normal schema - tables and rows into the target. -- `better-sqlite3-multiple-ciphers` backup cannot copy between incompatible encrypted/plaintext - database states, and the current binding does not treat `VACUUM INTO` filenames as SQLite URI - parameters. The attach/copy path is therefore the project-compatible migration primitive. -- Apply target passwords through `ATTACH DATABASE ? AS migration_target KEY ?` binding parameters - after configuring SQLCipher 4 compatibility mode, not by interpolating a password into SQL text. - For encrypted-to-plaintext disable, attach the target with an explicit empty key. -- Do not run native rekey against the active `agent.db`. - -Runtime migration sequence: - -1. Acquire a process-wide migration lock so settings, sync backup/import, and reset flows cannot run - concurrently. -2. Close the active `SQLitePresenter` connection. -3. Open the source DB with the current password if encrypted. -4. Run WAL checkpoint and switch the source connection to a non-WAL journal mode for the export. -5. Create a temp destination DB in the same `app_db` directory by attaching it to the source - connection. -6. Copy normal tables and row data into the attached target. Skip FTS virtual/shadow tables; the - existing table initialization rebuilds those derived search indexes after reopen. -7. Open the temp DB through the normal SQLite helper using the target password. -8. Run `PRAGMA quick_check`, schema version checks, and key table row count checks. -9. Rename the active database to a short-lived rollback file, move the temp DB to `agent.db`, and - remove sidecar files. -10. Reopen `SQLitePresenter` with the target password and reattach SQLite-backed config stores. -11. Update metadata only after reopen succeeds. -12. Delete the rollback file after successful reopen; on failure, restore it and reopen the original - DB. - -Status results returned to settings: - -```ts -type DatabaseSecurityStatus = { - enabled: boolean - cipher: 'sqlcipher' - safeStorageAvailable: boolean - safeStorageBackend?: string - passwordStorage: 'safeStorage' | 'manual' | 'none' - manualUnlockRequired: boolean - migrationInProgress: boolean - lastMigrationAt?: number -} -``` - -## Settings UI - -Add the section to the current Data settings page near privacy/data management controls. - -```text -+------------------------------------------------------------------+ -| SQLite database encryption Enabled | -| Protects local chat history, provider keys, MCP config, prompts, | -| and other sensitive settings stored in agent.db. | -| | -| Cipher SQLCipher | -| System unlock Available via OS secure storage | -| Startup unlock System unlock | -| Last migration 2026-05-22 14:30 | -| | -| Current password [ ************************ ] | -| New password [ ************************ ] | -| Confirm password [ ************************ ] | -| | -| [ Change password ] [ Disable encryption ] | -+------------------------------------------------------------------+ -``` - -Disabled state: - -```text -+------------------------------------------------------------------+ -| SQLite database encryption Disabled | -| | -| System unlock Unavailable | -| Startup unlock Manual password required after enabling | -| | -| New password [ ************************ ] | -| Confirm password [ ************************ ] | -| | -| [ Enable encryption ] | -| | -| This system cannot use Electron safeStorage. DeepChat can still | -| encrypt the database, but you must enter the password on every | -| startup. | -+------------------------------------------------------------------+ -``` - -## Sensitive Config Migration - -- Add DB-backed read/write adapters for sensitive app settings keys after SQLite attach. -- Migrate legacy values into `app_settings` once, guarded by a `config_migrations` marker such as - `sensitive-config-sqlite-v1`. -- After successful migration and verification, remove migrated sensitive keys from `app-settings` - JSON and replace prompt/knowledge JSON stores with empty defaults or remove the files. -- Keep non-sensitive startup settings in JSON. -- Update sync backup filtering so migrated sensitive settings are not copied into JSON backup paths. -- Import legacy backups by reading old JSON values and writing them into SQLite when config storage - migration is active. - -## Failure Modes - -- SafeStorage unavailable: allow encryption, set metadata to manual mode, show manual prompt on - startup. -- SafeStorage decrypt fails: show manual splash unlock; if manual unlock succeeds and safeStorage is - available, re-wrap the password. -- Wrong password: keep splash open and do not initialize SQLite presenters. -- Migration fails before replacement: delete temp DB and reopen original DB. -- Migration fails after replacement: restore rollback DB and old metadata. -- App exits during migration: next startup detects temp/rollback files, restores the last complete - database, and asks the user to retry migration. - -## Testing - -- Main tests cover safeStorage support states, password wrapping, unlock success/failure/cancel, - migration success/rollback, WAL cleanup, and metadata updates. -- Main integration tests use temp databases for plaintext-to-encrypted, encrypted password change, - encrypted-to-plaintext, and legacy sensitive config migration. -- Renderer tests cover Data settings states, validation errors, action disabling during migration, - and splash unlock states. -- Manual QA covers macOS system unlock, Linux/manual unlock fallback, wrong password retry, cancel - behavior, sync backup/import, and reset flows. - -## Quality Gates - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- `pnpm run typecheck` -- Focused Vitest suites for main encryption/migration and renderer settings/splash behavior. diff --git a/docs/architecture/sqlite-database-encryption/spec.md b/docs/architecture/sqlite-database-encryption/spec.md deleted file mode 100644 index 0828e5dc6..000000000 --- a/docs/architecture/sqlite-database-encryption/spec.md +++ /dev/null @@ -1,117 +0,0 @@ -# SQLite Database Encryption - -## User Story - -DeepChat users can store provider credentials, remote-control tokens, prompts, knowledge -configuration, and conversation history locally. Users need an optional database encryption mode so -the main SQLite database is encrypted at rest, can be unlocked during startup, and can be migrated -safely when the SQLite password changes. - -## Goals - -- Let users enable, change, and disable SQLite database encryption from the data settings screen. -- Require an unlock step before any SQLite-backed presenter opens an encrypted `agent.db`. -- Use Electron `safeStorage` to store the SQLite password when available. -- Fall back to manual unlock on every startup when `safeStorage` is unavailable or cannot decrypt. -- Migrate the existing database by writing a new database file, verifying it, and replacing the old - file only after successful validation. -- Move remaining sensitive JSON/ElectronStore configuration into SQLite so encryption covers it. - -## Acceptance Criteria - -- A new data settings section shows database encryption state, selected cipher, safeStorage support, - last migration time, and whether manual startup unlock is required. -- Enabling encryption requires a non-empty SQLite password and confirmation. On success, `agent.db` - is replaced by an encrypted database and opens only with that password. -- Changing the password requires the current password, writes a new encrypted database using the new - password, verifies the result, then replaces the active database. -- Disabling encryption requires the current password, writes a verified plaintext database, then - replaces the active encrypted database. -- A failed migration leaves the original database and sidecar files usable. -- Successful migration removes temporary files and old `agent.db` sidecar files (`-wal`, `-shm`). -- Startup does not initialize SQLite-backed presenters until the encrypted database is unlocked. -- When `safeStorage` decrypts the stored password successfully, startup proceeds without showing a - manual password prompt. In normal use this means the user unlocks once, and later launches open - automatically from the OS credential store. -- When `safeStorage` is unavailable, the OS credential store entry is missing, or decryption fails - after imported data is restored on a different device, the splash window shows an unlock form. - After a successful manual unlock on a system with safeStorage, DeepChat stores a fresh wrapped - password for future launches. -- Wrong passwords keep the user on the splash unlock form and do not open the main window. -- Canceling unlock exits startup without creating the main window. -- User-facing strings are localized through the renderer i18n system. -- No SQLite password or derived key is written to logs, route activity, migration errors, telemetry, - or renderer-visible state. -- Legacy plaintext config copies for migrated sensitive keys are removed or redacted after successful - SQLite migration. - -## Data To Encrypt - -Already covered once `agent.db` is encrypted: - -- Conversations, messages, tool traces, assistant blocks, pending inputs, usage stats, search - documents, search indexes, attachment metadata, projects, sessions, and agents. -- Provider credentials and provider metadata already stored in SQLite, including API keys, OAuth - tokens, AWS Bedrock secrets, and Vertex credentials. -- MCP server config, MCP env/custom headers, MCP settings, agent settings, and agent MCP selections - already stored in SQLite. - -Move into SQLite in this feature: - -- `remoteControl`: Telegram/Discord bot tokens, Feishu/Lark app secrets, verification tokens, - encrypt keys, QQBot client secrets, Weixin iLink bot tokens, account state, and bindings. -- `mcprouterApiKey`. -- `nowledgeMemConfig`, including `apiKey`. -- `hooksNotifications`, especially hook commands and webhook-like values. -- `custom_prompts` and `system_prompts`. -- `knowledge-configs`, including RAGFlow, Dify, FastGPT, and built-in knowledge config metadata. -- Legacy provider/model/MCP keys that remain in plaintext JSON after the previous config-to-SQLite - migration. - -Keep outside encrypted SQLite for startup: - -- Language, theme, logging, proxy mode, sync folder path, update channel, window/startup flags, and - other settings needed before the database is opened. -- `customProxyUrl` stays in startup config for compatibility, but credentials embedded in proxy URLs - should be rejected or split into encrypted storage in a follow-up. - -## Non-Goals - -- No cloud key backup or password recovery. -- No guaranteed secure deletion on SSDs or filesystems with snapshots. -- No encryption of external files already exported by the user, old sync backups, crash dumps, or OS - backups. -- No guarantee that every supported OS will show biometric UI; Electron delegates to the platform - password manager. -- No change to the core SQL schema names for existing conversation, provider, MCP, or agent tables. - -## Constraints - -- `configInitHook` must continue to read logging and proxy settings before SQLite opens. -- `databaseInitHook` is the earliest point where encrypted SQLite can be unlocked. -- The unlock UI belongs to the splash renderer so the main app is not shown before DB unlock. -- New renderer-main APIs should use typed route contracts and renderer API clients, not new direct - `useLegacyPresenter()` usage. -- Migration SQL must not include raw passwords in any logged SQL string. -- The implementation must support both safeStorage-backed startup unlock and manual startup unlock. - -## Security Notes - -- The SQLite password is present in main-process memory while the app is running. -- `safeStorage` protects stored password material at rest for the current OS user by using the OS - credential store, such as macOS Keychain, Windows Credential Vault, or a Linux secret store. It - does not defend against malware running as that user. -- Deleting old database files is ordinary file deletion, not cryptographic erasure. -- WAL/SHM sidecar files must be checkpointed and removed during migration because they may contain - plaintext data from before encryption. -- Sync backups created before encryption may still contain plaintext data and remain the user's - responsibility to delete. - -## References - -- Electron safeStorage: https://www.electronjs.org/docs/latest/api/safe-storage -- SQLite3 Multiple Ciphers overview: https://utelle.github.io/SQLite3MultipleCiphers/ -- SQLite3 Multiple Ciphers SQL pragmas: - https://utelle.github.io/SQLite3MultipleCiphers/docs/configuration/config_sql_pragmas/ -- SQLite3 Multiple Ciphers URI parameters: - https://utelle.github.io/SQLite3MultipleCiphers/docs/configuration/config_uri/ diff --git a/docs/architecture/sqlite-database-encryption/tasks.md b/docs/architecture/sqlite-database-encryption/tasks.md deleted file mode 100644 index 694ee41bb..000000000 --- a/docs/architecture/sqlite-database-encryption/tasks.md +++ /dev/null @@ -1,79 +0,0 @@ -# SQLite Database Encryption Tasks - -## SDD - -- [x] Draft encryption spec, implementation plan, and task breakdown. - -## Database Security Infrastructure - -- [x] Add database security metadata store outside SQLite. -- [x] Add safeStorage wrapper with support detection, backend reporting, password wrap/unwrap, and - normalized errors. -- [x] Add database password validation using the project SQLite open helper. -- [x] Replace SQL string password interpolation with the native `db.key(Buffer)` path or equivalent - parameter-safe key application. -- [x] Configure SQLCipher 4 compatibility mode before keying encrypted database connections. -- [x] Add tests for metadata defaults, safeStorage unavailable, safeStorage decrypt failure, and - password validation. - -## Startup And Splash Unlock - -- [x] Extend `SplashWindowManager` with an awaitable database unlock request API. -- [x] Add typed splash IPC channels for unlock submit and cancel. -- [x] Update `databaseInitHook` to resolve the SQLite password before creating `DatabaseInitializer`. -- [x] Add splash renderer unlock and system-unlock-progress states. -- [ ] Add tests for successful system unlock, manual unlock, wrong password retry, and cancel before - main window creation. - -## Migration Engine - -- [x] Verify backup-plus-temp-`db.rekey(Buffer)` behavior against the project Electron/SQLite ABI - and replace it with attach/copy migration after incompatibility was confirmed. -- [ ] Add a migration lock shared by encryption changes, sync backup/import, DB repair, and reset - flows. -- [x] Implement plaintext-to-encrypted migration with temp DB validation and rollback. -- [x] Implement encrypted password change with temp DB validation and rollback. -- [x] Implement encrypted-to-plaintext disable flow with temp DB validation and rollback. -- [x] Add sidecar cleanup for `agent.db-wal` and `agent.db-shm`. -- [x] Add startup recovery for leftover temp/rollback migration files. -- [ ] Add tests for migration success, validation failure, replacement failure, rollback, and sidecar - cleanup. - -## Typed Routes And Settings UI - -- [x] Add shared route contracts for status, enable encryption, change password, and disable - encryption. -- [x] Add renderer API client methods for the new database security routes. -- [x] Add Data settings encryption section with i18n text and password validation. -- [x] Explain OS credential-store unlock behavior and when manual password input is required. -- [x] Disable encryption actions while migration is running or when required password fields are - invalid. -- [x] Add settings activity records without storing raw passwords. -- [ ] Add renderer tests for enabled, disabled, safeStorage unavailable, validation, and migration - progress states. - -## Sensitive Config Into SQLite - -- [x] Add SQLite-backed generic `app_settings` storage for sensitive app config. -- [x] Migrate `remoteControl` into SQLite and redact its legacy JSON copy. -- [x] Migrate `mcprouterApiKey` into SQLite and redact its legacy JSON copy. -- [x] Migrate `nowledgeMemConfig` into SQLite and redact its legacy JSON copy. -- [x] Migrate `hooksNotifications` into SQLite and redact its legacy JSON copy. -- [x] Migrate `knowledge-configs` into SQLite and clear the legacy ElectronStore file. -- [x] Migrate `custom_prompts` and `system_prompts` into SQLite and clear legacy prompt files. -- [x] Remove legacy provider/model/MCP sensitive leftovers from JSON once SQLite-backed config is - verified. -- [x] Remove the legacy `providers` JSON copy when database encryption or password migration runs - after provider config has moved to SQLite. -- [x] Update sync backup/import filtering and legacy backup import for migrated sensitive settings. -- [ ] Add tests for idempotent sensitive config migration and legacy JSON redaction. - -## Verification - -- [x] Run focused main tests for encryption, migration, unlock, and sensitive config migration. -- [x] Run focused renderer tests for Data settings encryption controls. -- [ ] Run focused renderer tests for splash unlock UI. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. -- [x] Run `pnpm run typecheck`. diff --git a/docs/architecture/startup-orchestration/acceptance.md b/docs/architecture/startup-orchestration/acceptance.md deleted file mode 100644 index 64f9e80a9..000000000 --- a/docs/architecture/startup-orchestration/acceptance.md +++ /dev/null @@ -1,126 +0,0 @@ -# Startup Orchestration 验收方案 - -## 验收目标 - -本轮验收聚焦六件事: - -1. 主窗口 interactive 是否先于 provider/model background warmup -2. settings 是否先显示 shell + summaries,再按 section 渐进完成 -3. workload 是否具备 target 级取消与状态回推 -4. `providers.listSummaries` / `models.getProviderCatalog` 是否走新的轻量与内存路径 -5. skills discover / sync scan 是否已离开 main -6. 打包后 worker 路径是否仍然可用 - -## 必备观测点 - -日志或事件中至少应能观察到: - -1. `startup.bootstrap.ready` -2. `startup.session.first-page.ready` -3. `startup.provider.warmup.deferred` -4. `startup.workload.changed` -5. settings heavy section 的 skeleton -> ready 过渡 - -## P0 通过条件 - -### P0-1 主窗口首屏响应 - -- 冷启动时主窗口在 session 首批返回前已经可交互 -- interactive 后不会立刻自动扫所有 provider models -- provider/model 相关区域按需或 idle 后再补齐 - -### P0-2 Settings staged hydration - -- 打开 settings 时先出现 shell/nav/provider summaries -- `modelStore.initialize()` / `ollamaStore.initialize()` 不在 settings-open 默认链路上 -- 进入 provider / skills / MCP / remote 前,不会提前触发对应 heavy hydration -- heavy 页面在 ready 前展示 section skeleton,而不是空白页或整窗冻结 - -### P0-3 Workload cancellation - -- settings window 关闭后,`target = settings` 的 pending/running task 被取消 -- 再次打开 settings 时可以 replay 可见 workload 状态 - -### P0-4 Main hotspot control - -- `providers.listSummaries` 不返回模型数组 -- `models.getProviderCatalog` route 重复命中时以内存快照为主路径 -- `ModelStatusHelper` 首次构建 snapshot 后,批量查询不再重复打持久层读取 - -### P0-5 Off-main workers - -- skills discover / sync scan 正常返回结果 -- worker 抛错时自动回退主线程路径,应用仍可用 - -### P0-6 Packaging compatibility - -- `electron-vite build` 可以产出包含 worker 逻辑的 main bundle -- `electron-builder --dir` 可以产出 unpacked 包 -- worker 在 asar 环境下不依赖 `process.cwd()` 解析第三方依赖 - -## 手工验收步骤 - -### 1. 冷启动主窗口 - -1. 冷启动应用 -2. 观察: - - shell / new thread 可交互 - - session sidebar 先 skeleton 再首批 - - provider/model 相关 UI 不会在 interactive 后立刻卡住 main - -### 2. 冷启动后立刻打开 settings - -1. 冷启动后马上打开 settings -2. 观察: - - shell/nav 先出现 - - provider list 先出现 summaries - - 不出现整窗白屏或冻结 - -### 3. 进入 heavy settings routes - -1. 依次进入 provider / skills / MCP / remote -2. 观察: - - 进入前不提前拉起对应数据 - - 进入后先 skeleton,再 section-ready - -### 4. 关闭 settings 中断任务 - -1. 打开 settings 后迅速切到 heavy 页面 -2. 在加载过程中关闭 settings -3. 观察: - - settings 任务取消 - - main 窗口继续保持响应 - -### 5. 大量 provider / models / skills - -1. 准备多 provider、多 models、多 skills 数据 -2. 冷启动应用并操作主窗口输入、切换 settings -3. 观察: - - main 输入与窗口交互保持响应 - - skills discover / sync scan 不再明显阻塞 main - -## 自动化校验 - -每轮收口至少执行: - -1. `pnpm run format` -2. `pnpm run i18n` -3. `pnpm run lint` -4. `pnpm run typecheck` -5. 关键 vitest: - - coordinator - - provider summaries route - - model status snapshot - - settings startup - - worker direct tests - - worker fallback tests - -## 不通过条件 - -以下任一条件触发即判定不通过: - -1. 主窗口 interactive 后立即无条件扫所有 provider/models -2. settings 打开时仍拉起全量 model / ollama warmup -3. settings heavy route ready 前出现空白页或整窗卡住 -4. `ModelStatusHelper` 批量查询仍重复命中持久层 -5. worker 在打包产物环境下因依赖解析失败不可用 diff --git a/docs/architecture/startup-orchestration/plan.md b/docs/architecture/startup-orchestration/plan.md deleted file mode 100644 index 755cfee76..000000000 --- a/docs/architecture/startup-orchestration/plan.md +++ /dev/null @@ -1,213 +0,0 @@ -# Startup Orchestration 实施计划 - -## 规划结论 - -本轮实现拆成两阶段推进,但都沿用同一套 `startup-orchestration` 规格,不再另起一套启动方案: - -1. phase 1:coordinator、typed event、summary route、on-demand hydration、chunked main tasks -2. phase 2:skills discover / sync scan worker 化,补齐 main-side 热点优化 - -当前分支的交付目标是: - -```text -main: 先可交互,再渐进补 -settings: summary first,再分 section hydration -background: coordinator 调度 -cpu parse/scan: off-main -``` - -## 当前实现架构 - -```mermaid -sequenceDiagram - participant Main as Main Process - participant Coord as StartupWorkloadCoordinator - participant Renderer as Renderer - participant Settings as Settings Renderer - participant Worker as worker_threads - - Main->>Coord: createRun('main') - Main->>Coord: register floating/skills/mcp/remote tasks - Renderer->>Main: startup.getBootstrap - Main-->>Renderer: bootstrap shell - Renderer-->>Renderer: interactive shell ready - Renderer->>Main: sessions.listLightweight - Main-->>Renderer: first page - Renderer-->>Renderer: sidebar skeleton -> real list - Settings->>Main: providers.listSummaries - Main-->>Settings: provider summaries - Settings-->>Settings: shell/nav/provider list ready - Settings->>Main: models.getProviderCatalog (on route entry) - Main-->>Settings: provider catalog snapshot - Main->>Worker: skill discovery / sync scan - Worker-->>Main: JSON result or error - Main-->>Renderer: startup.workload.changed -``` - -## Phase 1 - -### 1. Coordinator 与 workload contract - -落地项: - -1. `StartupWorkloadCoordinator` -2. `startup.workload.changed` -3. shared task id / phase / state schema -4. settings target cancel / replay - -结果: - -1. startup task 不再是松散 fire-and-forget -2. renderer 可以按 target/task 感知 readiness -3. settings 关闭后后台 warmup 不再继续偷偷跑 - -### 2. Route-level startup tracking - -落地项: - -1. `startup.getBootstrap` -> `main.bootstrap` -2. `sessions.listLightweight` -> `main.session.firstPage` -3. `providers.listSummaries` -> `settings.providers.summary` -4. `models.getProviderCatalog` -> `settings.provider.models` / `main.provider.warmup` -5. Ollama / skills / MCP route tracking - -结果: - -1. route 级 workload 可观测 -2. 主要 settings section readiness 有统一事件源 - -### 3. Settings staged hydration - -落地项: - -1. settings 首屏只跑 cheap snapshot + provider summaries -2. `modelStore.initialize()` / `ollamaStore.initialize()` 退出 settings-open 默认链路 -3. provider detail / skills / MCP / remote 页面按路由进入后再 hydration -4. heavy settings section 统一给 skeleton - -结果: - -1. settings 打开不再整窗拉满 -2. heavy 页面 ready 前有明确占位态 - -### 4. Main window warmup policy - -落地项: - -1. 去掉主窗口 interactive 后立即全量 provider/model warmup -2. `ChatStatusBar` / `NewThreadPage` 改为 likely-provider on-demand warmup -3. coordinator idle 后增加低优先级 provider snapshot backfill - -结果: - -1. provider/model 数据只在需要时优先补齐 -2. 用户不操作相关 UI 时,不再立刻扫全量 provider -3. 空闲窗口下可以渐进预热缓存 - -### 5. Model status / provider catalog hotspot 修复 - -落地项: - -1. `providers.listSummaries` -2. `models.getProviderCatalog` memory-snapshot-first -3. `ModelStatusHelper` persisted snapshot - -结果: - -1. settings 首屏不再携带大模型数组 -2. route 重复命中时优先走内存而不是反复持久层读取 - -## Phase 2 - -### 1. Skill discovery worker - -落地项: - -1. `runInlineJsonWorker` -2. `discoverSkillMetadataInWorker(...)` -3. worker warning logging -4. worker failure fallback 到旧主线程路径 - -结果: - -1. skill manifest discover / parse 脱离 main -2. 仍保持错误可回退 - -### 2. Skill sync worker - -落地项: - -1. `scanExternalToolsInWorker(...)` -2. `scanAndDetectDiscoveriesInWorker(...)` -3. `SkillSyncPresenter` worker-first / main-thread fallback - -结果: - -1. external tool scan / compare 脱离 main -2. settings skills section 与后台扫描不再直接占用 main CPU - -## Worker 边界决策 - -本轮明确采用: - -1. `worker_threads` 处理短生命周期、纯 Node/FS/parse 的 CPU/scan 工作 -2. main 保留 Electron 绑定任务的调度与事件桥接 - -不在本轮切换为 `utilityProcess` 的原因: - -1. 当前技能 discover / sync scan 更像短任务,不需要独立进程生命周期管理 -2. 现有 `electron-vite + electron-builder + asar` 对当前 inline worker 路径已经可验证打包通过 -3. 更适合 `utilityProcess` 的是长驻、隔离性要求更强、失败域更大的 runtime/service 类任务 - -## 风险与缓解 - -### 风险 1:idle warmup 又变成新的隐性阻塞 - -缓解: - -1. idle warmup 只在 coordinator idle 后触发 -2. 每个 provider 之间显式 yield -3. 仍使用 background phase - -### 风险 2:worker 在打包后找不到依赖 - -缓解: - -1. worker 采用 inline eval 模式,避免额外 worker 入口文件打包问题 -2. 通过 `createRequire(...)` 把 worker 的依赖解析锚到 bundle 路径,而不是 `process.cwd()` - -### 风险 3:settings 关闭后任务残留 - -缓解: - -1. settings workload 全部归属 `target = settings` -2. window close 时统一 `cancelTarget('settings')` - -## 验证策略 - -主进程: - -1. coordinator 优先级 / 并发限制 / 取消 -2. provider summaries route -3. model status snapshot -4. worker success / fallback - -renderer: - -1. settings open 仅 cheap init + summaries -2. main window 不再 eager provider warmup -3. heavy settings section skeleton - -打包: - -1. `electron-vite build` -2. `electron-builder --dir` -3. worker 路径在 asar 环境下可解析依赖 - -## 后续 follow-up - -以下不再算本轮 blocker,但适合作为下一轮: - -1. 更细粒度的 main/splash unified trace -2. `utilityProcess` 在 long-lived runtime 任务上的替换评估 -3. 更强的 renderer route-level skeleton 自动化测试覆盖 diff --git a/docs/architecture/startup-orchestration/spec.md b/docs/architecture/startup-orchestration/spec.md deleted file mode 100644 index 284ada384..000000000 --- a/docs/architecture/startup-orchestration/spec.md +++ /dev/null @@ -1,219 +0,0 @@ -# Startup Orchestration 规格 - -## 概述 - -本轮规格把启动优化从“继续多挪一些 deferred”收敛成“把 deferred 背后的 main 负载拆成可调度、可取消、可观测、必要时 off-main 的工作流”。 - -目标固定为两条主线: - -1. 主窗口和 settings 都使用 staged hydration,先给用户可交互骨架,再按 section 渐进补齐。 -2. main 进程中会持续占满事件循环的初始化逻辑统一接入 coordinator;纯 Node/FS/parse 的重活迁到 `worker_threads`;仍必须留在 main 的 Electron 绑定任务改为低并发、分批让出事件循环。 - -## 当前决策 - -本规格以 2026-04-21 的 Startup Main-Unblocking V3 决策为准: - -1. main-side `StartupWorkloadCoordinator` 统一管理启动任务。 -2. workload phase 固定为 `interactive -> deferred -> background`。 -3. workload resource 并发固定为 `cpu = 1`、`io = 2`。 -4. main window 保持 `bootstrap + staged session hydration`,provider/model warmup 只在 on-demand 或 coordinator idle 后执行。 -5. settings window 首屏只加载 cheap snapshot、router ready、provider summaries;heavy tabs 按路由进入再 hydration。 -6. `models.getProviderCatalog` 改为内存快照优先,`ModelStatusHelper` 改为 snapshot-first。 -7. `SkillPresenter` manifest discover 和 `SkillSyncPresenter` scan/compare 迁到 `worker_threads`,失败时回落主线程路径。 -8. `startup.workload.changed` 作为统一 typed event 回推 renderer readiness。 - -## 背景 - -上一轮 deferred 已经减少了首屏直接阻塞,但 main 进程依然存在两个核心问题: - -1. deferred 任务虽然“晚一点跑”,但仍可能在一个时间窗内把 main 事件循环打满。 -2. settings 打开时仍然会顺手拉起过多 provider/model/ollama/skills/mcp/remote 相关初始化,导致整窗冻结感。 - -实际问题不再是“有没有 deferred”,而是: - -1. deferred 是否被拆成可调度、可取消、可观测的 workload。 -2. CPU/parse/scan 类工作是否已经离开 main。 -3. 仍在 main 的工作是否低并发、可中断、分批 yield。 - -## 目标体验 - -```text -Main Window -+ chat shell ready -+ agent/bootstrap ready -+ session sidebar skeleton -+ provider/model regions hydrate on demand or during idle - -Settings Window -+ shell/nav ready -+ cheap settings snapshot ready -+ provider summaries ready -+ provider/models/skills/mcp/remote hydrate by route task events -``` - -## 用户故事 - -### US-1:主窗口先交互,再补内容 - -作为用户,我希望主窗口出现后立刻可以进入 new thread 或 active chat,而不是等待 provider/model warmup 抢占 main。 - -### US-2:settings 先出框架,再补 section - -作为用户,我希望打开 settings 时先看到可用的 shell、导航和 summaries,而不是整窗白屏或冻结。 - -### US-3:后台任务有明确进度与取消边界 - -作为用户,我希望重任务能渐进完成;关闭 settings 时,settings 专属 warmup 不应继续偷偷占用 main。 - -### US-4:重 CPU/parse 工作不再阻塞 main - -作为用户,我希望 skills discover / sync scan 这类纯 Node 工作在后台线程里完成,不影响主窗口输入和切换。 - -## 功能范围 - -本轮范围覆盖: - -1. `StartupWorkloadCoordinator` 与 typed startup workload event。 -2. 主窗口 provider/model warmup 的 on-demand + idle backfill 策略。 -3. settings staged hydration 与 summary-first 路径。 -4. `providers.listSummaries` 精简 route。 -5. `models.getProviderCatalog` memory-snapshot-first 路径。 -6. `ModelStatusHelper` persisted snapshot。 -7. `SkillPresenter` discovery worker。 -8. `SkillSyncPresenter` scan/discovery worker。 -9. `docs/architecture/startup-orchestration/` 同步到 V3。 - -## 功能要求 - -### A. Main-side Startup Coordinator - -- main 侧必须存在统一的 `StartupWorkloadCoordinator`。 -- coordinator 负责: - - 任务注册 - - phase 优先级调度 - - `cpu/io` 并发限制 - - yield 控制 - - 按 target/run 取消 - - workload 状态事件推送 -- phase 固定为: - - `interactive` - - `deferred` - - `background` -- 资源并发固定为: - - `cpu = 1` - - `io = 2` - -### B. Presenter.init 注册方式 - -- `Presenter.init()` 不再散射式 `void initializeXxx()` fire-and-forget。 -- 以下任务统一注册到 coordinator: - - floating button - - yo browser - - skills init - - skill sync scan - - MCP init - - remote runtime init - - idle provider warmup - -### C. Window/Target 取消边界 - -- settings window 创建时创建或复用 `settings` startup run。 -- settings window ready 时可以 replay 现有 workload snapshot。 -- settings window 关闭时,`target = settings` 的未完成任务必须取消。 -- main window task 与 settings window task 必须在 coordinator 里分 target 管理。 - -### D. Startup Workload Typed Event - -- 新增 typed event:`startup.workload.changed` -- payload 固定包含: - - `startupRunId` - - `target` - - `tasks` -- shared task ids 固定覆盖: - - `main.bootstrap` - - `main.session.firstPage` - - `main.provider.warmup` - - `settings.providers.summary` - - `settings.provider.models` - - `settings.ollama` - - `settings.skills.catalog` - - `settings.skills.syncScan` - - `settings.mcp.runtime` - - `settings.remote.runtime` - -### E. Settings Summary-first Hydration - -- settings `onMounted` 只做: - - `uiSettingsStore.loadSettings()` - - router ready - - `providers.listSummaries` - - startup workload subscription -- settings open 时移除 eager: - - `modelStore.initialize()` - - `ollamaStore.initialize()` -- `settings-provider` 路由进入后再 `ensureProviderModelsReady(providerId)`。 -- Ollama 仅在命中对应 provider detail 时加载。 -- `skills/mcp/remote` 仅在进入各自页面后才触发 hydration。 - -### F. Main Window Provider Warmup Policy - -- `startup.provider.warmup.deferred` 不得在 interactive 后立刻无条件触发全量 sweep。 -- provider/model warmup 允许两个入口: - - 用户进入依赖 provider/model 的具体 UI - - coordinator idle 后进行低优先级 backfill -- idle backfill 必须: - - low priority - - 可取消 - - 每个 provider 之间显式 yield - -### G. Summary Route 与 Memory Snapshot - -- 新增 `providers.listSummaries` route。 -- settings 首屏只依赖 provider summaries,不带模型数组。 -- `models.getProviderCatalog` 保持存在,但改为: - - provider models - - custom models - - db provider models - - model status map - 的 memory-snapshot-first 返回。 - -### H. Model Status Snapshot - -- `ModelStatusHelper.getBatchModelStatus(...)` 不再频繁直接命中持久层。 -- 首次访问时构建内存 status snapshot。 -- 后续批量读取走内存查表。 -- `setModelStatus/deleteModelStatus` 同时更新: - - 内存 snapshot - - 持久层 - -### I. Chunked Main Tasks - -- 仍在 main 的批量任务禁止长时间同步 for-loop 跑完整表。 -- 批量任务必须分批推进,并在批次间 yield。 -- 本轮至少保证: - - provider idle warmup 逐 provider yield - - MCP / remote 等 background init 不和 interactive 抢同一时段资源 - -### J. Off-main Workers - -- 明确迁到 `worker_threads` 的任务: - - `SkillPresenter` manifest discover / parse - - `SkillSyncPresenter` external tool scan / compare -- worker 输入输出必须是 JSON serializable 数据。 -- worker 失败时必须回落主线程路径,不影响应用可用性。 -- 本轮不把 MCP runtime / remote runtime 迁出 main;它们仍走 coordinator。 - -## 非目标 - -1. 本轮不新增新的 legacy IPC。 -2. 本轮不把 MCP runtime / remote runtime 切到 `utilityProcess`。 -3. 本轮不重写 provider store 整体架构。 -4. 本轮不追求“完全无 loading”,而是明确 skeleton / section-ready。 - -## 约束 - -1. 继续保持 typed route / typed event 方向一致。 -2. settings 与 main 必须共享同一套 startup workload contract,而不是两套启动协议。 -3. 允许显式 section skeleton,禁止“表面无 loading、实际整页卡住”。 -4. `worker_threads` 落地必须兼容 `electron-vite + electron-builder + asar` 打包环境。 -5. 仍留在 main 的任务必须在 workload、日志和取消边界上可解释。 diff --git a/docs/architecture/startup-orchestration/tasks.md b/docs/architecture/startup-orchestration/tasks.md deleted file mode 100644 index 5575cdda8..000000000 --- a/docs/architecture/startup-orchestration/tasks.md +++ /dev/null @@ -1,98 +0,0 @@ -# Startup Orchestration 任务拆分 - -## Phase 1 - -### T1 Coordinator / Contract - -- [x] 新增 `StartupWorkloadCoordinator` -- [x] 固定 phase 优先级为 `interactive / deferred / background` -- [x] 固定资源并发为 `cpu = 1 / io = 2` -- [x] 支持 target run 创建、复用、取消、replay -- [x] 新增 typed event `startup.workload.changed` -- [x] 固定 shared startup task ids - -### T2 Presenter / Window Wiring - -- [x] `Presenter.init()` 改成 coordinator 注册 startup task -- [x] floating button 接入 coordinator -- [x] skills init 接入 coordinator -- [x] skill sync background scan 接入 coordinator -- [x] MCP init 接入 coordinator -- [x] remote runtime init 接入 coordinator -- [x] settings window create / ready / close 接入 run create / replay / cancel - -### T3 Main Window Hydration - -- [x] 保持 `startup.getBootstrap` + staged session path -- [x] 去掉 interactive 后立刻无条件 provider/model warmup -- [x] `ChatStatusBar` 改为 likely-provider on-demand warmup -- [x] `NewThreadPage` 改为 likely-provider on-demand warmup -- [x] coordinator idle 后增加低优先级 provider snapshot backfill - -### T4 Settings Hydration - -- [x] settings `onMounted` 只做 cheap init + router ready + `providers.listSummaries` -- [x] 移除 settings open 时的 eager `modelStore.initialize()` -- [x] 移除 settings open 时的 eager `ollamaStore.initialize()` -- [x] `settings-provider` 路由进入后再 `ensureProviderModelsReady(providerId)` -- [x] Ollama 仅在命中对应 provider detail 时加载 -- [x] skills / MCP / remote 页面进入后再各自 hydration -- [x] heavy settings section 增加 skeleton - -### T5 Summary Route / Memory Snapshot - -- [x] 新增 `providers.listSummaries` -- [x] settings 首屏改走 provider summaries -- [x] `models.getProviderCatalog` 改成 memory-snapshot-first -- [x] `ModelStatusHelper` 改为 snapshot-first -- [x] `setModelStatus/deleteModelStatus` 同步更新 snapshot + persistent store - -### T6 Route-level Tracking / Tests - -- [x] route runtime 接入 workload task tracking -- [x] coordinator 优先级 / 并发 / 取消单测 -- [x] `providers.listSummaries` route 单测 -- [x] `ModelStatusHelper` snapshot 单测 -- [x] renderer 侧去 eager warmup 的测试更新 - -## Phase 2 - -### T7 Worker: Skill Discovery - -- [x] 新增 inline JSON worker runner -- [x] `SkillPresenter` discover / parse 迁到 worker -- [x] worker warning logging -- [x] worker failure fallback 到主线程路径 -- [x] direct worker 单测 -- [x] presenter fallback 单测 - -### T8 Worker: Skill Sync - -- [x] `SkillSyncPresenter` scan / compare 迁到 worker -- [x] worker failure fallback 到主线程路径 -- [x] direct worker 单测 -- [x] presenter fallback 单测 -- [x] worker dependency resolution 固定锚到 bundle path,而不是 `process.cwd()` - -## 文档同步 - -- [x] 更新 `spec.md` -- [x] 更新 `plan.md` -- [x] 更新 `tasks.md` -- [x] 更新 `acceptance.md` - -## 最终校验 - -- [x] `pnpm run format` -- [x] `pnpm run i18n` -- [x] `pnpm run lint` -- [x] `pnpm run typecheck` -- [x] 处理与本轮无关的自动拉新资源变更 - -## 后续 Follow-up - -以下不算本轮 blocker,但保留给下一轮: - -- [ ] main / splash 统一 startup trace -- [ ] `utilityProcess` 在 long-lived runtime 上的替换评估 -- [ ] 更完整的 heavy settings route skeleton 自动化测试 diff --git a/docs/architecture/sync-config-import-versioning/plan.md b/docs/architecture/sync-config-import-versioning/plan.md deleted file mode 100644 index 116cf2b81..000000000 --- a/docs/architecture/sync-config-import-versioning/plan.md +++ /dev/null @@ -1,24 +0,0 @@ -# Sync Config Import Versioning Plan - -## Architecture - -- Add a small sync config import service that parses legacy JSON config files and writes them to - `ConfigTables`. -- Keep `agent.db` as the source of truth for v2 config backups. -- Use the backup manifest for version routing: missing/v1 is legacy, v2 is current, future versions - are rejected. - -## Import Behavior - -- For v2 backups, import SQLite tables using the existing database import flow. -- For legacy backups, import conversations as before, then import old config files into SQLite. -- Increment mode only fills missing config rows. Overwrite mode replaces the corresponding config - tables from backup data. -- Imported legacy config marks the config migration as applied so startup does not re-import stale - JSON over the restored SQLite tables. - -## Export Behavior - -- Write `manifest.version = 2`, `configStorage = sqlite`, and `configSchemaVersion = 1`. -- Export `agent.db`, lightweight app settings, custom prompts, and system prompts. -- Do not export `mcp-settings.json` or migrated app-settings keys. diff --git a/docs/architecture/sync-config-import-versioning/spec.md b/docs/architecture/sync-config-import-versioning/spec.md deleted file mode 100644 index d0c8ed760..000000000 --- a/docs/architecture/sync-config-import-versioning/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# Sync Config Import Versioning - -## User Story - -DeepChat sync backups must preserve the SQLite-backed Provider, Model, MCP, and Agent-adjacent -configuration while still importing old backups that stored those settings in JSON files. - -## Acceptance Criteria - -- New backups use manifest version 2 and declare SQLite-backed config storage. -- New backups do not export migrated configuration in legacy JSON files. -- Old backups import legacy Provider, Model, MCP, and ACP residual settings into SQLite config - tables. -- Increment imports preserve local config when IDs conflict. -- Overwrite imports replace config with backup data while preserving local sync settings. -- Backups with unsupported future versions fail before local data is changed. - -## Non-Goals - -- Do not change renderer routes or sync APIs. -- Do not add a separate config-v2 JSON export. -- Do not migrate prompts or knowledge configs into SQLite in this change. diff --git a/docs/architecture/sync-config-import-versioning/tasks.md b/docs/architecture/sync-config-import-versioning/tasks.md deleted file mode 100644 index fe7342eb9..000000000 --- a/docs/architecture/sync-config-import-versioning/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Sync Config Import Versioning Tasks - -- [x] Add manifest v2 parsing and unsupported-version handling. -- [x] Add legacy config parser/importer for app-settings, MCP, model config, provider models, and ACP residual settings. -- [x] Update SyncPresenter import/export flow to call the importer and keep JSON lightweight. -- [x] Add sync/config import tests and unsupported-version coverage. -- [x] Add i18n key for unsupported backup versions. -- [x] Run format, i18n, lint, typecheck, and targeted tests. diff --git a/docs/archives/agent-cleanup/plan.md b/docs/archives/agent-cleanup/plan.md deleted file mode 100644 index 9e6bd3b8a..000000000 --- a/docs/archives/agent-cleanup/plan.md +++ /dev/null @@ -1,43 +0,0 @@ -# Agent Cleanup Final Plan State - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Summary - -The cleanup is no longer paused. The runtime retirement slice completed on March 23, 2026. - -## Completed Order - -1. moved shared runtime helpers out of legacy presenter folders -2. moved active renderer chat path off legacy message protocol -3. archived dead renderer path code -4. persisted new-session skills in `new_sessions.active_skills` -5. removed legacy global presenter access -6. retired legacy `AgentPresenter` runtime and public exposure -7. migrated retained ACP/agent-tool helpers into current live modules -8. archived retired source/tests and refreshed active docs - -## Keep For Now - -- `LegacyChatImportService` -- legacy import hook / status tracking -- old `conversations/messages` tables as import-only or export-facing sources -- `SessionPresenter` as internal compatibility/data adapter -- `scripts/agent-cleanup-guard.mjs` - -## Follow-up Order - -When cleanup resumes, use this order: - -1. clear remaining export-only / non-active-path type coupling -2. inventory and reduce adjacent provider globals -3. normalize older specs/docs that still mention retired paths where useful -4. only then consider deeper removal of legacy import-era tables - -## Default Rules - -1. Do not reintroduce retired runtime entrypoints. -2. Prefer archiving dead code before hard deletion. -3. Keep import-only compatibility separate from active runtime design. -4. Update SDD documents in the same slice when retiring architecture. diff --git a/docs/archives/agent-cleanup/spec.md b/docs/archives/agent-cleanup/spec.md deleted file mode 100644 index d23d7fcc4..000000000 --- a/docs/archives/agent-cleanup/spec.md +++ /dev/null @@ -1,68 +0,0 @@ -# Agent Cleanup - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Summary - -The cleanup reached final runtime retirement on March 23, 2026. - -Current primary flow: - -- renderer active chat pages/stores/components -- `agentSessionPresenter` -- `agentRuntimePresenter` -- `toolPresenter` -- `llmProviderPresenter` - -Completed in this retirement slice: - -- retired live legacy `AgentPresenter` runtime wiring -- removed public `agentPresenter` / `sessionPresenter` IPC exposure -- removed `ILlmProviderPresenter.startStreamCompletion()` -- migrated retained ACP helpers into `src/main/presenter/llmProviderPresenter/acp/` -- migrated retained agent tools into `src/main/presenter/toolPresenter/agentTools/` -- moved retained user message formatting helpers into `src/main/presenter/sessionPresenter/` -- removed retired source and tests from the active tree, with history preserved in docs - -## Compatibility Boundary - -The supported compatibility boundary is now: - -- keep `LegacyChatImportService` -- keep legacy import hook / status tracking -- keep old `conversations/messages` tables as import-only or export-facing sources -- keep `SessionPresenter` as a main-internal compatibility/data facade only - -The new primary flow must not regain runtime ownership from retired `AgentPresenter`. - -## Guardrails - -`scripts/agent-cleanup-guard.mjs` remains the anti-regression guard. - -It now protects these invariants: - -1. new main-path modules must not import retired legacy presenter runtime paths -2. active renderer chat path must not reintroduce `@shared/chat` -3. provider-layer code must not regain retired legacy fallbacks -4. `SkillPresenter` and MCP gating must not regain retired legacy global access -5. retired runtime code stays archived rather than silently re-entering the live tree - -## Completed Milestones - -- shared helper ownership moved to `src/main/lib/agentRuntime` -- active renderer chat path moved off legacy message protocol -- dead renderer and mock/orphan code removed from the active tree -- new-session skills persisted in `new_sessions.active_skills` -- legacy `agentPresenter/**` removed from global presenter access -- provider-layer MCP global access removed -- final legacy runtime retirement completed and documented - -## Remaining Backlog - -The remaining work is no longer runtime-retirement work. It is adjacent cleanup only: - -- export-only `@shared/chat` coupling in `agentSessionPresenter` -- non-active renderer residual import in `PromptEditorSheet` -- adjacent provider globals such as `devicePresenter` / `oauthPresenter` -- optional archival/normalization of older specs that still mention retired paths diff --git a/docs/archives/agent-cleanup/tasks.md b/docs/archives/agent-cleanup/tasks.md deleted file mode 100644 index 65aaf1c28..000000000 --- a/docs/archives/agent-cleanup/tasks.md +++ /dev/null @@ -1,45 +0,0 @@ -# Agent Cleanup Final Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Completed - -- [x] Added cleanup docs and static guardrails -- [x] Moved shared runtime helpers out of legacy presenter folders -- [x] Moved active renderer chat path off `@shared/chat` -- [x] Removed dead renderer path code from the active tree -- [x] Removed renderer mock/orphan code from the active tree -- [x] Persisted new-session skills in `new_sessions.active_skills` -- [x] Retired old-session skill fallback to legacy conversation settings -- [x] Removed global `presenter.*` access from legacy runtime modules -- [x] Removed provider-layer `presenter.mcpPresenter` access -- [x] Removed live legacy `AgentPresenter` runtime wiring -- [x] Removed public `agentPresenter` / `sessionPresenter` IPC exposure -- [x] Removed `ILlmProviderPresenter.startStreamCompletion()` -- [x] Migrated retained ACP helpers to `src/main/presenter/llmProviderPresenter/acp/` -- [x] Migrated retained agent tools to `src/main/presenter/toolPresenter/agentTools/` -- [x] Migrated retained message formatting helper to `src/main/presenter/sessionPresenter/` -- [x] Removed retired source and tests from the active tree and preserved history in docs -- [x] Refreshed active architecture / flow / navigation docs - -## Kept Intentionally - -- [x] `LegacyChatImportService` -- [x] legacy import hook / status tracking -- [x] old `conversations/messages` tables -- [x] `SessionPresenter` as internal compatibility/data adapter -- [x] `scripts/agent-cleanup-guard.mjs` - -## Remaining Backlog - -- [ ] remove export-only `@shared/chat` coupling in `src/main/presenter/agentSessionPresenter/index.ts` -- [ ] remove non-active renderer residual import in `PromptEditorSheet` -- [ ] review adjacent provider globals such as `devicePresenter` / `oauthPresenter` -- [ ] normalize older historical specs that still mention retired legacy paths - -## Historical Cleanup Batches - -- [x] dead renderer batch -- [x] mock / orphan UI batch -- [x] legacy agent runtime batch diff --git a/docs/archives/agent-tooling-v2/plan.md b/docs/archives/agent-tooling-v2/plan.md deleted file mode 100644 index 3fe692d54..000000000 --- a/docs/archives/agent-tooling-v2/plan.md +++ /dev/null @@ -1,398 +0,0 @@ -# Agent Tooling V2 实施计划(Main Loop 优先) - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 当前实现基线 - -### 1.1 工具路由 - -主路由为: - -1. `ToolPresenter` 统一汇总和路由工具 - `src/main/presenter/toolPresenter/index.ts` -2. Agent 本地工具由 `AgentToolManager` 管理 - `src/main/presenter/agentPresenter/acp/agentToolManager.ts` -3. 文件能力由 `AgentFileSystemHandler` 执行 - `src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts` - -### 1.2 Loop 与事件 - -1. 生成与工具调度:`AgentLoopHandler` + `ToolCallProcessor` - `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts` - `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` -2. 主事件类型:`LLMAgentEventData` - `src/shared/types/core/agent-events.ts` -3. 对 renderer 推送由 `StreamUpdateScheduler` 聚合 - `src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts` - -### 1.3 Skills - -1. active skills 与 allowedTools 来源:`SkillPresenter` - `src/main/presenter/skillPresenter/index.ts` -2. 当前 allowedTools 未做统一 canonical 归一化。 - -### 1.4 实施边界(补充) - -本计划仅涉及: - -1. `src/main/presenter/agentPresenter/acp/agentToolManager.ts` -2. `src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts` -3. `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` -4. `src/main/presenter/skillPresenter/index.ts`(仅 allowedTools 归一化接入) -5. `src/main/presenter/toolPresenter/index.ts`(tool prompt 与路由提示) -6. `src/main/presenter/agentPresenter/message/messageBuilder.ts`(system prompt 拼接) -7. `src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts`(skills allowedTools 接入) -8. `src/main/lib/agentRuntime/systemEnvPromptBuilder.ts`(env prompt 生成) -9. 与上述模块直接相关的测试与文档 - -明确不改动: - -1. `src/main/presenter/browser/**` -2. `src/main/presenter/agentPresenter/acp/chatSettingsTools.ts` -3. `src/main/lib/agentRuntime/questionTool.ts` -4. `src/main/presenter/mcpPresenter/**` - -## 2. 设计决策 - -### 2.1 工具命名策略 - -决策:使用固定 canonical 工具名,不保留旧名兼容调用。 - -canonical: - -1. `read` -2. `write` -3. `edit` -4. `find` -5. `grep` -6. `ls` -7. `exec` -8. `process` - -实现点: - -1. `AgentToolManager.fileSystemSchemas` 直接改名。 -2. `isFileSystemTool()`、`callFileSystemTool()`、`collectWriteTargets()` 全量切换新名。 -3. `ToolCallProcessor.TOOLS_REQUIRING_OFFLOAD` 同步新名。 - -旧工具删除清单(本次落地必须删除): - -1. `read_file` -2. `write_file` -3. `list_directory` -4. `create_directory` -5. `move_files` -6. `edit_text` -7. `glob_search` -8. `directory_tree` -9. `get_file_info` -10. `grep_search` -11. `text_replace` -12. `edit_file` -13. `execute_command` - -### 2.1.1 Canonical 参数收敛 - -决策:参数名统一,删除旧参数别名;一处校验,多处复用。 - -参数标准: - -1. `read`: `path`, `offset?`, `limit?` -2. `write`: `path`, `content` -3. `edit`: `path`, `oldText`, `newText`, `replaceAll?` -4. `find`: `pattern`, `path?`, `maxResults?`, `exclude?` -5. `grep`: `pattern`, `path?`, `filePattern?`, `caseSensitive?`, `contextLines?`, `maxResults?` -6. `ls`: `path`, `depth?` -7. `exec`: `command`, `cwd?`, `timeoutMs?`, `background?`, `yieldMs?` -8. `process`: `action`, `sessionId?`, `offset?`, `limit?`, `data?`, `eof?` - -落地原则: - -1. schema 校验失败直接返回 `INVALID_ARGUMENT`,不执行工具。 -2. 不再接受 `old_string/new_string` 等 alias。 -3. skills 映射只做工具名映射,不改写工具参数。 -4. 不引入 `allowParallel` 等并行开关参数,避免工具语义分裂。 - -### 2.1.2 工具返回 envelope - -决策:工具返回统一为“可读摘要 + 结构化数据”。 - -约定: - -1. `content`: 给模型看的短摘要。 -2. `rawData.toolResult`: 结构化对象,至少包含 `ok`。 -3. 查询类工具(`read/find/grep/ls/process(log)`)补充 `meta`(截断、分页)。 -4. 写入类工具(`write/edit/exec/process(write|kill...)`)补充 `affectedPaths` 或 `sessionId` 等执行结果元信息。 - -### 2.2 Skills 工具映射层 - -决策:新增“skills allowedTools canonicalizer”,在 skills 到运行时工具过滤的边界做归一化。 - -建议新增模块: - -`src/main/presenter/skillPresenter/toolNameMapping.ts` - -提供: - -1. `normalizeSkillToolName(toolName: string): { canonical: string; mapped: boolean }` -2. `normalizeSkillAllowedTools(tools: string[]): { tools: string[]; warnings: string[] }` - -首批映射(Claude Code 优先): - -1. `Read -> read` -2. `Write -> write` -3. `Edit -> edit` -4. `MultiEdit -> edit` -5. `Glob -> find` -6. `Grep -> grep` -7. `LS -> ls` -8. `Bash -> exec` - -接入点: - -1. `SkillPresenter.getActiveSkillsAllowedTools()` 返回前归一化。 -2. 归一化 warning 通过日志输出,避免静默丢失。 - -### 2.3 rg 增强策略 - -决策:`find/grep` 均采用 “rg 优先,fallback 次级实现”。 - -1. `find` - - 优先:`rg --files` + `-g` include/exclude - - 回退:`glob` -2. `grep` - - 优先:现有 `runRipgrepSearch` 路径继续强化结构化输出 - - 回退:现有 JS grep 路径 - -约束: - -1. 保持 `maxResults` 生效,返回截断信息。 -2. 统一默认 ignore 集。 -3. 当 `rg` 调用异常时必须记录告警并稳定回退。 - -### 2.4 事件与消息格式定版(main 导出) - -决策:不在本阶段改事件通道,但冻结字段约束。 - -事件: - -1. `stream:response` -2. `stream:error` -3. `stream:end` - -`stream:response` 内 tool 事件规范: - -1. 公共字段:`eventId`, `stream_kind`, `seq` -2. tool 事件必备: - - `tool_call` - - `tool_call_id` - - `tool_call_name` -3. permission 事件附加: - - `permission_request.toolName` - - `permission_request.serverName` - - `permission_request.permissionType` - - `permission_request.description` - -字段样例: - -1. 文本增量: - -```json -{ - "eventId": "evt_123", - "stream_kind": "delta", - "seq": 7, - "content": "partial text" -} -``` - -2. 工具执行中: - -```json -{ - "eventId": "evt_123", - "tool_call": "running", - "tool_call_id": "call_1", - "tool_call_name": "grep", - "tool_call_params": "{\"pattern\":\"TODO\",\"path\":\"src\"}", - "tool_call_server_name": "agent-filesystem" -} -``` - -3. 工具执行结束: - -```json -{ - "eventId": "evt_123", - "tool_call": "end", - "tool_call_id": "call_1", - "tool_call_name": "grep", - "tool_call_response": "Found 3 matches in 2 files", - "tool_call_response_raw": { - "toolResult": { - "ok": true, - "summary": "Found 3 matches in 2 files", - "data": { - "matches": [] - }, - "meta": { - "truncated": false - } - } - } -} -``` - -4. 权限请求: - -```json -{ - "eventId": "evt_123", - "tool_call": "permission-required", - "tool_call_id": "call_2", - "tool_call_name": "write", - "permission_request": { - "toolName": "write", - "serverName": "agent-filesystem", - "permissionType": "write", - "description": "Write access requires approval." - } -} -``` - -5. 提问请求: - -```json -{ - "eventId": "evt_123", - "tool_call": "question-required", - "tool_call_id": "call_3", - "tool_call_name": "deepchat_question", - "question_request": { - "question": "Select environment", - "choices": ["dev", "prod"] - } -} -``` - -说明:renderer 暂不改,仅作为后续改造输入契约。 - -### 2.5 Prompt 管线与调用策略更新(main) - -决策:固定 system prompt 管线,避免动态状态碎片化与缓存抖动。 - -改动点: - -1. `src/main/presenter/toolPresenter/index.ts` - - `buildToolSystemPrompt()` 增加 canonical 工具清单与“意图到工具”选择规则。 -2. `src/main/presenter/agentPresenter/message/messageBuilder.ts` - - 保证 system prompt 固定顺序拼接。 -3. `src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts` - - `getSkillsAllowedTools()` 使用 canonicalized allowed tools。 -4. `src/main/lib/agentRuntime/systemEnvPromptBuilder.ts` - - 统一生成 env prompt(模型、系统、仓库、AGENTS.md)。 - -拼接顺序(固定): - -1. conversation `systemPrompt` -2. Runtime 简要说明段(YoBrowser/后台进程能力说明,静态) -3. Skills Prompt(metadata + active skills) -4. Env Prompt(模型名/模型 ID/工作目录/git/platform/date/AGENTS.md 全文) -5. Tooling Prompt(canonical 规则) - -约束: - -1. 不在 system prompt 注入 YoBrowser 当前 tab 或后台进程实时列表。 -2. `enhanceSystemPromptWithDateTime` 的运行态信息迁移至统一 env prompt。 -3. Tooling Prompt 保留独立段,不并入 env prompt。 - -### 2.6 Loop 执行顺序收敛 - -统一流程: - -1. LLM 产出 tool calls -2. `batchPreCheckPermissions()` 批量权限预检 -3. 发出 `permission-required`(若有)并暂停 loop -4. 逐个 tool 执行,发 `running -> end/error` -5. 大输出按 offload 规则写入 session 文件并返回 stub -6. 继续下一轮推理或结束 - -## 3. 实施阶段 - -### Phase 1:工具面切换(无兼容) - -1. 重命名并收敛 tool schemas + definitions。 -2. 切换 handler 分派逻辑。 -3. 删除旧工具名引用(包括测试、文档、skills 默认示例)。 -4. 回归确认 browser/skills/settings/question 工具定义与行为无变更。 - -### Phase 2:Skills 映射 - -1. 新增映射模块与单元测试。 -2. 接入 `getActiveSkillsAllowedTools()`。 -3. 在 skill sync 适配器层保持原始值,运行时归一化。 - -### Phase 3:rg 增强 - -1. `find` 引入 rg 分支(若尚未实现)。 -2. `grep` 完善 rg 结果结构化(命中文件/行号/上下文/截断)。 -3. 完善 fallback 与错误日志。 - -### Phase 4:Prompt 与协议导出 - -1. 新增统一 env prompt builder 并接入 messageBuilder。 -2. 调整 system prompt 固定拼接顺序(含 runtime 静态说明与 skills)。 -3. 更新 main 侧 tool prompt(仅 canonical + 调用规则)。 -4. 增加消息/事件契约测试,确保字段不回归。 -5. 回退 `allowParallel` 相关参数/逻辑与测试预期。 - -## 4. 数据与配置影响 - -1. **破坏性变化(明确)**:旧工具名不可再调用。 -2. 不涉及数据库 schema 迁移。 -3. Skills 的 `allowedTools` 原始存储不强制改写,仅在运行时归一化。 - -## 5. 测试策略 - -### 5.1 单元测试 - -1. `AgentToolManager`: - - 工具定义仅包含 canonical 名。 - - 旧工具名调用报错。 - - canonical 参数 schema 校验正确。 -2. Skills 映射: - - Claude Code 常见工具名映射正确。 - - 未知工具产生 warning。 -3. `AgentFileSystemHandler`: - - `find/grep` 在 rg 可用与不可用分支均可运行。 -4. Prompt 组装: - - tool prompt 仅包含 canonical 工具名。 - - 不包含旧工具名提示。 - - system prompt 顺序固定且可断言。 - - env prompt 包含 AGENTS.md 全文与关键环境字段。 - -### 5.2 集成测试 - -1. loop 工具调用事件序列:start/running/end。 -2. permission-required 负载完整性。 -3. offload 与大输出行为在新工具名下仍生效。 -4. toolResult envelope 字段(`ok/summary/data/meta`)在关键工具路径可观测。 - -## 6. 风险与缓解 - -1. 风险:旧 prompt 或 skill 仍调用旧工具名导致失败。 - 缓解:在系统 prompt 与 skills metadata prompt 中明确仅 canonical 名。 - -2. 风险:不同平台 rg 参数兼容性差异。 - 缓解:统一封装 rg 参数构造,Windows/Linux/macOS 加测试样例。 - -3. 风险:skills 映射冲突导致过度归并。 - 缓解:映射表版本化,保留原值告警,必要时支持精细映射策略。 - -## 7. 质量门槛(DoD) - -1. `pnpm run format` -2. `pnpm run lint` -3. `pnpm run typecheck` -4. 关键 main 测试通过(tool/loop/permission/skills 映射相关) diff --git a/docs/archives/agent-tooling-v2/spec.md b/docs/archives/agent-tooling-v2/spec.md deleted file mode 100644 index 44fc64097..000000000 --- a/docs/archives/agent-tooling-v2/spec.md +++ /dev/null @@ -1,300 +0,0 @@ -# Agent Tooling V2(Main Loop 优先) - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 背景 - -当前 main 层 agent 工具体系存在以下问题: - -1. 工具命名与主流 Agent 生态不一致(大量下划线命名、语义重叠)。 -2. 文件工具数量偏多且参数风格不统一,模型选工具与组参成本高。 -3. Skills 来源多样(尤其 Claude Code),`allowedTools` 与 DeepChat 当前工具名不直接兼容。 -4. 文件匹配/检索能力未充分利用 `rg`,在大型仓库下性能和结果质量不稳定。 -5. loop 已基本可运行,但“对 renderer 输出的消息/事件格式”尚未形成明确、稳定的主协议。 - -本规格聚焦 main 层:先让 loop + tool 协议稳定、清晰、可推理,再推进 renderer 适配。 - -## 目标 - -1. **工具面收敛**:仅保留 8 个主工具(不兼容旧名)。 -2. **Skills 工具映射**:引入标准映射层,重点支持 Claude Code 工具名。 -3. **文件匹配增强**:`find/grep` 优先使用 `rg`,大仓库性能可预期。 -4. **协议清晰**:导出 main 层稳定的消息格式与事件格式,供 renderer 后续接入。 -5. **Prompt 管线稳定**:system prompt 拼接顺序固定,避免随意动态段影响缓存命中。 -6. **环境信息统一**:模型/系统/仓库/AGENTS.md 信息统一由 `env prompt` 生成。 - -## 非目标 - -1. 本阶段不改 renderer 逻辑与 UI 交互。 -2. 不保留旧工具名的后向兼容(不做 alias call)。 -3. 不重构 MCP 协议与第三方 provider 的底层实现。 -4. 不引入新的权限类型(仍为 `read|write|all|command`)。 -5. 不在 system prompt 中注入 YoBrowser 当前 tab 明细或后台进程实时列表。 - -## 范围边界(补充) - -本次优化仅覆盖: - -1. 文件操作工具(fs) -2. runtime 工具(命令执行与后台进程管理) -3. 上述工具在 main loop 中的调用、权限与事件输出 - -本次**不改动**: - -1. browser 相关工具(如 `yo_browser_*`) -2. skills 管理工具(如 `skill_list`、`skill_control`) -3. DeepChat 设置类工具(如 `deepchat_settings_*`) -4. 提问工具(`deepchat_question`) -5. MCP 工具本体及其服务端协议 - -## 工具集合(V2 Canonical) - -固定为以下 8 个工具: - -1. `read` -2. `write` -3. `edit` -4. `find` -5. `grep` -6. `ls` -7. `exec` -8. `process` - -## Canonical 参数定义(V2) - -### A. 文件工具(fs) - -| 工具 | 必填参数 | 可选参数 | 说明 | -|---|---|---|---| -| `read` | `path: string` | `offset?: number`, `limit?: number` | 单文件读取;大文件分页读取。 | -| `write` | `path: string`, `content: string` | 无 | 创建或覆盖文件。 | -| `edit` | `path: string`, `oldText: string`, `newText: string` | `replaceAll?: boolean` | 精确文本替换;仅接受 canonical 参数名。 | -| `find` | `pattern: string` | `path?: string`, `maxResults?: number`, `exclude?: string[]` | 文件匹配,优先 `rg --files`。 | -| `grep` | `pattern: string` | `path?: string`, `filePattern?: string`, `caseSensitive?: boolean`, `contextLines?: number`, `maxResults?: number` | 内容搜索,优先 `rg`。 | -| `ls` | `path: string` | `depth?: number` | 列目录(默认浅层)。 | - -### B. 运行时工具(runtime) - -| 工具 | 必填参数 | 可选参数 | 说明 | -|---|---|---|---| -| `exec` | `command: string` | `cwd?: string`, `timeoutMs?: number`, `background?: boolean`, `yieldMs?: number` | 命令执行;前台仅等待 yield 窗口,超时后自动转后台并返回 `sessionId`。 | -| `process` | `action: enum` | `sessionId?: string`, `offset?: number`, `limit?: number`, `data?: string`, `eof?: boolean` | 后台会话管理(list/poll/log/write/kill/clear/remove)。 | - -约束: - -1. 不再接受旧参数别名(如 `old_string/new_string`),只保留 canonical 参数名。 -2. 参数校验失败必须返回结构化错误,不进入工具执行。 -3. `exec` 不引入 `allowParallel` 参数。 - -## 工具返回格式(统一) - -所有 canonical 工具返回统一 envelope(`content` 可读摘要 + `rawData.toolResult` 结构化数据): - -```json -{ - "ok": true, - "summary": "Found 12 matches in 3 files", - "data": {}, - "meta": { - "truncated": false - } -} -``` - -错误格式: - -```json -{ - "ok": false, - "error": { - "code": "INVALID_ARGUMENT", - "message": "path is required" - } -} -``` - -说明: - -1. `content` 供模型快速理解,`rawData.toolResult` 供 loop/renderer 稳定消费。 -2. `find/grep/read` 需包含分页或截断信息(如 `returned`, `total`, `nextOffset`)。 - -## 待删除旧工具清单(用于边界确认) - -以下旧工具将从 agent 文件/运行时工具定义中移除: - -1. `read_file` -2. `write_file` -3. `list_directory` -4. `create_directory` -5. `move_files` -6. `edit_text` -7. `glob_search` -8. `directory_tree` -9. `get_file_info` -10. `grep_search` -11. `text_replace` -12. `edit_file` -13. `execute_command` - -说明: - -1. `process` 不删除,保留并归入 runtime canonical 集合。 -2. 上述删除仅针对本地 Agent 工具定义层,不影响外部 MCP server 自带同名工具。 - -## Prompt 约束(main) - -目标:降低模型选错工具/组参错误率,减少多余调用。 - -1. 系统提示中只暴露 canonical 工具名与参数摘要,不出现旧名。 -2. 增加工具选择规则(按意图): - - 定位文件:`find` / `ls` - - 搜索内容:`grep` - - 读取内容:`read` - - 精确修改:`edit` - - 整体写入:`write` - - 执行命令:`exec` + `process` -3. 增加调用策略: - - 先查找再读取,再修改(`find/grep -> read -> edit/write`) - - 优先小步调用,避免一次返回超大输出 -4. 对无效工具名统一返回:`Unknown Agent tool: `,不做自动别名纠正。 - -## System Prompt 组装顺序(V2.1) - -`conversation.settings.systemPrompt` 之后固定拼接顺序: - -1. **Runtime 简要说明段**(静态说明) - - 仅说明 YoBrowser 能力和后台进程能力。 - - 不注入当前 tab 列表、当前 active tab、当前运行进程明细等动态快照。 -2. **Skills Prompt 段** - - 含 skills metadata + active skills 内容。 -3. **Env Prompt 段** - - 统一封装环境信息,格式稳定。 - - 包含:模型名、模型 ID、工作目录、是否 git 仓库、平台、日期。 - - 包含 `AGENTS.md` 的具体内容(全文)。 -4. **Tooling Prompt 段** - - 继续保留独立工具调用规则段(canonical 工具名 + 推荐调用顺序)。 - -说明: - -1. Env Prompt 统一由独立 builder 生成,不在多个模块分散拼接。 -2. 运行态动态信息不进 system prompt,避免提示词频繁变化影响缓存。 - -Env Prompt 参考格式: - -```text -You are powered by the model named . -The exact model ID is / -Here is some useful information about the environment you are running in: - -Working directory: -Is directory a git repo: yes|no -Platform: -Today's date: - - - - -Instructions from: /AGENTS.md - -``` - -## 用户故事 - -### US-1:模型能更稳定选对工具 -作为 agent 用户,我希望模型面对文件任务时优先在 6 个文件工具中做确定选择,而不是在多个重叠工具之间摇摆。 - -### US-2:Claude Code Skills 可直接复用 -作为多工具用户,我希望导入 Claude Code skills 后,`allowed-tools` 能自动映射到 DeepChat 的 canonical 工具,不需要手工改名。 - -### US-3:大型仓库下检索稳定 -作为 agent 用户,我希望 `find/grep` 在大仓库下保持高性能和一致结果,优先走 `rg`。 - -### US-4:事件协议可作为 renderer 改造输入 -作为开发者,我希望 main 层先产出稳定的事件与消息格式,后续 renderer 可按协议接入,不再反复追 main 内部状态细节。 - -## Skills 工具映射(重点:Claude Code) - -定义 canonical 映射(首批): - -| 外部工具名(Claude Code 常见) | DeepChat Canonical | -|---|---| -| `Read` | `read` | -| `Write` | `write` | -| `Edit` | `edit` | -| `MultiEdit` | `edit` | -| `Glob` | `find` | -| `Grep` | `grep` | -| `LS` | `ls` | -| `Bash` | `exec` | - -说明: - -1. 该映射用于 `skills allowedTools` 归一化,不影响 MCP 原生工具名。 -2. 无法映射的工具名保留原值并标记 warning(不静默丢失)。 -3. 映射后再参与“工具可用性过滤”。 - -## 文件匹配增强(rg 优先) - -1. `find`:优先 `rg --files` + `-g` 模式过滤(含排除规则),不可用时回退 `glob`。 -2. `grep`:优先 `rg`(支持行号、上下文、max results),不可用时回退 JS 扫描。 -3. 输出必须包含可消费的结构化元信息(命中数、文件数、截断标识)。 -4. 默认忽略目录保持统一:`.git`、`node_modules`、`dist`、`build`、`.next`(可扩展)。 - -## 消息与事件格式(main 导出) - -本阶段定义并冻结 main 输出契约(renderer 暂不改): - -1. 仍通过 `STREAM_EVENTS.RESPONSE/ERROR/END` 发送。 -2. `tool_call` 状态集合保持:`start|running|update|end|error|permission-required|question-required`。 -3. 明确字段稳定性: - - 必带:`eventId` - - tool 事件必带:`tool_call_id`, `tool_call_name` - - 权限事件必带:`permission_request.toolName/serverName/permissionType/description` -4. 在 `docs/archives/agent-tooling-v2/plan.md` 给出字段级 schema 约束与 JSON 样例(text/reasoning/tool/permission/question/end)。 - -## 约束 - -1. 安全边界:文件访问必须受 workspace/approved paths/conversation session 限制。 -2. 权限门闩:遇到 permission-required 必须暂停,遵循已落地的 permission stabilization 语义。 -3. 输出控制:工具大输出继续遵循 offload guardrails,不向模型直接注入超大文本。 - -## 验收标准 - -### A. 工具面 - -1. agent 工具定义列表只包含 8 个 canonical 工具名(加业务工具如 `deepchat_question`、skills 管理工具不在本条约束内)。 -2. 旧文件工具名(如 `read_file/write_file/edit_file/...`)不再出现在工具定义中。 -3. 调用旧工具名返回明确错误:`Unknown Agent tool`。 -4. `yo_browser_*`、`skill_*`、`deepchat_settings_*` 的可见性与行为保持不变。 - -### B. Skills 映射 - -1. Claude Code `allowed-tools` 输入可映射到 canonical 工具。 -2. `MultiEdit -> edit`、`Bash -> exec`、`Glob -> find` 等关键映射可通过测试验证。 -3. 未知工具名不会被静默吞掉,存在可观测 warning。 - -### C. rg 增强 - -1. `find` 在可用 `rg` 时走 `rg` 分支;`rg` 不可用时自动回退 `glob`。 -2. `grep` 在可用 `rg` 时走 `rg` 分支;`rg` 不可用时自动回退 JS。 -3. 大仓库场景下 `find/grep` 都有 `maxResults` 截断行为并返回截断信息。 - -### D. 协议 - -1. `STREAM_EVENTS.RESPONSE` 中 tool 相关字段满足 plan 中定义的 schema。 -2. `permission-required` 事件负载完整且可用于恢复执行链路。 -3. 主流程可导出标准消息样例(text/reasoning/tool/permission/question/end)。 - -### E. 参数与 Prompt - -1. 所有 canonical 工具参数满足本 spec 的统一定义。 -2. 系统提示仅出现 canonical 工具名,不出现旧工具名或旧参数别名。 -3. 工具参数校验失败可观测(明确错误码/错误信息),且不执行工具副作用操作。 -4. system prompt 拼接顺序严格符合“V2.1 顺序”。 -5. system prompt 中不包含 YoBrowser/后台进程动态状态快照。 -6. env prompt 中包含模型名/模型 ID/工作目录/git 检测/platform/date/AGENTS.md 全文。 - -## 开放问题 - -无。 diff --git a/docs/archives/agent-tooling-v2/tasks.md b/docs/archives/agent-tooling-v2/tasks.md deleted file mode 100644 index 30de05f6b..000000000 --- a/docs/archives/agent-tooling-v2/tasks.md +++ /dev/null @@ -1,60 +0,0 @@ -# Agent Tooling V2 Tasks(Prompt 管线与 Env Prompt) - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## T0 文档先行 - -- [x] 更新 `spec.md`:增加 V2.1 system prompt 固定顺序与 env prompt 约束。 -- [x] 更新 `plan.md`:补充实现边界、顺序、回退 `allowParallel` 要求。 -- [x] 新建 `tasks.md`:形成可执行任务清单。 - -## T1 回退并清理 `allowParallel` - -- [ ] 从 `src/main/presenter/agentPresenter/acp/agentToolManager.ts` 的 `exec` schema 删除 `allowParallel`。 -- [ ] 删除 `exec` description 中关于 `allowParallel` 的文案。 -- [ ] 删除 `exec` 前置并行守卫逻辑和相关辅助方法。 -- [ ] 清理/更新相关测试断言(若涉及)。 - -## T2 新增统一 Env Prompt Builder - -- [ ] 新增 `src/main/lib/agentRuntime/systemEnvPromptBuilder.ts`。 -- [ ] 实现 `buildSystemEnvPrompt(...)`,输出固定格式: - - 模型名 + 模型 ID - - ``:workdir、git repo、platform、date - - `` 空块 - - `Instructions from: /AGENTS.md` - - AGENTS.md 全文(读取失败时输出可观测 fallback) -- [ ] 实现 runtime 静态能力说明 builder(YoBrowser + terminal background)。 - -## T3 调整 Message Builder 拼接顺序 - -- [ ] 在 `src/main/presenter/agentPresenter/message/messageBuilder.ts` 按固定顺序拼接: - 1. conversation `systemPrompt` - 2. runtime 静态能力说明 - 3. skills prompt - 4. env prompt - 5. tooling prompt -- [ ] 移除 YoBrowser 动态状态注入(tab/active tab 实时快照)。 -- [ ] 确认不注入后台进程动态列表。 - -## T4 收敛 Prompt Enhancer 责任 - -- [ ] 更新 `src/main/presenter/agentPresenter/utility/promptEnhancer.ts`,避免重复拼接 runtime/date/platform/workdir 信息。 -- [ ] runtime 环境信息统一由 `systemEnvPromptBuilder` 提供。 - -## T5 测试 - -- [ ] 新增/更新 `messageBuilder` 测试:断言 system prompt 段落顺序。 -- [ ] 新增 `systemEnvPromptBuilder` 测试: - - git yes/no - - AGENTS.md 存在/不存在 - - model name/model id 回退逻辑 -- [ ] 更新 `agentToolManager` 测试:确保 `allowParallel` 不可用。 - -## T6 验证 - -- [ ] `pnpm run format` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] 跑关键 main 测试集并记录结果 diff --git a/docs/archives/agentpresenter-mvp-replacement/plan.md b/docs/archives/agentpresenter-mvp-replacement/plan.md deleted file mode 100644 index 7a69dc7d6..000000000 --- a/docs/archives/agentpresenter-mvp-replacement/plan.md +++ /dev/null @@ -1,151 +0,0 @@ -# AgentPresenter 全量替换(MVP)实施计划 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 当前基线(Updated 2026-02-28) - -1. 新旧双栈并存:`agentSessionPresenter/agentRuntimePresenter` 与旧 `sessionPresenter/useChatStore` 同时存在。 -2. 新 loop 已可运行,**streaming 和 message persistence 已完成**,但**权限流程完全缺失**。 -3. 产品方向已确定:MVP 先替换核心能力,再完成 chat 模式彻底移除。 -4. **关键发现**:`agentRuntimePresenter/dispatch.ts` 的 `executeTools()` 直接调用工具,**无任何权限检查**。 - -## 2. 核心架构决策 - -1. 会话真源:`new_sessions + deepchat_sessions`。 -2. 消息真源:`deepchat_messages`。 -3. 主执行链路:`agentSessionPresenter -> agentRuntimePresenter`。 -4. 新 UI 页面不再依赖 `useChatStore` 与旧 `sessionPresenter` 主流程。 -5. variants 本轮下线,fork 保留为唯一分叉能力。 - -## 3. 权限与数据模型决策 - -### 3.1 Session 权限模式 - -- `new_sessions` 增加 `permission_mode`:`default | full`,默认 `default`。 -- `permission_mode` 为 session 级别配置,不是全局配置。 - -### 3.2 Full access 规则 - -- 启用前置条件:`session.projectDir` 必须非空。 -- 若 `projectDir` 为空,UI 禁用 `Full access` 并提示先绑定 workspace。 -- `Full access` 仅自动通过 `projectDir` 内请求,越界请求统一拒绝。 - -### 3.3 Default 规则 - -- 走显式权限确认流程。 -- 白名单粒度:`sessionId + toolName + pathPattern`。 -- 判定顺序:先 `projectDir` 边界校验,再执行白名单匹配。 - -## 4. 关键能力设计 - -### 4.1 Workspace 绑定 - -1. 工具执行上下文绑定 `session.projectDir`。 -2. 工具链路统一传递 `conversationId = sessionId`。 -3. 权限消息、审批、执行回执必须在同一 `sessionId` 闭环。 - -### 4.2 编辑历史 user 消息 - -1. API:`editUserMessage(sessionId, messageId, newContent)`。 -2. 行为:编辑后截断目标消息后的全部消息。 -3. 随后自动 regenerate,生成新的 assistant 消息。 - -### 4.3 Retry/Regenerate(无 variants) - -1. API:`retryAssistantMessage(sessionId, messageId)`。 -2. 行为:不创建 variants;仅追加 assistant 消息。 -3. 使用消息边界标记避免旧分支内容污染上下文。 - -### 4.4 Fork - -1. API:`forkSessionFromMessage(sessionId, messageId)`。 -2. 切点定义:从“当前 assistant 消息(含它)”截取。 -3. 新 session 继承必要上下文后可继续对话。 - -## 5. 分阶段迁移 - -### Phase 0:稳定主链路 - -1. 清理新 UI 对 `useChatStore` 的依赖点。 -2. active session 查询与事件分发统一到 `agentSessionPresenter`。 -3. 建立最小回归测试基线。 - -### Phase 1:权限 + Workspace(MVP 核心) - -1. 打通 `ChatStatusBar` 权限模式选择到 session 持久化。 -2. 实现 `Default` 权限审批与白名单命中。 -3. 实现 `Full access` 自动放行 + `projectDir` 越界拒绝。 - -### Phase 2:消息操作(MVP 核心) - -1. 实现历史 user 消息编辑(截断+再生成)。 -2. 实现 retry/regenerate 追加 assistant(无 variants)。 -3. 实现 fork(含当前 assistant)。 - -### Phase 3:设置收敛 - -1. conversation settings UI 与逻辑下线。 -2. agent 默认配置在 session 中落地。 -3. 清理 legacy settings 读取/写入路径。 - -### Phase 4:chat 模式清理 - -1. 类型层移除 `chat`。 -2. UI 与 presenter 中移除 chat 分支。 -3. 完成兼容迁移后删除残留代码。 - -## 6. IPC 与类型面 - -1. `IAgentSessionPresenter` 扩展: - - `setSessionPermissionMode` - - `editUserMessage` - - `retryAssistantMessage` - - `forkSessionFromMessage` -2. shared types 补充: - - `PermissionMode` - - `PermissionWhitelistRule`(含 `toolName` 与 `pathPattern`) -3. preload 暴露新增方法,保持 typed IPC。 - -## 7. 测试策略 - -1. Main 单测:权限边界、白名单匹配、编辑/重试/fork 行为。 -2. 集成测试:从 UI 发起到 tool 执行完整链路。 -3. 迁移回归:旧 session 与旧 chat 数据可正常打开并升级。 - -## 8. 风险与缓解 - -1. 权限误放行风险:main 进程集中校验,默认 deny。 -2. 上下文污染风险:编辑/重试后强制消息边界重算。 -3. 双栈耦合风险:每阶段明确“唯一真源”,禁止双写。 - -## 9. 质量门槛 - -1. `pnpm run format` -2. `pnpm run lint` -3. `pnpm run typecheck` -4. 关键单测与集成测试通过后进入下一阶段 - ---- - -## 10. Implementation Notes (2026-02-28) - -**Critical Discovery**: Permission flow is completely missing from new architecture. - -**Current State**: -- ✅ Streaming infrastructure: COMPLETE -- ✅ Message persistence: COMPLETE -- ✅ Session management: COMPLETE -- ❌ Permission flow: NOT STARTED (CRITICAL) -- ❌ Message operations (edit/retry/fork): NOT STARTED -- ❌ Session configuration: PARTIAL (missing advanced options) - -**Immediate Next Steps**: -1. Create `PermissionChecker` class in `agentRuntimePresenter/` -2. Modify `executeTools()` in `dispatch.ts` to check permissions before tool calls -3. Add `handlePermissionResponse()` IPC method to `agentSessionPresenter` -4. Update `ChatStatusBar.vue` to show permission mode dropdown -5. Add `permission_mode` column to `new_sessions` table -6. Create `permission_whitelists` table for session-scoped whitelists - -See `gap-analysis.md` for complete details. diff --git a/docs/archives/agentpresenter-mvp-replacement/spec.md b/docs/archives/agentpresenter-mvp-replacement/spec.md deleted file mode 100644 index 1f67a2405..000000000 --- a/docs/archives/agentpresenter-mvp-replacement/spec.md +++ /dev/null @@ -1,163 +0,0 @@ -# AgentPresenter 全量替换(MVP)规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 概述 - -以 `agentRuntimePresenter` 新 loop 为唯一核心,分阶段替换旧 chat 体系。MVP 先完成权限、workspace 绑定、消息编辑、retry/regenerate、fork 五个核心能力,再推进设置收敛与 chat 模式清理。 - -## 背景与目标 - -1. 当前新旧链路并存,存在行为不一致与维护成本。 -2. 权限模型需要与新 UI 对齐,并提供明确边界。 -3. conversation settings 需要收敛到 agent 默认配置 + session 落地。 -4. 最终目标是彻底移除 chat 模式,仅保留 agent 路径。 - -## 用户故事 - -### US-1:权限模式选择 - -作为用户,我希望在状态栏选择 `Default` 或 `Full access`,并且配置在当前 session 生效。 - -### US-2:Full access 边界 - -作为用户,我希望 `Full access` 仍受限制,只允许在当前 session 的 `projectDir` 内执行敏感操作。 - -### US-3:编辑历史 user 消息 - -作为用户,我编辑历史 user 消息后,系统应截断后续消息并基于新内容重新生成。 - -### US-4:Retry/Regenerate(无 variants) - -作为用户,我执行 retry/regenerate 时,系统应追加新的 assistant 消息,不走 variants 分支。 - -### US-5:Fork - -作为用户,我希望从“当前 assistant 消息(含它)”切 fork,创建可继续对话的新 session。 - -### US-6:统一 agent 体验 - -作为用户,我希望不再感知 chat 模式,所有会话统一使用 agent 能力。 - -## 验收标准 - -### A. 权限模式 - -- [x] `ChatStatusBar` 可选择 `Default` 与 `Full access`。 -- [x] 权限模式持久化在 session 维度。 -- [x] 当 `session.projectDir` 为空时,`Full access` 不可选并提示先绑定 workspace。 -- [x] `Default` 走权限确认流程,白名单按 `session` 维度隔离。 -- [x] `Default` 白名单匹配粒度为 `toolName + pathPattern`。 -- [x] `Full access` 自动通过请求,但任何越出 `projectDir` 的操作必须拒绝。 - -**Implementation Notes** (added 2026-02-28): -- Add `permission_mode TEXT DEFAULT 'default'` to `new_sessions` table -- ChatStatusBar currently shows read-only "Default permissions" button - must convert to dropdown -- NewThreadPage must pass `permissionMode` to `createSession()` -- Backend must enforce `projectDir` boundary in tool execution - -### B. Workspace 绑定与上下文 - -- [x] 工具执行上下文绑定 `session.projectDir`。 -- [x] 工具调用链路统一传递 `conversationId = sessionId`。 -- [x] 权限判定与消息归属基于同一 `sessionId`。 - -**Implementation Notes** (added 2026-02-28): -- `agentSessionPresenter.createSession()` already passes `projectDir` to session manager -- `agentRuntimePresenter.processStream()` uses `sessionId` throughout -- CRITICAL GAP: `executeTools()` in dispatch.ts does NOT check permissions before calling tools -- Must add `PermissionChecker` class and integrate before tool execution - -### C. 编辑历史 user 消息 - -- [ ] 仅允许编辑 user 消息。 -- [ ] 编辑后删除该消息之后的所有消息。 -- [ ] 自动触发 regenerate,生成新的 assistant 结果。 - -**Implementation Notes** (added 2026-02-28): -- NOT YET IMPLEMENTED in new architecture -- Requires new IPC: `editUserMessage(sessionId, messageId, newContent)` -- Must delete messages with `orderSeq > editedMessage.orderSeq` -- Must trigger `processMessage()` to regenerate -- Frontend: add edit action to user message context menu - -### D. Retry/Regenerate(无 variants) - -- [ ] 不提供 variants 路径。 -- [ ] 每次 retry/regenerate 追加新的 assistant 消息。 -- [ ] 上下文边界正确,避免被替代消息污染后续生成。 - -**Implementation Notes** (added 2026-02-28): -- NOT YET IMPLEMENTED in new architecture -- Requires new IPC: `retryMessage(sessionId, messageId)` -- Must create new assistant message (not replace) -- Must manage message boundaries correctly -- Old architecture: `agentPresenter.retryMessage()` exists but uses variants -- New implementation must skip variants path - -### E. Fork - -- [ ] 支持从 assistant 消息发起 fork。 -- [ ] fork 切点包含当前 assistant 消息本身。 -- [ ] fork 后新 session 消息序列可继续生成。 - -**Implementation Notes** (added 2026-02-28): -- NOT YET IMPLEMENTED in new architecture -- Requires new IPC: `forkSessionFromMessage(sessionId, messageId)` -- Must copy messages up to and including the fork point -- Must create new session with copied context -- Old architecture had fork in sessionPresenter - -### F. 设置收敛 - -- [ ] conversation settings 入口下线。 -- [ ] agent 默认配置生效。 -- [ ] 运行时具体配置数据落到 session。 - -**Implementation Notes** (added 2026-02-28): -- PARTIALLY IMPLEMENTED: new architecture uses agent defaults -- MISSING: session-level configuration (temperature, contextLength, maxTokens) -- `agentSessionPresenter.createSession()` only accepts providerId/modelId -- Must extend CreateSessionInput to include all config options -- Old architecture: CONVERSATION_SETTINGS had 12+ fields -- New architecture: must decide which settings to persist at session level - -### G. 架构替换 - -- [x] 新 UI 主链路不依赖 `useChatStore` 与旧 `sessionPresenter`。 -- [x] `agentSessionPresenter + agentRuntimePresenter` 成为唯一主执行链路。 - -**Implementation Notes** (added 2026-02-28): -- MOSTLY COMPLETE: New UI uses sessionStore/messageStore -- ChatStatusBar still imports `useChatStore` for config updates (line 11-12) -- Should migrate ChatStatusBar to use sessionStore config instead -- Core streaming/persistence fully migrated to new architecture - -### H. chat 模式清理 - -- [ ] 类型、UI、主流程中不再暴露 `chat` 模式。 -- [ ] 旧 chat 数据有明确兼容迁移策略(静默升级或等价兼容)。 - -**Implementation Notes** (added 2026-02-28): -- NOT YET STARTED -- Old tables (conversations, messages) still exist -- No migration path defined -- Must decide: migrate old data to new tables? Or maintain dual-read compatibility? -- Recommended: dual-read during transition, migrate on-demand when old session opened - -## 非目标 - -1. 本轮不恢复 variants。 -2. 本轮不扩展与替换目标无关的 MCP 新能力。 -3. 本轮不做大规模视觉改版。 - -## 约束 - -1. MVP 优先,分阶段替换,避免一次性重构。 -2. 任何自动放行都必须受 `projectDir` 边界约束。 -3. 不引入新链路反向依赖旧 chat store。 - -## 开放问题 - -无。本轮关键决策已确认。 diff --git a/docs/archives/agentpresenter-mvp-replacement/tasks.md b/docs/archives/agentpresenter-mvp-replacement/tasks.md deleted file mode 100644 index d3d5c1efa..000000000 --- a/docs/archives/agentpresenter-mvp-replacement/tasks.md +++ /dev/null @@ -1,247 +0,0 @@ -# AgentPresenter 全量替换(MVP)任务清单 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -**Updated**: 2026-02-28 - Added implementation notes and priority markers - -## T0 规格冻结 - -- [x] 移除并确认无 `[NEEDS CLARIFICATION]`。 -- [x] 锁定 MVP 范围:权限、workspace、编辑、retry/regenerate、fork。 -- [x] 明确本轮不做 variants。 - -**Status**: COMPLETE - See `gap-analysis.md` for full specification - -## T1 Session 权限模型 🔴 P0 CRITICAL - -- [ ] 为 `new_sessions` 增加 `permission_mode` 字段(默认 `default`)。 - - **SQL**: `ALTER TABLE new_sessions ADD COLUMN permission_mode TEXT DEFAULT 'default'` - - **File**: `src/main/presenter/sqlitePresenter/tables/newSessionsTable.ts` -- [ ] session manager 增加读写 `permission_mode` 能力。 - - **File**: `src/main/presenter/agentSessionPresenter/sessionManager.ts` - - Add `permissionMode` to `create()` method - - Add getter/setter methods -- [ ] 补齐迁移与回填策略测试。 - - Existing sessions default to 'default' mode - -**Priority**: P0 - MVP Blocker -**Estimated**: 2-3 hours -**Status**: NOT STARTED - -## T2 ChatStatusBar 权限接入 🔴 P0 CRITICAL - -- [ ] `ChatStatusBar` 接入 `Default/Full access` 选择与展示。 - - **Current**: Line 91 shows read-only button "Default permissions" - - **Required**: Convert to DropdownMenu with Default/Full options - - **File**: `src/renderer/src/components/chat/ChatStatusBar.vue` -- [ ] session `projectDir` 为空时禁用 `Full access`。 - - Check `sessionStore.activeSession.projectDir` - - Disable dropdown option when empty -- [ ] `Full access` 禁用态提示“先绑定 workspace"。 - - Add tooltip on disabled option -- [ ] 选择结果写回 session 并可恢复。 - - Call `agentSessionPresenter.updateSessionPermissionMode()` (NEW IPC) - - Load on session activation - -**Priority**: P0 - MVP Blocker -**Estimated**: 4-6 hours -**Status**: NOT STARTED -**Dependencies**: T1 (session permission_mode field) - -## T3 Default 权限流程 🔴 P0 CRITICAL - -- [ ] 新链路接入权限请求消息块与审批动作。 - - **File**: `src/main/presenter/agentRuntimePresenter/dispatch.ts` - - Modify `executeTools()` to check permissions BEFORE calling tools - - Create permission request block: `{ type: 'action', action_type: 'tool_call_permission', ... }` - - Emit `STREAM_EVENTS.RESPONSE` with permission block - - PAUSE stream processing (set session status to 'paused') -- [ ] 实现 session 级白名单存储与查询。 - - **CREATE**: `src/main/presenter/agentRuntimePresenter/permissionChecker.ts` - - Create `permission_whitelists` table: `{ sessionId, toolName, pathPattern, permissionType, createdAt }` - - Query: `SELECT * FROM permission_whitelists WHERE sessionId = ? AND toolName = ?` -- [ ] 白名单匹配规则为 `toolName + pathPattern`。 - - Match exact tool name - - Match path pattern (glob or regex) - - Check `remember: true` from previous approvals -- [ ] 补齐白名单命中与未命中测试。 - - Unit tests for PermissionChecker - - Integration tests for full permission flow - -**Priority**: P0 - MVP Blocker -**Estimated**: 2-3 days -**Status**: NOT STARTED -**Dependencies**: T1 -**Key Files**: -- CREATE: `src/main/presenter/agentRuntimePresenter/permissionChecker.ts` -- MODIFY: `src/main/presenter/agentRuntimePresenter/dispatch.ts` -- MODIFY: `src/main/presenter/agentSessionPresenter/index.ts` - -## T4 Full access 边界控制 🔴 P0 CRITICAL - -- [ ] 实现自动通过逻辑(仅对 `projectDir` 内操作)。 - - **File**: `src/main/presenter/agentRuntimePresenter/permissionChecker.ts` - - Check `session.permission_mode === 'full'` - - If full access: auto-approve tools within projectDir -- [ ] 实现路径归一化与越界检测。 - - Normalize paths (resolve `..`, `.`, symlinks) - - Check if resolved path starts with `session.projectDir` - - Reject if outside boundary -- [ ] 越界请求返回拒绝事件与可见反馈。 - - Add error block to message - - Emit `STREAM_EVENTS.ERROR` with "Operation outside project directory" message -- [ ] 补齐越界绕过测试(相对路径、软链接、`..`)。 - - Test cases: `../secret.txt`, `./../../etc/passwd`, symlink escapes - -**Priority**: P0 - MVP Blocker -**Estimated**: 1-2 days -**Status**: NOT STARTED -**Dependencies**: T1, T3 - -## T5 Workspace 与会话绑定 ✅ P0 COMPLETE - -- [x] 工具执行上下文绑定 `session.projectDir`。 - - **Status**: COMPLETE - `agentSessionPresenter.createSession()` passes projectDir -- [x] 统一传递 `conversationId = sessionId`。 - - **Status**: COMPLETE - `agentRuntimePresenter.processStream()` uses sessionId throughout -- [x] 权限与消息归属链路统一按 `sessionId` 路由。 - - **Status**: COMPLETE - All new architecture uses sessionId - -**Priority**: P0 - COMPLETE -**Status**: ✅ DONE -**Notes**: Infrastructure is solid, just needs permission integration (T3, T4) - -## T6 编辑历史 user 消息 🟡 P1 HIGH - -- [ ] 实现 `editUserMessage(sessionId, messageId, newContent)`。 - - **File**: `src/main/presenter/agentSessionPresenter/index.ts` - - **IPC**: Add `editUserMessage(sessionId, messageId, newContent)` method - - Validate: only user messages can be edited -- [ ] 执行"编辑点后消息截断"。 - - **File**: `src/main/presenter/agentRuntimePresenter/messageStore.ts` - - Add `deleteMessagesAfter(messageId)` method - - Delete all messages with `orderSeq > editedMessage.orderSeq` -- [ ] 自动触发 regenerate 并同步状态。 - - Call `processMessage(sessionId, newContent)` after edit - - Set session status to 'generating' -- [ ] 补齐编辑后上下文正确性测试。 - - Test: edit message, verify subsequent messages deleted - - Test: regenerate produces new assistant response - - Test: message boundaries correct - -**Priority**: P1 - High (Core functionality) -**Estimated**: 1-2 days -**Status**: NOT STARTED -**Dependencies**: T5 (session binding) -**Frontend**: Add edit action to user message context menu in MessageList - -## T7 Retry/Regenerate(无 variants) 🟡 P1 HIGH - -- [ ] 移除或短路 variants 路径。 - - **Note**: New architecture doesn't have variants - already clean - - Old: `agentPresenter.retryMessage()` created variants - - New: Must create NEW assistant message (not replace) -- [ ] 实现 retry/regenerate 追加 assistant 消息。 - - **File**: `src/main/presenter/agentSessionPresenter/index.ts` - - **IPC**: Add `retryMessage(sessionId, messageId)` method - - Find the assistant message by messageId - - Create new assistant message with same context - - Call `processStream()` to generate new response -- [ ] 使用消息边界控制上下文收敛。 - - Mark message boundary to avoid context pollution - - Ensure only messages up to retry point are used -- [ ] 补齐多次 retry 的上下文一致性测试。 - - Test: retry 3 times → 3 assistant messages created - - Test: context uses correct message history - - Test: each retry independent - -**Priority**: P1 - High (Core functionality) -**Estimated**: 1-2 days -**Status**: NOT STARTED -**Dependencies**: T5 (session binding) -**Frontend**: Add retry action to assistant message context menu - -## T8 Fork 🟡 P1 HIGH - -- [ ] 实现 `forkSessionFromMessage(sessionId, messageId)`。 - - **File**: `src/main/presenter/agentSessionPresenter/index.ts` - - **IPC**: Add `forkSessionFromMessage(sessionId, messageId)` method - - Get all messages up to and including messageId - - Create new session with copied messages - - Copy session config (provider, model, permissionMode, etc.) -- [ ] 切点包含当前 assistant 消息本身。 - - Include the assistant message at fork point - - New session can continue from that point -- [ ] fork 后新 session 可继续发送与生成。 - - Initialize new session with agentRuntimePresenter - - Enable sending messages and generating responses -- [ ] 补齐 fork 前后消息隔离测试。 - - Test: fork creates independent session - - Test: messages in forked session don't affect original - - Test: can continue conversation in forked session - -**Priority**: P1 - High (Core functionality) -**Estimated**: 1-2 days -**Status**: NOT STARTED -**Dependencies**: T1 (session model), T5 (session binding) -**Frontend**: Add fork action to assistant message context menu - -## T9 设置收敛 🟢 P2 MEDIUM - -- [ ] 下线 conversation settings 入口与依赖逻辑。 - - **Status**: PARTIAL - old settings still exist in parallel - - Remove conversation settings UI from ChatPage - - Migrate to session-level config -- [ ] 将 agent 默认配置下沉到 session 存储。 - - **File**: `src/main/presenter/agentSessionPresenter/index.ts` - - Extend `CreateSessionInput` to include: - - `temperature?: number` - - `contextLength?: number` - - `maxTokens?: number` - - `systemPrompt?: string` - - Store in session record or separate config table -- [ ] 清理 legacy settings 读取/写入路径。 - - Remove references to old CONVERSATION_SETTINGS - - Migrate to new session config - -**Priority**: P2 - Medium (Can use defaults for MVP) -**Estimated**: 3-5 days -**Status**: PARTIALLY COMPLETE - defaults work, advanced config missing -**Dependencies**: T1 (session model extension) - -## T10 移除 chat 模式 🟢 P2 MEDIUM - -- [ ] 类型层移除 `chat`。 - - Remove `chatMode` from types - - Remove legacy chat-specific code paths -- [ ] presenter/UI 中移除 chat 分支。 - - Clean up agentPresenter/sessionPresenter - - Remove chat mode conditionals -- [ ] 旧 chat 数据兼容迁移验证通过。 - - **Strategy**: Dual-read during transition - - When old session opened, migrate on-demand to new tables - - Or maintain read-only compatibility layer - -**Priority**: P2 - Medium (Can coexist for now) -**Estimated**: 1 week -**Status**: NOT STARTED -**Dependencies**: T6, T7, T8 (message operations) -**Risk**: Breaking old session compatibility - must test thoroughly - -## T11 质量门槛 - -- [ ] `pnpm run format` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] 关键单测与集成测试通过 - -**Required Tests**: -1. Permission flow integration test -2. Whitelist matching test -3. Full access boundary test -4. Edit message test -5. Retry/regenerate test -6. Fork session test - -**Status**: PENDING - depends on T1-T10 completion diff --git a/docs/archives/ai-sdk-runtime/plan.md b/docs/archives/ai-sdk-runtime/plan.md deleted file mode 100644 index 343880413..000000000 --- a/docs/archives/ai-sdk-runtime/plan.md +++ /dev/null @@ -1,13 +0,0 @@ -# AI SDK Runtime Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Status: completed, rollback retired. See [../legacy-llm-provider-runtime-retirement/plan.md](../legacy-llm-provider-runtime-retirement/plan.md). - -1. Introduce shared AI SDK runtime modules without changing upper-layer interfaces. -2. Migrate OpenAI-compatible and OpenAI responses providers first. -3. Migrate Anthropic / Gemini / Vertex / Bedrock / Ollama to the shared runtime. -4. Keep routing providers (`new-api`, `zenmux`) as thin delegates over migrated providers. -5. Freeze `LLMCoreStreamEvent` behavior with adapter-focused tests. -6. Retire the rollback path and delete legacy state machines. diff --git a/docs/archives/ai-sdk-runtime/spec.md b/docs/archives/ai-sdk-runtime/spec.md deleted file mode 100644 index d25ba1c2a..000000000 --- a/docs/archives/ai-sdk-runtime/spec.md +++ /dev/null @@ -1,96 +0,0 @@ -# AI SDK Runtime Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Status - -Completed in commit `4c8345a7`. - -As of `2026-04-11`, the rollback path is retired. See [../legacy-llm-provider-runtime-retirement/spec.md](../legacy-llm-provider-runtime-retirement/spec.md). - -## Goal - -Unify DeepChat's low-level LLM request pipeline on Vercel AI SDK while keeping the upper-layer contracts unchanged: - -- `BaseLLMProvider` -- `LLMProviderPresenter` -- `LLMCoreStreamEvent` -- existing provider IDs, model configs, and conversation history - -The AI SDK runtime is the only remaining implementation. - -## Non-Negotiable Compatibility - -- No functional regression in text streaming, reasoning streaming, tool call streaming, image output, prompt cache, proxy handling, request tracing, routing, and embeddings. -- `LLMCoreStreamEvent` event names, field names, and stop reasons remain unchanged. -- Existing `function_call_record` history must stay reusable across providers. -- Existing provider list / model list / provider check / key status responsibilities remain in provider classes. - -## Runtime Mode - -- Single runtime: `ai-sdk` -- `DEEPCHAT_LLM_RUNTIME` has been removed -- config setting `llmRuntimeMode` has been removed - -## Scope - -Shared runtime under `src/main/presenter/llmProviderPresenter/aiSdk/` provides: - -- provider factory -- model / message mapper -- MCP tool mapper -- streaming adapter -- image runtime -- embedding runtime -- provider-options mapper -- reasoning middleware -- legacy function-call compatibility middleware - -## Provider Rollout - -Phase 1: - -- `OpenAICompatibleProvider` -- `OpenAIResponsesProvider` -- all `extends OpenAICompatibleProvider` providers - -Phase 2: - -- `AnthropicProvider` -- `GeminiProvider` -- `VertexProvider` -- `AwsBedrockProvider` -- `OllamaProvider` - -Phase 3: - -- `NewApiProvider` -- `ZenmuxProvider` - -Out of scope for first unification pass: - -- `AcpProvider` -- `VoiceAIProvider` - -## Validation Matrix - -- pure text -- reasoning native -- reasoning via `` -- native tool streaming -- legacy `` fallback -- multi-tool history replay -- image input -- image output -- usage mapping -- prompt cache mapping -- proxy / trace / abort -- embeddings -- retired rollback path verification - -## Legacy Removal Exit Criteria - -- AI SDK runtime passes the provider regression matrix -- duplicated legacy stream parsers / tool parsers have no remaining callers -- retirement is documented in [../legacy-llm-provider-runtime-retirement/spec.md](../legacy-llm-provider-runtime-retirement/spec.md) diff --git a/docs/archives/ai-sdk-runtime/tasks.md b/docs/archives/ai-sdk-runtime/tasks.md deleted file mode 100644 index 98e3f7da0..000000000 --- a/docs/archives/ai-sdk-runtime/tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# AI SDK Runtime Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -- [x] Freeze migration scope in SDD docs. -- [x] Add shared AI SDK runtime modules. (implemented in 4c8345a7) -- [x] Integrate OpenAI-compatible runtime path. (implemented in 4c8345a7) -- [x] Integrate OpenAI responses runtime path. (implemented in 4c8345a7) -- [x] Integrate Anthropic runtime path. (implemented in 4c8345a7) -- [x] Integrate Gemini runtime path. (implemented in 4c8345a7) -- [x] Integrate Vertex runtime path. (implemented in 4c8345a7) -- [x] Integrate Bedrock runtime path. (implemented in 4c8345a7) -- [x] Integrate Ollama runtime path. (implemented in 4c8345a7) -- [x] Add regression tests for runtime adapter behavior. (implemented in 4c8345a7) -- [x] Retire `DEEPCHAT_LLM_RUNTIME` and `llmRuntimeMode`. -- [x] Remove legacy provider SDK fallback branches. -- [x] Remove provider-specific MCP conversion interfaces from presenter ports. -- [x] Record retirement history in [../legacy-llm-provider-runtime-retirement/tasks.md](../legacy-llm-provider-runtime-retirement/tasks.md). -- [x] Run format, i18n, lint, and targeted tests. diff --git a/docs/archives/chat-audio-tts-routing/plan.md b/docs/archives/chat-audio-tts-routing/plan.md deleted file mode 100644 index e711ff914..000000000 --- a/docs/archives/chat-audio-tts-routing/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# Chat Audio TTS Routing Plan - -## Implementation - -- Tighten `isChatAudioTtsModel` so MiMo IDs must match the known MiMo prefixes and include a standalone `tts` segment. -- Update `executeTtsPatternB` to treat `message.content` as unknown response data. -- Extract audio parts only after checking `Array.isArray(message.content)`. -- Keep `message.audio.data` as the first-preference extraction path. -- Leave the existing missing-audio error path in place for responses that contain no audio data. - -## Test Strategy - -- Add shared helper coverage for MiMo TTS and non-TTS model IDs. -- Extend `test/main/presenter/llmProviderPresenter/aiSdkRuntime.test.ts`. -- Cover `mimo-v2.5-pro` using normal chat streaming instead of direct TTS `fetch`. -- Cover a successful HTTP response with string `message.content` and no audio payload. -- Assert the runtime rejects with the expected missing-audio error, not `content.find is not a function`. - -## Compatibility - -This change is backward-compatible for actual MiMo TTS models. Non-TTS MiMo chat models stop being routed through TTS handling, while providers returning `message.audio.data` or array content audio parts keep the same behavior. diff --git a/docs/archives/chat-audio-tts-routing/spec.md b/docs/archives/chat-audio-tts-routing/spec.md deleted file mode 100644 index bfbbca2c5..000000000 --- a/docs/archives/chat-audio-tts-routing/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -# Chat Audio TTS Routing - -## User Story - -When a MiMo chat model is selected, DeepChat should only enter chat-audio TTS handling for model IDs that are actually TTS variants. Regular MiMo chat models such as `MiMo-V2.5-Pro` should use the normal chat streaming runtime. - -## Acceptance Criteria - -- `mimo-v2.5-pro` and provider-prefixed variants are not classified as TTS models. -- MiMo model IDs with a `tts` segment, such as `mimo-v2.5-tts`, continue to use chat-audio TTS Pattern B. -- Chat-audio TTS responses with `choices[0].message.audio.data` continue to emit cached audio. -- Chat-audio TTS responses with array `choices[0].message.content` can still extract an audio content part. -- Chat-audio TTS responses with string `choices[0].message.content` do not throw a `TypeError`. -- If no audio payload exists, DeepChat raises the existing missing-audio error instead of a response-shape crash. - -## Non-Goals - -- No changes to renderer audio display behavior. -- No changes to request body construction for chat-audio TTS models. - -## Constraints - -- Keep the fix localized to the AI SDK runtime. -- Keep TTS model classification in shared helpers so provider and agent runtime checks agree. -- Preserve current OpenAI-compatible chat-audio behavior. -- Add focused regression coverage for the reported MiMo Pro misrouting and response shape. diff --git a/docs/archives/chat-audio-tts-routing/tasks.md b/docs/archives/chat-audio-tts-routing/tasks.md deleted file mode 100644 index 9ff9742f3..000000000 --- a/docs/archives/chat-audio-tts-routing/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Chat Audio TTS Routing Tasks - -- [x] Create SDD issue artifacts. -- [x] Guard chat-audio TTS content audio extraction by response shape. -- [x] Add a regression test for string `message.content`. -- [x] Tighten MiMo chat-audio TTS classification. -- [x] Add regression coverage for MiMo Pro chat routing. -- [x] Run focused test coverage and quality checks. diff --git a/docs/archives/cua-runtime-plugin/non-macos-handoff.md b/docs/archives/cua-runtime-plugin/non-macos-handoff.md deleted file mode 100644 index e7d3bb1b3..000000000 --- a/docs/archives/cua-runtime-plugin/non-macos-handoff.md +++ /dev/null @@ -1,55 +0,0 @@ -# Non-macOS Handoff Notes - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -This implementation was completed on Windows, so the architecture and TypeScript integration were -validated locally, while macOS runtime behavior still needs a Mac pass. - -## Implemented - -- Official-source-only plugin installation path with GitHub Release download and local - `.dcplugin` selection. -- Generic plugin resources for runtimes, MCP servers, skills, settings contributions, and tool - policies. -- Plugin-owned MCP registration with `ownerPluginId`. -- Plugin-owned skill contribution support. -- Isolated plugin settings renderer preload API exposed as `window.deepchatPlugin`. -- Official `plugins/cua` package with a bundled macOS `DeepChat Computer Use.app` helper runtime. -- CI/package scripts for architecture-specific - `deepchat-plugin-cua--darwin-.dcplugin` artifacts. -- App packaging no longer embeds the CUA helper inside the DeepChat app bundle. -- App packaging no longer embeds the CUA plugin source; the app and plugin artifacts are built - independently. - -## Manual Install Flow - -1. Build the plugin package for the current Mac: - `pnpm run plugin:cua:package` - - Apple Silicon explicit package: `pnpm run plugin:cua:package:mac:arm64` - - Intel explicit package: `pnpm run plugin:cua:package:mac:x64` -2. Open Settings > Plugins. -3. Click Install on `com.deepchat.plugins.cua`. - - DeepChat first downloads - `https://github.com/ThinkInAIXYZ/deepchat/releases/download/v/deepchat-plugin-cua--darwin-.dcplugin`. - - If the asset is missing, DeepChat opens the matching GitHub Release page. -4. Click Choose `.dcplugin` and select the downloaded package when automatic download is not - available. -5. Enable `com.deepchat.plugins.cua`, then verify runtime status, permission check, MCP - registration, skill visibility, and tool policy prompts. - -## Requires macOS validation - -- `cua-driver --version` output shape. -- `cua-driver check_permissions` output shape and permission parsing. -- `cua-driver mcp` startup under DeepChat MCP stdio management. -- TCC ownership remains with the bundled `DeepChat Computer Use.app` helper inside the installed - plugin. -- Plugin settings window can open the CUA permission guide and refresh status. -- Signed `.dcplugin` packaging and official source distribution metadata. - -## Legacy Demo Code - -The prior built-in Computer Use implementation is removed from startup, routes, renderer API, -settings, helper packaging, vendored source, and tests. The remaining CUA-specific source of truth is -`plugins/cua`. diff --git a/docs/archives/cua-runtime-plugin/plan.md b/docs/archives/cua-runtime-plugin/plan.md deleted file mode 100644 index da221ae22..000000000 --- a/docs/archives/cua-runtime-plugin/plan.md +++ /dev/null @@ -1,546 +0,0 @@ -# CUA Runtime Plugin Implementation Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Feature: `cua-runtime-plugin` -Spec: [spec.md](./spec.md) - -## Summary - -The implementation should land as two independent lines: - -1. Generic plugin runtime infrastructure in DeepChat core. -2. First-party `deepchat-plugin-cua` packaged as an installable runtime plugin. - -The current built-in Computer Use branch proves that CUA can drive DeepChat through MCP and skills. -It should not be used as the final architecture because it makes CUA a core app packaging, signing, -permission, route, renderer, and agent prompt concern. - -## Architecture - -```text -DeepChat Core -+-- PluginHost -+-- PluginResourceStore -+-- RuntimeRegistry -+-- ManagedMcpRegistry -+-- SkillRegistry -+-- ToolPolicyRegistry -+-- SettingsContributionHost - | - | enable / disable / delete - v -Installed Plugin: com.deepchat.plugins.cua -+-- plugin.json -+-- dist/main.js -+-- settings/index.html -+-- types/settings-preload.d.ts -+-- skills/cua-driver/SKILL.md -+-- mcp/cua-driver.json -+-- policies/tool-policy.json - | - | detects / opens / executes declared helper commands - v -Bundled Plugin Helper -+-- /runtime/darwin//DeepChat Computer Use.app - +-- Contents/MacOS/cua-driver -``` - -Core owns the registries, official-source trust policy, lifecycle, and renderer isolation. The -plugin owns CUA-specific runtime detection, bundled helper runtime, status, settings web bundle, -typed preload API, MCP contribution, skill content, tool policy, and helper guidance. - -## Key Decisions - -### 1. First-Party Signed Plugin Scope - -The first production increment should install plugins only from the DeepChat official plugin source. -That is enough for `deepchat-plugin-cua` and deliberately avoids opening a dangerous third-party -execution surface before the trust model, review process, and isolation story are mature. - -Required boundaries: - -- Production builds reject arbitrary local `.dcplugin` sideloading. -- Production builds verify official source URL, plugin id reservation, checksums, and DeepChat - signature metadata before install or update. -- Local plugin development is available only in development builds or explicit developer mode. -- A plugin cannot register arbitrary IPC routes. -- A plugin cannot write raw MCP config or raw skill cache. -- A plugin can only contribute through `PluginContext` registries. -- Process execution is only available through declared command IDs. -- Plugin packages require checksums and signature metadata before installation. - -Longer-term untrusted plugin isolation is a separate feature. - -### 2. Declarative First, Activation Code Second - -The CUA plugin should use manifest-declared contributions wherever possible: - -- runtime id and detection candidates -- MCP server shape -- skill path -- settings contribution id -- tool policy - -Activation code should only handle runtime detection, version checks, permission checks, and helper -install/uninstall actions that need runtime state. - -### 3. Owner-Aware Resources - -Add a `PluginResourceStore` as the single source of truth for plugin-owned runtime resources. It may -start as an ElectronStore-backed store to match existing MCP settings, but it should be represented -behind an interface so it can move to SQLite later if needed. - -Suggested records: - -```typescript -interface PluginInstallationRecord { - pluginId: string - version: string - path: string - enabled: boolean - trusted: boolean - installedAt: number - updatedAt: number -} - -interface PluginResourceRecord { - pluginId: string - kind: 'runtime' | 'mcpServer' | 'skill' | 'settings' | 'toolPolicy' - key: string - payloadJson: string - enabled: boolean - createdAt: number - updatedAt: number -} - -interface RuntimeDependencyRecord { - pluginId: string - runtimeId: string - provider: string - command?: string - version?: string - installSource?: string - state: 'missing' | 'installed' | 'running' | 'error' -} -``` - -### 4. MCP Ownership Wrapper - -Do not let plugins call `configPresenter.addMcpServer` directly. - -Add `ManagedMcpRegistry`: - -- validates plugin ownership -- writes MCP server config with `ownerPluginId` -- maps plugin server id to a stable internal key -- stops server before unregistering it -- refreshes MCP client and tool caches after changes - -Existing `source` / `sourceId` can remain for external catalogs. Add `ownerPluginId` for plugin -ownership instead of overloading those fields. - -### 5. Skill Contributions - -Extend SkillPresenter discovery to merge: - -- user skill directory -- built-in resource skills -- plugin-owned skill roots from `SkillRegistry` - -Plugin-owned skills should not be copied into `resources/skills` or `~/.deepchat/skills`. - -Required behavior: - -- hidden when plugin disabled -- unavailable to `skill_view` when disabled -- removed from prompt loading and active skill validation -- hot reloaded in development mode if plugin path changes - -### 6. Tool Policies - -Add `ToolPolicyRegistry` and call it from MCP tool permission pre-check and execution paths before -fallback name heuristics. - -Policy evaluation order: - -1. Session-scoped permission cache. -2. Plugin-owned exact tool policy. -3. Server `autoApprove`. -4. Existing read/write heuristic. - -Decision mapping: - -- `allow` returns no permission request. -- `ask` returns a permission request. -- `deny` returns a clear blocked response. - -CUA action tools should use `ask` by default. Read/status tools can use `allow`, but should still be -overridable by stricter global settings later. - -### 7. Settings Contribution Host - -Add a generic Settings > Plugins surface before adding CUA UI. - -Plugin settings contribution contract: - -```typescript -interface SettingsContribution { - id: string - ownerPluginId: string - title: string - entry: string // standalone HTML entry inside the plugin package - preloadTypes: string - placement: 'plugins' -} -``` - -DeepChat should not render plugin settings inside the normal Vue renderer tree. The contribution -host should create an isolated renderer/webContents for the plugin settings page: - -```text -DeepChat Settings > Plugins - | - | owns container and lifecycle only - v -Isolated plugin settings renderer - | - | contextIsolation + no Node integration - v -Dedicated plugin preload - | - | plugin-scoped typed API - v -PluginHost / Plugin main module -``` - -The plugin settings web bundle can be implemented with any frontend stack that compiles to static -assets. It talks to DeepChat only through the dedicated preload API. Plugin developers should code -against the shipped `.d.ts` file, not against DeepChat renderer internals. - -Generic host APIs available through preload: - -- `plugins.list` -- `plugins.get` -- `plugins.enable` -- `plugins.disable` -- `plugins.invokeAction` - -Plugin-specific APIs available through preload are declared by the plugin and handled through the -plugin host, for example: - -```typescript -interface CuaSettingsApi { - getRuntimeStatus(): Promise - checkPermissions(): Promise - openPermissionGuide(): Promise - uninstallHelper(): Promise -} -``` - -No CUA-specific typed route should exist in core. - -### 8. Minimal Main SDK - -Plugins should provide agent-facing capabilities through MCP and skills first. If a plugin needs -DeepChat capabilities outside MCP/skills, add a small main-process SDK method to `PluginContext`. - -Rules: - -- SDK methods require manifest capability declarations. -- SDK methods expose stable typed functions, not presenters or stores. -- SDK methods are reviewed as core API surface. -- SDK methods should be narrow enough that a future untrusted-plugin host can mediate them. - -CUA expected SDK needs are limited to: - -- declared process execution for `cua-driver` status/version/permission checks -- opening official external URLs or macOS permission panes -- plugin storage -- managed runtime/MCP/skill/settings/tool-policy registries - -### 9. CUA Helper Integration - -The CUA plugin should detect official helper candidates: - -```text -/Applications/CuaDriver.app/Contents/MacOS/cua-driver -~/.local/bin/cua-driver -PATH:cua-driver -``` - -Runtime actions: - -- `cua-driver.version`: run detected command with `--version` -- `cua-driver.status`: run detected command with status/check command when available -- `cua-driver.permissions`: run helper permission check command -- `cua-driver.open-permissions`: open official permission guide or helper-driven prompt -- `cua-driver.uninstall`: run official uninstall flow only after user confirmation - -The plugin should prefer the app-bundled binary path when present so macOS TCC grants are owned by -`CuaDriver.app`. - -### 10. Plugin Package Layout - -Source: - -```text -plugins/ - cua/ - plugin.json - package.json - tsconfig.json - src/ - main.ts - runtime/locator.ts - runtime/permissions.ts - runtime/actions.ts - settings/ - index.html - src/CuaRuntimeSettings.ts - src/style.css - types/ - settings-preload.d.ts - skills/ - cua-driver/ - SKILL.md - README.md - RECORDING.md - TESTS.md - WEB_APPS.md - mcp/ - cua-driver.json - policies/ - tool-policy.json -``` - -Packaged artifact: - -```text -deepchat-plugin-cua.dcplugin -+-- plugin.json -+-- dist/main.js -+-- settings/index.html -+-- settings/assets/index.js -+-- settings/assets/index.css -+-- types/settings-preload.d.ts -+-- skills/cua-driver/SKILL.md -+-- skills/cua-driver/README.md -+-- skills/cua-driver/RECORDING.md -+-- skills/cua-driver/TESTS.md -+-- skills/cua-driver/WEB_APPS.md -+-- mcp/cua-driver.json -+-- policies/tool-policy.json -+-- checksums.json -+-- signature.sig -``` - -### 11. Build And CI - -Add plugin scripts without coupling them to core app packaging: - -```text -pnpm run plugin:cua:build -pnpm run plugin:cua:package -pnpm run plugin:cua:validate -``` - -Release shape: - -- Core app jobs build Windows, Linux, and macOS artifacts without CUA helper steps. -- Plugin job builds `deepchat-plugin-cua-.dcplugin`. -- Plugin job validates official-source metadata, checksums, and signature metadata. -- Release job uploads plugin artifact separately from app artifacts. -- No Swift build, helper entitlements, nested helper signing, or CUA notarization path is required - for DeepChat app release. - -### 12. Demo Branch Migration - -Remove CUA-specific core changes from the final app: - -```text -remove: - src/main/presenter/computerUsePresenter - src/renderer/api/ComputerUseClient.ts - src/shared/contracts/routes/computerUse.routes.ts - src/shared/types/computerUse.ts - src/renderer/settings/components/ComputerUseSettingsCard.vue - resources/skills/cua-driver - vendor/cua-driver - scripts/build-cua-driver.mjs - scripts/update-cua-driver.mjs - build/entitlements.computer-use.plist - runtime/computer-use - -refactor into generic infrastructure: - typed route pattern -> plugin routes - MCP source/sourceId usage -> ownerPluginId-aware managed MCP - skill visibility gate -> plugin-owned skill contribution visibility - CUA tool buckets -> ToolPolicyRegistry - CUA settings card -> isolated SettingsContributionHost renderer - helper path/status -> RuntimeRegistry -``` - -Also remove CUA helper steps from macOS build and release workflows. - -## Implementation Milestones - -### M1 Core Plugin Contracts - -Add shared plugin types, manifest schema, official-source trust policy, package validation, -installation records, and lifecycle routes. - -Exit criteria: - -- Core can list installed plugins. -- A fixture plugin manifest validates. -- Invalid package paths, ids, versions, and unsupported platforms are rejected. -- Production installation rejects non-official source URLs and untrusted signatures. - -### M2 Plugin Resource Store And Registries - -Add owner-aware resource records plus runtime, MCP, skill, settings, and tool policy registry shells. - -Exit criteria: - -- `disableByOwner(pluginId)` disables every owned resource type. -- `removeByOwner(pluginId)` removes every owned record. -- Startup repair removes resources for missing plugin installations. - -### M3 Managed MCP And Tool Policy Integration - -Wire managed MCP registration into existing MCP presenter/config flow. Wire tool policies into MCP -permission pre-check and execution. - -Exit criteria: - -- Plugin-owned MCP server appears in MCP runtime state when plugin enabled. -- Plugin-owned MCP server disappears on disable. -- Exact tool policy overrides fallback name heuristic. -- User-owned MCP servers are unaffected. - -### M4 Skill And Settings Contributions - -Wire plugin-owned skills into SkillPresenter and plugin settings into isolated settings renderers. - -Exit criteria: - -- Plugin-owned skill appears only while plugin enabled. -- `skill_view` cannot read disabled plugin-owned skills. -- Settings > Plugins lists plugin cards through contribution metadata. -- Plugin settings load in a separate renderer with a dedicated preload. - -### M5 Plugin Packaging Toolchain - -Add packaging and validation scripts for `.dcplugin`. - -Exit criteria: - -- Package contains required files. -- Checksums cover all package files except signature. -- Invalid manifests or missing files fail validation. -- Missing settings HTML or preload type declarations fail validation when a settings contribution is - declared. -- CI can build a plugin artifact without building DeepChat app. - -### M6 CUA Plugin - -Move CUA-specific integration into `plugins/cua`. - -Exit criteria: - -- Missing helper state is visible. -- Installed helper version and path are detected. -- MCP server `cua-driver` starts through normal MCP. -- CUA skill is contributed by the plugin. -- CUA action tools trigger approval prompts. -- CUA read/status tools follow plugin policy. - -### M7 Demo Core Removal - -Remove the built-in Computer Use branch changes and keep only generic infrastructure plus plugin -source. - -Exit criteria: - -- Core grep acceptance passes. -- macOS app package contains no CUA Driver helper or vendored CUA source. -- DeepChat build workflows no longer run CUA helper build steps. - -### M8 Release Validation - -Validate end-to-end behavior on macOS with and without official Cua Driver installed. - -Exit criteria: - -- Fresh app install has no CUA capability until plugin installation. -- Plugin enable/disable/delete acceptance passes. -- App release and plugin release artifacts are independent. - -## Test Strategy - -Main process tests: - -- manifest schema validation -- package path safety -- plugin lifecycle -- resource store owner disable/delete -- managed MCP register/unregister -- tool policy exact match and fallback behavior -- plugin skill visibility and prompt injection -- stale resource startup repair - -Renderer tests: - -- Settings > Plugins plugin list -- plugin enable/disable actions -- isolated settings contribution host lifecycle -- plugin preload API availability -- blocked access to DeepChat renderer globals -- CUA settings states: missing helper, installed helper, permission missing, permission granted - -Script tests: - -- `.dcplugin` package validation -- checksum generation and validation -- missing file failure -- unsupported platform manifest failure -- non-official source rejection -- missing preload type declarations - -Manual macOS checks: - -- Cua Driver official install -- Cua Driver permission ownership in System Settings -- `cua-driver mcp` tool discovery -- click/type/hotkey approval prompt -- plugin disable removes tools from current session after refresh -- plugin delete survives restart without stale resources - -## Rollout - -1. Land the generic plugin foundation without CUA plugin. -2. Add CUA plugin package and CI artifact. -3. Remove built-in CUA demo code from core. -4. Validate macOS external helper flow. -5. Publish DeepChat app and CUA plugin as separate artifacts. - -## Risks And Mitigations - -- Risk: plugin code becomes an unbounded execution surface. - - Mitigation: production installs only official-source signed plugins, settings run in isolated - renderers, APIs are capability-declared, and there is no public marketplace scope. -- Risk: settings UI can pierce DeepChat renderer privileges. - - Mitigation: standalone settings web bundle in an isolated renderer with dedicated preload, - context isolation, no Node integration, and plugin-scoped typed APIs only. -- Risk: plugin-owned MCP config leaves stale tools after disable/delete. - - Mitigation: owner-aware resource records, stop-before-unregister, and startup repair. -- Risk: skills stay pinned after plugin disable. - - Mitigation: skill validation must filter disabled owner resources before prompt composition. -- Risk: user already has an MCP server named `cua-driver`. - - Mitigation: use an internal owner-aware key and a user-facing display name, or block enable with - a clear conflict message. -- Risk: official Cua Driver install paths change. - - Mitigation: detect declared canonical paths plus PATH, keep install guidance in the plugin, and - update plugin independently from core. diff --git a/docs/archives/cua-runtime-plugin/spec.md b/docs/archives/cua-runtime-plugin/spec.md deleted file mode 100644 index 88dce51d3..000000000 --- a/docs/archives/cua-runtime-plugin/spec.md +++ /dev/null @@ -1,444 +0,0 @@ -# CUA Runtime Plugin Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Summary - -DeepChat should move macOS Computer Use out of the core application bundle and into an -installable runtime plugin. The core app should provide reusable plugin infrastructure: -Plugin Host, Runtime Registry, Managed MCP Registry, Skill Registry, Tool Policy Registry, and -Settings Contribution Host. The CUA integration should live in a separate plugin package that -registers an external Cua Driver helper, MCP server, skill, settings card, and tool permission -policy only while the plugin is enabled. - -This replaces the current `codex/mac-computer-use` branch direction where DeepChat bundles a -source-built helper app, vendored `cua-driver` source, CUA-specific settings UI, CUA typed routes, -CUA-specific skill visibility, and CUA-specific MCP permission rules directly in core. - -## Current Findings - -Treat the current built-in Computer Use implementation as a technical feasibility demo: - -- `src/main/presenter/computerUsePresenter/index.ts` owns helper discovery, helper path layout, - permission checks, enable state, and MCP registration. -- `src/shared/contracts/routes/computerUse.routes.ts`, - `src/shared/types/computerUse.ts`, and `src/renderer/api/ComputerUseClient.ts` expose a - CUA-specific renderer-main API surface. -- `src/renderer/settings/components/ComputerUseSettingsCard.vue` puts CUA settings directly inside - core MCP settings. -- `src/main/presenter/mcpPresenter/toolManager.ts` contains CUA-only read/write tool buckets. -- `src/main/presenter/skillPresenter/index.ts` hides the built-in `cua-driver` skill with a - `computer-use` feature flag. -- `src/main/presenter/agentRuntimePresenter/index.ts` auto-pins the `cua-driver` skill based on the - built-in CUA MCP server. -- `scripts/build-cua-driver.mjs`, `scripts/update-cua-driver.mjs`, `scripts/afterPack.js`, - `electron-builder.yml`, GitHub workflows, `build/entitlements.computer-use.plist`, - `resources/skills/cua-driver`, and `vendor/cua-driver` make the helper a core packaging concern. - -Treat `origin/dev` as the production baseline: - -- There is no general Plugin Host for runtime plugins. -- MCP servers are persisted in the current MCP config store and can carry `source` / `sourceId` - metadata, but there is no owner-aware managed resource registry. -- Skills are file-based and already support metadata, platform filters, scripts, and activation, but - they do not yet support plugin-owned contribution roots. -- Tool permission is currently based on broad read/write heuristics plus server `autoApprove`. - There is no declarative per-plugin tool policy registry. -- Remote channel code has a narrow channel plugin manifest shape, but it is not a general runtime - plugin system. - -## Goals - -- Keep DeepChat core free of `cua`, `computerUse`, and `CuaDriver` product-specific code. -- Add generic plugin infrastructure that can support CUA and future runtime integrations. -- Ship CUA as `deepchat-plugin-cua`, a separately packaged `.dcplugin` artifact. -- Allow production plugin installation only from DeepChat official sources for the first release. -- Let CI build DeepChat app artifacts and CUA plugin artifacts separately. -- Use the official Cua Driver helper externally, rather than vendoring and notarizing driver source - inside DeepChat app packages. -- Let the CUA plugin own its skills, settings UI, runtime detection, MCP contribution, and tool - permission policy. -- Ensure disabling or deleting the plugin removes all DeepChat-side CUA capabilities. -- Keep the CUA helper runtime and macOS TCC grants outside DeepChat core. - -## Non-Goals - -- Do not merge the current built-in CUA helper as a core feature. -- Do not package `vendor/cua-driver` or `DeepChat Computer Use.app` inside DeepChat app artifacts. -- Package the signed `DeepChat Computer Use.app` helper inside the CUA `.dcplugin` artifact. -- Do not add DeepChat-owned Accessibility or Screen Recording permissions for CUA. -- Do not add Windows or Linux Computer Use support in this feature. -- Do not create a public third-party plugin marketplace in the first increment. -- Do not allow arbitrary local `.dcplugin` sideloading in production builds. -- Do not let plugins mutate arbitrary core state outside declared contribution APIs. - -## User Stories - -- As a DeepChat user, I can install the CUA runtime plugin and see it in Settings > Plugins. -- As a macOS user, I can enable the CUA plugin only after seeing its requested capabilities. -- As a macOS user without Cua Driver installed, I see a clear install guide and no active CUA tools. -- As a macOS user with Cua Driver installed, I see helper version, detected command path, - Accessibility status, Screen Recording status, MCP status, and skill status. -- As a privacy-sensitive user, I can disable or delete the plugin and have all CUA MCP tools, - skills, settings cards, and policies disappear from DeepChat. -- As a release engineer, I can build and publish the DeepChat app without building, signing, - notarizing, or vendoring CUA Driver. -- As a plugin maintainer, I can update `deepchat-plugin-cua` independently from DeepChat core. - -## Functional Requirements - -### Plugin Host - -- Core must discover installed plugins from the DeepChat plugin install directory. -- Core must install plugins only from the DeepChat official plugin source in production builds. -- Core must parse a signed plugin manifest before activation. -- Core must reject packages whose publisher, source URL, signature, or checksum chain is not - trusted by the official source policy. -- Core must support enable, disable, update, and delete plugin lifecycle operations. -- Core must expose a stable `PluginContext` with only declared contribution APIs. -- Core must persist plugin installation state separately from MCP, skills, and runtime state. -- Core must emit plugin lifecycle events so renderer settings can refresh plugin state. -- Development sideloading, if needed, must be gated behind a development build or explicit developer - mode and must never be available as the normal production install path. - -### Official Source Policy - -- Production plugin discovery and update metadata must come from a DeepChat-owned source. -- Plugin package URLs must match the official source allowlist. -- Plugin signatures must chain to a DeepChat-controlled signing identity. -- Plugin ids must be reserved by the official source registry; a package cannot claim another - official plugin id. -- The installer must show plugin capabilities before enabling a newly installed plugin. -- Plugin updates must preserve the same plugin id and trusted publisher identity. -- Local file install is allowed only for development builds or an explicit developer-mode workflow. - -### Manifest - -The CUA plugin manifest must be enough to describe first-party runtime integration: - -```json -{ - "id": "com.deepchat.plugins.cua", - "name": "CUA Computer Use Runtime", - "version": "1.0.0", - "publisher": "DeepChat", - "engines": { - "deepchat": ">=1.1.0", - "platforms": ["darwin"] - }, - "activationEvents": ["onEnable"], - "capabilities": [ - "runtime.manage", - "mcp.register", - "skills.register", - "settings.contribute", - "shell.openExternal", - "process.execDeclared" - ], - "settings": { - "entry": "settings/index.html", - "preloadTypes": "types/settings-preload.d.ts" - } -} -``` - -The manifest may contain declarative runtime, MCP, skill, settings, and tool-policy contributions. -Activation code may refine those contributions after runtime detection. - -### Plugin APIs - -Plugins should primarily add agent capabilities through MCP servers and skills. If a plugin needs -DeepChat app capabilities that cannot be expressed through MCP or skills, core may expose a very -small main-process SDK through `PluginContext`. - -Rules: - -- Main SDK methods must be capability-gated by manifest declarations. -- Main SDK methods must be stable, typed, and reviewed as core API surface. -- Main SDK methods must not expose raw presenters, raw stores, arbitrary IPC, or Electron objects. -- Renderer-side plugin APIs must be defined by plugin-specific preload `.d.ts` files. -- Plugin developers should build settings pages against the preload type definitions, not against - DeepChat renderer internals. - -### Resource Ownership - -Every resource registered by a plugin must have an owner: - -```typescript -type ManagedResourceKind = 'runtime' | 'mcpServer' | 'skill' | 'settings' | 'toolPolicy' - -type ManagedResource = { - ownerPluginId: string - kind: ManagedResourceKind - key: string - payload: unknown - enabled: boolean - createdAt: number - updatedAt: number -} -``` - -Core must support: - -- `disableByOwner(pluginId)` to unregister runtime-visible resources without deleting plugin files. -- `removeByOwner(pluginId)` to remove all resource records and stop owned runtime services. -- Owner-aware startup repair so stale plugin-owned MCP, skill, settings, and policies do not survive - a deleted plugin. - -### Runtime Registry - -- Core must support plugin-owned external-helper runtimes. -- Runtime detection must be allowed only through declared commands and declared paths. -- Runtime records must store detected command, version, status, install source, last check time, and - last error. -- CUA plugin runtime detection must check, at minimum: - - `/Applications/CuaDriver.app/Contents/MacOS/cua-driver` - - `~/.local/bin/cua-driver` - - `PATH:cua-driver` -- The registry should tolerate additional official install paths if upstream changes them. -- Runtime install and uninstall actions must require user confirmation. - -### Managed MCP Registry - -- Plugins must be able to register MCP servers without writing raw MCP config directly. -- Plugin-owned MCP servers must be identifiable by `ownerPluginId`. -- Disabling the plugin must stop and unregister its MCP servers. -- Deleting the plugin must remove plugin-owned MCP config records. -- User-created MCP servers must never be removed by plugin disable/delete. -- CUA plugin must register a stdio MCP server equivalent to: - -```json -{ - "id": "cua-driver", - "displayName": "CUA Driver", - "transport": "stdio", - "command": "${runtime.cua-driver.command}", - "args": ["mcp"], - "autoApprove": [] -} -``` - -### Skill Registry - -- Plugins must be able to contribute skill roots without copying files into built-in resources or - the user's personal skills directory. -- Plugin-owned skills must be visible only while the owning plugin is enabled. -- Plugin-owned skills must be removed from available skill metadata, `skill_view`, pinned prompt - content, and active skill validation when the plugin is disabled or deleted. -- The CUA plugin must contribute `skills/cua-driver/SKILL.md`. -- The CUA plugin may copy upstream CUA skill content into the plugin package and maintain - DeepChat-specific additions in plugin-owned files. - -### Tool Policy Registry - -- Core must replace CUA-specific permission logic with generic plugin-owned tool policy entries. -- Policy must be evaluated before fallback read/write name heuristics. -- Supported decisions: - - `allow` - - `ask` - - `deny` -- CUA plugin default policy: - -```json -{ - "cua-driver.check_permissions": "allow", - "cua-driver.list_apps": "allow", - "cua-driver.list_windows": "allow", - "cua-driver.get_screen_size": "allow", - "cua-driver.get_window_state": "allow", - "cua-driver.get_accessibility_tree": "allow", - "cua-driver.get_cursor_position": "allow", - "cua-driver.screenshot": "allow", - "cua-driver.launch_app": "ask", - "cua-driver.click": "ask", - "cua-driver.right_click": "ask", - "cua-driver.double_click": "ask", - "cua-driver.scroll": "ask", - "cua-driver.move_cursor": "ask", - "cua-driver.type_text": "ask", - "cua-driver.type_text_chars": "ask", - "cua-driver.press_key": "ask", - "cua-driver.hotkey": "ask", - "cua-driver.set_value": "ask", - "cua-driver.set_config": "ask", - "cua-driver.set_recording": "ask", - "cua-driver.replay_trajectory": "ask", - "cua-driver.zoom": "ask" -} -``` - -`autoApprove` must remain empty by default. - -### Settings Contribution - -- Core must provide a generic Settings > Plugins page. -- Plugins must package settings as a standalone web bundle. -- DeepChat must load plugin settings in an isolated renderer/webContents, not inside the main - DeepChat renderer component tree. -- The isolated settings renderer must use `contextIsolation`, no Node integration, and a dedicated - plugin preload. -- The dedicated preload must expose only plugin-scoped typed APIs declared for that plugin. -- Plugin settings UI must not access DeepChat renderer stores, global IPC, Vue app state, or raw - Electron APIs. -- Plugin settings UI must not depend on CUA-specific core routes. -- Plugin settings data/config APIs must be defined as plugin-owned typed APIs and routed through the - plugin host. -- CUA status text, permission guidance, install guidance, and helper uninstall actions belong to the - plugin. - -ASCII layout: - -```text -Settings -+-- Plugins - +-- CUA Computer Use Runtime - +------------------------------------------------+ - | CUA Computer Use Runtime | - | Runtime CuaDriver 0.x | - | Helper /Applications/CuaDriver.app | - | MCP Registered / Running | - | Skill Active | - | Permissions | - | Accessibility Granted | - | Screen Recording Missing | - | | - | [Open Permission Guide] [Check Again] | - | [Disable Plugin] [Uninstall Helper] | - +------------------------------------------------+ -``` - -### Packaging - -- Add a top-level plugin source directory for first-party plugins, starting with CUA: - -```text -plugins/ - cua/ - plugin.json - package.json - src/ - settings/ - index.html - src/ - types/ - settings-preload.d.ts - skills/cua-driver/ - mcp/cua-driver.json - policies/tool-policy.json -``` - -- Add plugin packaging scripts that produce bundled macOS plugin packages: - -```text -build/bundled-plugins/deepchat-plugin-cua--darwin-.dcplugin -``` - -- The `.dcplugin` package must contain manifest, compiled plugin code, settings bundle, skills, - preload type definitions, bundled helper runtime, checksums, and signature metadata. -- DeepChat app packaging must include the bundled CUA plugin package inside the macOS app resources. -- Release CI must upload DeepChat app artifacts without publishing `.dcplugin` files separately. - -### Migration - -- The current built-in branch should be split: - - Generic plugin infrastructure PR. - - `deepchat-plugin-cua` PR. -- Current CUA-specific core files should not survive in final core. -- Existing `deepchat/computer-use` MCP config entries created by the demo branch should be removed - or migrated to plugin-owned `cua-driver` entries only if the CUA plugin is installed and enabled. -- Existing user-created MCP servers and skills must not be affected. - -## Acceptance Criteria - -### Core Grep - -- `src/main`, `src/renderer`, and `src/shared` contain no CUA-specific symbols: - - `computerUse` - - `ComputerUse` - - `cua-driver` - - `CuaDriver` -- Allowed generic symbols include: - - `PluginHost` - - `RuntimeRegistry` - - `ManagedMcpRegistry` - - `SkillRegistry` - - `SettingsContribution` - - `ToolPolicy` - - `ownerPluginId` - -### Official Source - -- Production builds can install/update plugins only from the official DeepChat plugin source. -- A local `.dcplugin` chosen by a user is rejected in production builds. -- A plugin with an untrusted publisher, unknown signature, mismatched checksum, or non-official - source URL is rejected before activation. -- Developer sideloading works only in development builds or explicit developer mode. - -### Plugin Enable - -- CUA plugin appears in Settings > Plugins. -- CUA plugin settings are rendered in an isolated plugin renderer with its own preload. -- CUA settings code can call only the CUA plugin typed preload API and generic plugin host APIs. -- Missing helper state shows install guidance and registers no running CUA tools. -- Installed helper state shows detected path and version. -- Accessibility and Screen Recording state is read from Cua Driver helper behavior. -- `cua-driver` MCP contribution registers and can start through the normal MCP flow. -- `cua-driver` skill appears in available skills and can be injected into agent context. -- `click`, `type_text`, and `hotkey` default to a tool approval prompt. - -### Plugin Disable - -- CUA MCP server disappears from DeepChat MCP runtime state. -- CUA skill disappears from available skills and active prompt context. -- CUA tool policies are not evaluated. -- CUA settings card disappears or changes to disabled plugin state. -- Existing sessions cannot call CUA tools after registry refresh. -- External `CuaDriver.app` remains installed. - -### Plugin Delete - -- Plugin files are removed from the plugin install directory. -- Plugin storage is removed. -- Plugin-owned resource records are removed. -- DeepChat restart does not restore stale CUA MCP, skill, settings, or policy records. -- Optional helper uninstall only runs after explicit user confirmation. - -### External Helper - -- Cua Driver macOS permissions are granted to `CuaDriver.app`, not DeepChat. -- DeepChat app does not request Accessibility or Screen Recording for CUA. -- DeepChat build and notarization do not depend on Swift, CUA source, or CUA helper entitlements. - -### CI - -- DeepChat core app build succeeds without CUA plugin build steps. -- CUA plugin build produces a `.dcplugin` artifact. -- Release CI publishes app artifacts and plugin artifacts separately. -- Plugin package validation verifies manifest schema, checksums, signature metadata, and required - files. - -### Settings Isolation - -- Plugin settings cannot import or access DeepChat renderer stores/components. -- Plugin settings cannot call arbitrary DeepChat IPC channels. -- Plugin settings cannot access Node APIs. -- Plugin settings preload API has generated or checked TypeScript declarations. -- Plugin development docs point plugin authors at the preload `.d.ts` API surface. - -## Assumptions - -- First production increment supports only signed official-source plugins. -- Local plugin development is available only through development builds or an explicit developer - mode that is not part of normal production installation. -- Broader third-party plugin execution requires a stronger isolated extension host, review process, - and marketplace policy, and is outside this CUA migration scope. -- Official Cua Driver continues to expose a stdio MCP mode through `cua-driver mcp`. -- Official Cua Driver remains responsible for macOS TCC permission ownership and helper lifecycle. - -## References - -- Existing demo spec: `docs/archives/mac-computer-use/` -- Existing SDD guide: `docs/spec-driven-dev.md` -- Cua Driver introduction: https://cua.ai/docs/cua-driver/guide/getting-started/introduction -- Cua Driver installation and MCP registration: https://cua.ai/docs/cua-driver/guide/getting-started/installation diff --git a/docs/archives/cua-runtime-plugin/tasks.md b/docs/archives/cua-runtime-plugin/tasks.md deleted file mode 100644 index b83c2a119..000000000 --- a/docs/archives/cua-runtime-plugin/tasks.md +++ /dev/null @@ -1,332 +0,0 @@ -# CUA Runtime Plugin Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Feature: `cua-runtime-plugin` -Spec: [spec.md](./spec.md) -Plan: [plan.md](./plan.md) - -## Status Legend - -- `[ ]` Not started -- `[~]` In progress -- `[x]` Complete - -## M0 Specification - -- [x] Create SDD artifacts under `docs/archives/cua-runtime-plugin/`. -- [x] Capture current built-in CUA demo coupling points. -- [x] Define final target as generic Plugin Host plus installable CUA runtime plugin. -- [x] Define production plugin installation as official-source only for the first increment. -- [x] Define plugin settings isolation through a standalone web bundle, isolated renderer, and - plugin-specific typed preload API. -- [x] Keep implementation code untouched during the specification step. - -## M1 Core Plugin Contracts - -- [x] Add shared plugin manifest types and schema. -- [~] Add official plugin source trust policy: - - source URL allowlist - - publisher identity - - plugin id reservation - - checksum verification - - signature metadata verification -- [x] Add plugin lifecycle route contracts: - - `plugins.list` - - `plugins.get` - - `plugins.enable` - - `plugins.disable` - - `plugins.invokeAction` -- [x] Add renderer API client for generic plugin routes. -- [~] Add package path validation that rejects absolute paths, `..`, drive-letter paths, and unsafe - symlinks inside plugin packages. -- [ ] Reject arbitrary local `.dcplugin` install in production builds. -- [ ] Gate local plugin development behind development builds or explicit developer mode. -- [ ] Add tests for valid and invalid plugin manifests. -- [ ] Add tests for unsupported `engines.deepchat` and unsupported platform manifests. -- [ ] Add tests for non-official source, untrusted publisher, bad signature, and checksum mismatch. - -Validation: - -- [ ] `pnpm test -- test/main/routes/contracts.test.ts` -- [ ] Focused plugin manifest test suite passes. -- [ ] Production install path rejects non-official plugin packages before activation. - -## M2 Plugin Installation And Resource Store - -- [x] Add `PluginHost` initialization in main presenter startup. -- [x] Add `PluginInstallationRecord` persistence. -- [x] Add `PluginResourceRecord` persistence. -- [x] Add `RuntimeDependencyRecord` persistence or store abstraction. -- [x] Implement `disableByOwner(pluginId)`. -- [x] Implement `removeByOwner(pluginId)`. -- [x] Implement startup repair for resources whose owning plugin is missing. -- [ ] Add tests for disable, delete, update, and startup repair. - -Validation: - -- [ ] Disabling a fixture plugin disables all owned resources. -- [ ] Deleting a fixture plugin removes all owned resources and plugin storage. -- [ ] Restart simulation does not revive stale owned resources. - -## M3 Runtime Registry - -- [ ] Add `RuntimeRegistry.register`. -- [ ] Add `RuntimeRegistry.unregisterByOwner`. -- [x] Add runtime status refresh support. -- [ ] Add declared command execution API: - - command id - - executable/path arguments - - timeout - - stdout/stderr size limit - - environment allowlist -- [~] Add shell open API for declared external URLs or system settings URLs. -- [ ] Add tests for declared command allow/deny behavior. -- [ ] Add minimal main SDK capability gate for plugin storage, declared process execution, shell - opening, and registry access. -- [ ] Add tests that plugins cannot access raw presenters, raw stores, arbitrary IPC, or Electron - objects through the SDK. - -Validation: - -- [ ] Plugins cannot execute undeclared commands. -- [ ] Runtime records carry owner, status, command, version, and last error. -- [ ] Plugin main code can use only declared SDK capabilities. - -## M4 Managed MCP Registry - -- [x] Add `ownerPluginId` to plugin-owned MCP server records without breaking user MCP config. -- [ ] Add `ManagedMcpRegistry.register`. -- [ ] Add `ManagedMcpRegistry.unregisterByOwner`. -- [x] Stop running plugin-owned MCP servers before unregistering them. -- [ ] Refresh MCP clients and tool caches after managed changes. -- [x] Add conflict handling for user-owned server names. -- [ ] Add tests that user-owned MCP servers survive plugin disable/delete. - -Validation: - -- [ ] Enabling a fixture plugin registers its MCP server. -- [ ] Disabling the fixture plugin removes only its MCP server. -- [ ] User-created MCP servers with similar names are untouched. - -## M5 Tool Policy Registry - -- [x] Add `ToolPolicyRegistry.register`. -- [x] Add `ToolPolicyRegistry.unregisterByOwner`. -- [x] Add exact policy lookup by server id and original tool name. -- [x] Evaluate policy in MCP permission pre-check before fallback heuristics. -- [x] Evaluate policy in MCP execution path before fallback heuristics. -- [ ] Add `allow`, `ask`, and `deny` behavior tests. -- [x] Remove CUA-specific read/write tool sets from core. - -Validation: - -- [ ] `allow` skips permission prompt. -- [ ] `ask` returns a permission request. -- [ ] `deny` blocks execution with a clear message. -- [ ] Unknown tools still use existing fallback heuristics. - -## M6 Skill Registry Contributions - -- [x] Add `SkillRegistry.register`. -- [x] Add `SkillRegistry.unregisterByOwner`. -- [x] Extend SkillPresenter discovery to merge plugin-owned skill roots. -- [x] Hide plugin-owned skills when owner plugin is disabled. -- [x] Filter disabled plugin-owned skills from: - - metadata list - - `skill_view` - - active skill validation - - prompt content loading - - allowed tools lookup -- [x] Remove CUA-specific skill visibility and auto-pin logic from core. -- [x] Add tests for plugin-owned skill enable/disable behavior. - -Validation: - -- [ ] Plugin-owned skill appears while plugin enabled. -- [ ] Plugin-owned skill disappears while plugin disabled. -- [ ] Existing user skills continue to work. - -## M7 Isolated Settings Contribution Host - -- [x] Add Settings > Plugins page or section. -- [x] Add generic plugin list UI. -- [x] Add settings contribution registry. -- [ ] Define settings contribution metadata: - - standalone HTML entry - - settings asset root - - dedicated preload path - - preload type declaration path -- [x] Load plugin settings as a standalone web bundle in an isolated renderer/webContents. -- [x] Enable `contextIsolation` for plugin settings renderers. -- [x] Disable Node integration for plugin settings renderers. -- [x] Add a dedicated plugin settings preload that exposes only plugin-scoped typed APIs. -- [x] Add generated or checked `settings-preload.d.ts` support for plugin developers. -- [x] Add generic plugin action bridge for settings UI actions. -- [x] Add plugin-owned typed API bridge for CUA settings actions. -- [ ] Block plugin settings access to DeepChat renderer stores, global IPC, Vue app state, and raw - Electron APIs. -- [x] Remove CUA-specific settings card from core MCP settings. -- [ ] Add renderer tests for plugin list and settings contribution states. - -Validation: - -- [ ] A fixture plugin settings web bundle appears when plugin enabled. -- [ ] The settings renderer is destroyed or shows disabled state when plugin disabled. -- [ ] Plugin settings can call only its preload API and generic plugin host APIs. -- [ ] Plugin settings cannot call arbitrary DeepChat IPC channels. -- [ ] Plugin developers can compile against the preload `.d.ts` without importing DeepChat renderer - internals. -- [ ] No CUA-specific renderer route or component remains in core. - -## M8 Plugin Packaging Toolchain - -- [x] Add top-level `plugins/` workspace support if needed. -- [x] Add `.dcplugin` package builder. -- [x] Add `.dcplugin` validator. -- [x] Add checksum generation. -- [x] Package the macOS CUA helper binary into the plugin artifact. -- [ ] Add signature metadata handling. -- [x] Add official-source metadata validation. -- [x] Require settings HTML and preload type declarations when `settings.contribute` is declared. -- [ ] Add package fixture tests. -- [x] Add scripts: - - `plugin:cua:build` - - `plugin:cua:package` - - `plugin:cua:validate` - - `plugin:cua:package:mac:arm64` - - `plugin:cua:package:mac:x64` - -Validation: - -- [ ] Valid fixture package passes. -- [ ] Missing `plugin.json` fails. -- [ ] Missing declared skill file fails. -- [ ] Missing declared settings entry fails. -- [ ] Missing declared preload `.d.ts` fails. -- [ ] Checksum mismatch fails. -- [ ] Non-official source metadata fails in production validation. - -## M9 CUA Plugin Source - -- [x] Add `plugins/cua/plugin.json`. -- [x] Add CUA runtime locator. -- [x] Add CUA helper version/status checks. -- [x] Add CUA permission status checks. -- [x] Add CUA install guide action. -- [ ] Add optional CUA helper uninstall action with confirmation. -- [x] Resolve bundled helper app paths from plugin runtime records. -- [x] Add `mcp/cua-driver.json`. -- [x] Inject CUA MCP env from plugin runtime template values. -- [x] Add `policies/tool-policy.json`. -- [x] Add `skills/cua-driver/SKILL.md` and related upstream skill docs. -- [x] Rewrite CUA skill docs to guide model actions through MCP tools only. -- [x] Inject plugin root, process architecture, and owner plugin context into plugin-owned skills. -- [x] Keep CUA driver MCP-mode cache errors focused on `get_window_state` ordering. -- [x] Add CUA settings standalone web bundle for missing helper, installed helper, permissions, MCP, - and skill state. -- [x] Add CUA plugin settings preload `.d.ts`. -- [x] Add GitHub Release install fallback and local `.dcplugin` picker in Settings > Plugins. -- [x] Add CUA plugin-specific typed settings API implementation: - - `getRuntimeStatus` - - `checkPermissions` - - `openPermissionGuide` - - `uninstallHelper` -- [~] Add CUA plugin tests with mocked runtime detection and static package assertions. - -Validation: - -- [ ] Missing helper shows install guidance. -- [x] Installed helper shows version and path. -- [x] Plugin enable registers CUA MCP, skill, settings, and policy resources. -- [x] Plugin enable starts the CUA MCP server when global MCP is ready and enabled. -- [x] Plugin disable unregisters CUA MCP, skill, settings, and policy resources. -- [ ] CUA settings UI works through only the plugin-specific preload API. - -## M10 Remove Built-In CUA Demo Code - -- [x] Remove `src/main/presenter/computerUsePresenter`. -- [x] Remove `src/renderer/api/ComputerUseClient.ts`. -- [x] Remove `src/shared/contracts/routes/computerUse.routes.ts`. -- [x] Remove `src/shared/types/computerUse.ts`. -- [x] Remove `src/renderer/settings/components/ComputerUseSettingsCard.vue`. -- [x] Remove `resources/skills/cua-driver`. -- [x] Remove `vendor/cua-driver`. -- [x] Remove `scripts/build-cua-driver.mjs`. -- [x] Remove `scripts/update-cua-driver.mjs`. -- [x] Remove `build/entitlements.computer-use.plist`. -- [x] Remove CUA helper handling from `scripts/afterPack.js`. -- [x] Remove CUA helper steps from build and release workflows. -- [x] Remove CUA-specific tests or convert them into plugin/generic infrastructure tests. - -Validation: - -- [x] Core grep acceptance passes. -- [x] macOS app packaging no longer contains CUA helper or vendored source. -- [ ] Existing MCP, skills, and settings tests still pass. - -## M11 CI And Release - -- [x] Add plugin build job to build workflow. -- [x] Add plugin build job to release workflow. -- [x] Upload `.dcplugin` as a separate artifact. -- [x] Require both macOS CUA plugin architecture assets before release upload. -- [x] Keep each CUA `.dcplugin` limited to its target runtime architecture. -- [ ] Publish or attach official-source metadata for the CUA plugin artifact. -- [x] Keep core app artifact naming unchanged. -- [ ] Ensure app release jobs do not depend on plugin jobs unless publishing requires all artifacts. -- [x] Document plugin artifact installation path and manual QA flow. - -Validation: - -- [ ] Core app CI succeeds without CUA helper build. -- [x] Plugin CI produces `deepchat-plugin-cua--darwin-x64.dcplugin`. -- [x] Plugin CI produces `deepchat-plugin-cua--darwin-arm64.dcplugin`. -- [x] Release assets include app artifacts and plugin artifacts separately. -- [ ] Production install validates the released CUA plugin as official-source trusted. - -## M12 End-To-End QA - -- [ ] macOS without Cua Driver: - - plugin installs from official source - - plugin enables - - missing helper state appears - - no CUA MCP tools run -- [ ] macOS with Cua Driver: - - helper version detected - - permissions detected - - MCP starts - - skill appears - - read/status tools work - - action tools ask for approval -- [ ] Settings isolation: - - CUA settings load in isolated renderer - - CUA settings preload exposes only CUA typed API - - CUA settings cannot access DeepChat renderer globals or arbitrary IPC -- [ ] Disable: - - MCP stops - - skill disappears - - settings contribution disappears or becomes disabled - - policies disappear -- [ ] Delete: - - plugin directory removed - - resource records removed - - restart has no stale CUA records -- [ ] External helper: - - `CuaDriver.app` remains installed after plugin disable/delete - - DeepChat has no CUA TCC permission ownership - -Validation: - -- [ ] Acceptance criteria from [spec.md](./spec.md) are checked and recorded in the PR. - -## Quality Gates - -- [ ] `pnpm run format` -- [ ] `pnpm run i18n` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] Focused main process tests -- [ ] Focused renderer tests -- [ ] Manual macOS CUA plugin QA diff --git a/docs/archives/default-model-settings/plan.md b/docs/archives/default-model-settings/plan.md deleted file mode 100644 index e295751b6..000000000 --- a/docs/archives/default-model-settings/plan.md +++ /dev/null @@ -1,130 +0,0 @@ -# 默认模型与默认视觉模型实施计划 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 当前实现基线 - -### 1.1 新建会话模型来源(现状) - -1. `src/renderer/src/components/NewThread.vue` 初始化模型时,优先“最近会话/偏好模型/第一个可用模型”。 -2. `src/main/presenter/sessionPresenter/managers/conversationManager.ts` 在 `createConversation()` 中默认继承最近会话 `settings`。 -3. 因此当前“新建会话默认模型”不稳定,会受最近会话影响。 - -### 1.2 imageServer 模型来源(现状) - -1. `src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts` 构造函数接收 `provider/model`。 -2. `src/main/presenter/mcpPresenter/inMemoryServers/builder.ts` 通过 `new ImageServer(args[0], args[1])` 传入。 -3. `src/renderer/src/components/mcp-config/mcpServerForm.vue` 存在 `imageServer` 专属模型选择 UI,并把选择写入 server `args`。 - -## 2. 设计决策 - -### 2.1 设置数据结构 - -新增两个设置键(存储于 `app-settings`): - -1. `defaultModel: { providerId: string; modelId: string }` -2. `defaultVisionModel: { providerId: string; modelId: string }` - -说明: - -1. 两者均通过现有 `configPresenter.getSetting/setSetting` 访问。 -2. 不新增独立 store 文件,先沿用现有配置存储体系。 - -### 2.2 新建会话默认模型决策 - -会话创建链路分两层处理: - -1. **Renderer 层(UI 体验)**:`NewThread.vue` 初始化时优先读 `defaultModel`(非 ACP)。 -2. **Main 层(最终兜底)**:`conversationManager.createConversation` 在调用方未显式传 `providerId/modelId` 时应用 `defaultModel`(非 ACP)。 - -规则: - -1. 显式传入 `providerId/modelId` 时不覆盖。 -2. `chatMode === 'acp agent'` 或目标 provider 为 `acp` 时不应用 `defaultModel`。 -3. `defaultModel` 未配置或无效时,回退到现有逻辑(保持兼容)。 - -### 2.3 默认视觉模型决策 - -1. `defaultVisionModel` 选择器只展示 `vision=true` 的已启用模型。 -2. 保存时做前置校验(非视觉模型不可保存)。 -3. `imageServer` 运行时读取 `defaultVisionModel`;不再依赖 `args`。 - -### 2.4 imageServer 架构调整 - -目标模块: - -1. `src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts` -2. `src/main/presenter/mcpPresenter/inMemoryServers/builder.ts` -3. `src/renderer/src/components/mcp-config/mcpServerForm.vue` - -调整方式: - -1. `ImageServer` 构造函数去掉 provider/model 参数。 -2. 每次视觉调用时动态读取 `defaultVisionModel` 并校验可用性。 -3. `mcpServerForm.vue` 删除 `imageServer` 专属模型选择与 args 反解析逻辑。 -4. `builder.ts` 改为 `new ImageServer()`。 - -### 2.5 兼容与迁移策略 - -1. 保留旧 `imageServer.args` 数据但不再使用(兼容读取,不破坏旧配置文件结构)。 -2. 不做强制迁移脚本;缺失 `defaultVisionModel` 时由运行时错误提示引导用户配置。 - -## 3. 实施阶段 - -### Phase 1:配置与类型接入 - -1. 新增 `defaultModel/defaultVisionModel` 的读写与默认空值处理。 -2. 补充必要类型定义(若现有类型未覆盖)。 - -### Phase 2:新建会话默认模型 - -1. 调整 `NewThread.vue` 初始化优先级(`defaultModel` 优先)。 -2. 调整 `conversationManager.createConversation` 的兜底模型决策。 -3. 校验 `fork` 路径未被覆盖。 - -### Phase 3:默认视觉模型与 imageServer - -1. 设置页新增 `defaultVisionModel` 选择项(vision-only)。 -2. 移除 `mcpServerForm.vue` 中 `imageServer` 模型配置 UI 与 args 绑定逻辑。 -3. `imageServer` 改为全局读取 `defaultVisionModel`。 -4. `builder.ts` 去除 `args[0]/args[1]` 注入。 - -### Phase 4:验证与收尾 - -1. 回归新建会话路径(UI 创建、主进程创建)。 -2. 回归 `imageServer` 调用成功与失败场景。 -3. 统一补 i18n 文案与错误提示。 - -## 4. 测试策略 - -### 4.1 Main 测试 - -1. `createConversation`:无显式模型时应用 `defaultModel`。 -2. `createConversation`:ACP 模式不应用 `defaultModel`。 -3. `forkConversation`:继承行为不变。 -4. `imageServer`:读取 `defaultVisionModel` 成功/缺失/无效分支。 - -### 4.2 Renderer 测试 - -1. `NewThread` 初始化模型优先级验证(`defaultModel` 优先)。 -2. 设置页视觉模型选择仅展示 vision 模型。 -3. `mcpServerForm` 不再展示 `imageServer` 模型选择控件。 - -## 5. 风险与缓解 - -1. 风险:部分隐式创建会话路径未经过 UI,仍可能走旧默认。 -缓解:在 `conversationManager.createConversation` 做主进程兜底。 - -2. 风险:用户升级后未配置 `defaultVisionModel` 导致 imageServer 报错。 -缓解:统一错误文案,明确引导至设置页。 - -3. 风险:`defaultModel` 与 `preferredModel` 语义冲突。 -缓解:明确优先级为 `defaultModel > preferredModel`(仅非 ACP)。 - -## 6. 质量门槛 - -1. `pnpm run format` -2. `pnpm run lint` -3. `pnpm run typecheck` -4. 关键 main/renderer 测试通过 diff --git a/docs/archives/default-model-settings/spec.md b/docs/archives/default-model-settings/spec.md deleted file mode 100644 index 4588143cd..000000000 --- a/docs/archives/default-model-settings/spec.md +++ /dev/null @@ -1,92 +0,0 @@ -# 默认模型与默认视觉模型规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 概述 - -新增两个全局设置项: - -1. `默认模型`(`defaultModel`) -2. `默认视觉模型`(`defaultVisionModel`) - -其中: - -1. `默认模型`用于所有“新建会话”默认模型选择(`fork` 例外,`acp` 模式例外)。 -2. `默认视觉模型`用于视觉场景,当前仅供内置 `imageServer` 使用。 -3. `imageServer` 现有的“按服务器 args 配置模型”能力移除,统一改为读取全局 `defaultVisionModel`。 -4. `默认视觉模型`只能选择具备 `vision` 能力的模型。 - -## 背景与动机 - -1. 当前新建会话模型会受到“最近会话/偏好模型”影响,缺少稳定的全局默认入口。 -2. `imageServer` 以 MCP 服务器局部参数维护模型,配置分散,和全局模型管理不一致。 -3. 视觉模型应统一做能力约束(`vision=true`),避免运行时才发现模型不支持图像输入。 - -## 用户故事 - -### US-1:新建会话统一默认模型 - -作为用户,我希望设置一次“默认模型”,以后新建会话时自动使用它,而不是被最近会话模型影响。 - -### US-2:ACP 模式不受影响 - -作为用户,我希望 ACP 会话仍按 ACP 机制选模型,不被“默认模型”覆盖。 - -### US-3:视觉能力统一入口 - -作为用户,我希望设置一个“默认视觉模型”,内置图片工具直接使用它,不再在 `imageServer` 里重复配置。 - -## 功能需求 - -### A. 新增全局设置项 - -- [ ] 新增 `defaultModel` 配置,数据结构为 `{ providerId: string, modelId: string }` -- [ ] 新增 `defaultVisionModel` 配置,数据结构为 `{ providerId: string, modelId: string }` -- [ ] 两项配置均通过 `configPresenter.getSetting/setSetting` 读写并持久化 - -### B. 新建会话默认模型规则 - -- [ ] 适用范围:所有“新建会话”路径(即调用 `createConversation` 创建新会话) -- [ ] 排除范围:`forkConversation`(以及基于分支语义的会话继承路径)不改,继续继承源会话模型 -- [ ] ACP 例外:当会话处于 `acp agent` 模式时,不应用 `defaultModel` -- [ ] 优先级:当调用方未显式传入 `providerId/modelId` 时,`defaultModel` 优先于“最近会话/旧偏好模型”逻辑 -- [ ] 当 `defaultModel` 未配置或已失效时,回退到当前现有兜底策略 - -### C. 默认视觉模型规则 - -- [ ] `defaultVisionModel` 的候选列表仅允许 `vision=true` 的已启用模型 -- [ ] 若用户尝试保存非视觉模型,需阻止并给出明确提示 -- [ ] 若 `defaultVisionModel` 未配置或失效,视觉调用返回可读错误并引导去设置页配置 - -### D. imageServer 统一使用全局视觉模型 - -- [ ] `imageServer` 不再从 MCP server `args` 读取 provider/model -- [ ] `imageServer` 每次视觉调用前从全局配置读取 `defaultVisionModel` -- [ ] `inMemoryServers/builder.ts` 中 `imageServer` 构造不再依赖 `args[0]/args[1]` -- [ ] MCP 配置表单中针对 `imageServer` 的模型选择 UI 移除 - -### E. 验收标准 - -- [ ] 在非 ACP 新建会话中,未手动改模型时默认使用 `defaultModel` -- [ ] `fork` 新会话继续继承原会话模型,不受 `defaultModel` 干预 -- [ ] ACP 新建会话不受 `defaultModel` 影响 -- [ ] `imageServer` 在已配置 `defaultVisionModel` 时可正常调用视觉能力 -- [ ] `imageServer` 在未配置/配置无效时给出明确错误(非静默失败) -- [ ] `imageServer` 相关 MCP args 模型配置入口已移除 - -## 非目标 - -1. 不改标题生成链路的模型选择策略(本次仅新增会话默认模型与视觉默认模型)。 -2. 不新增“按工具分别配置视觉模型”的能力(仅一个全局视觉模型)。 -3. 不修改 ACP 模型管理机制。 - -## 约束 - -1. 保持现有 Presenter 架构与 IPC 类型边界,不引入新通信通道。 -2. 保持设置持久化兼容,旧配置文件可继续加载。 -3. UI 文案必须走 i18n。 - -## 开放问题 - -无。 diff --git a/docs/archives/default-model-settings/tasks.md b/docs/archives/default-model-settings/tasks.md deleted file mode 100644 index a4e221b2c..000000000 --- a/docs/archives/default-model-settings/tasks.md +++ /dev/null @@ -1,60 +0,0 @@ -# 默认模型与默认视觉模型 Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## T0 规格确认 - -- [x] 完成 `spec.md` -- [x] 完成 `plan.md` -- [x] 完成 `tasks.md` - -## T1 配置层 - -- [ ] 在配置体系新增 `defaultModel` 设置读写(`providerId/modelId`)。 -- [ ] 在配置体系新增 `defaultVisionModel` 设置读写(`providerId/modelId`)。 -- [ ] 为设置页提供读取/保存接口(复用 `configPresenter.getSetting/setSetting`)。 - -## T2 新建会话默认模型(Renderer + Main) - -- [ ] 调整 `src/renderer/src/components/NewThread.vue` 初始化模型优先级:非 ACP 时优先 `defaultModel`。 -- [ ] 保持手动选模可覆盖默认值(仅默认初始化受影响)。 -- [ ] 在 `src/main/presenter/sessionPresenter/managers/conversationManager.ts` 中补主进程兜底:未显式模型且非 ACP 时应用 `defaultModel`。 -- [ ] 验证主进程自动建会话入口(如 in-memory server 调用 `createConversation`)同样生效。 -- [ ] 验证 `forkConversation` 路径不受影响。 - -## T3 设置页 UI - -- [ ] 在设置页新增“默认模型”选择项(全模型,排除 ACP provider)。 -- [ ] 在设置页新增“默认视觉模型”选择项(仅 `vision=true`)。 -- [ ] 补齐 i18n 文案(至少 `zh-CN` + `en-US`)。 -- [ ] 视觉模型保存时增加校验与错误提示。 - -## T4 imageServer 改造 - -- [ ] 修改 `src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts`:移除构造注入 provider/model,改为运行时读取 `defaultVisionModel`。 -- [ ] 修改 `src/main/presenter/mcpPresenter/inMemoryServers/builder.ts`:`imageServer` 改为无参构造。 -- [ ] 修改 `src/renderer/src/components/mcp-config/mcpServerForm.vue`:删除 `imageServer` 模型选择 UI、args 反解析与写回逻辑。 -- [ ] 保持其他 inmemory server 的 args 行为不变。 - -## T5 失败处理与提示 - -- [ ] `defaultVisionModel` 缺失时,`imageServer` 返回可读错误。 -- [ ] `defaultVisionModel` 指向非视觉或不可用模型时,`imageServer` 返回可读错误。 -- [ ] 错误提示文案包含“去设置中配置默认视觉模型”。 - -## T6 测试 - -- [ ] Main:`createConversation` 非 ACP 默认模型应用测试。 -- [ ] Main:ACP 场景不应用默认模型测试。 -- [ ] Main:`forkConversation` 不受影响测试。 -- [ ] Main:`imageServer` 读取 `defaultVisionModel` 成功/失败测试。 -- [ ] Renderer:默认视觉模型只展示 vision 模型测试。 -- [ ] Renderer:`mcpServerForm` 不再出现 `imageServer` 专属模型配置测试。 - -## T7 质量检查 - -- [ ] `pnpm run format` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] 跑相关测试并记录结果 diff --git a/docs/archives/i18n-missing-translations/plan.md b/docs/archives/i18n-missing-translations/plan.md deleted file mode 100644 index 94b1e096d..000000000 --- a/docs/archives/i18n-missing-translations/plan.md +++ /dev/null @@ -1,25 +0,0 @@ -# Plan - -## Scope - -The scan found four real missing key paths that are referenced from renderer code: - -- `mcp.errors.loadClientsFailed` -- `mcp.prompts.required` -- `promptSetting.uploadFailed` -- `settings.mcp.noServersDescription` - -`searchDisclaimer` is supplied from each locale `index.ts`, and `settings.display.*` is dynamically built from existing `text-sm`, `text-base`, `text-lg`, `text-xl`, and `text-2xl` keys, so those are not changed. - -## Implementation - -- Add the missing keys to every locale JSON file in the matching namespace. -- Re-run the i18n type generator so `src/types/i18n.d.ts` reflects the source locale. -- Validate with format, i18n check, and lint. - -## Test Strategy - -- Run `pnpm run i18n:types`. -- Run `pnpm run format`. -- Run `pnpm run i18n`. -- Run `pnpm run lint`. diff --git a/docs/archives/i18n-missing-translations/spec.md b/docs/archives/i18n-missing-translations/spec.md deleted file mode 100644 index e33478126..000000000 --- a/docs/archives/i18n-missing-translations/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# Missing i18n Translations - -## User Story - -As a DeepChat user, I want every supported locale to provide translations for UI keys that are currently used by the app so that the interface never falls back to raw i18n key strings. - -## Acceptance Criteria - -- All statically used i18n keys found missing from the active locale bundles are added to every supported locale. -- `pnpm run i18n` reports no missing or invalid translations. -- The generated i18n type definitions include the restored source-locale keys. - -## Non-Goals - -- Do not rewrite existing translations unrelated to missing keys. -- Do not remove stale extra keys that are not currently used by the UI. -- Do not change runtime i18n loading behavior. - -## Constraints - -- Keep the existing locale file layout under `src/renderer/src/i18n//`. -- Preserve interpolation placeholders such as `{count}` and `{serverName}` exactly where needed. diff --git a/docs/archives/i18n-missing-translations/tasks.md b/docs/archives/i18n-missing-translations/tasks.md deleted file mode 100644 index 1438e7925..000000000 --- a/docs/archives/i18n-missing-translations/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -- [x] Create SDD notes for the missing translation fix. -- [x] Add `mcp.errors.loadClientsFailed` and `mcp.prompts.required` in all locales. -- [x] Add `promptSetting.uploadFailed` in all locales. -- [x] Add `settings.mcp.noServersDescription` in all locales. -- [x] Regenerate i18n types and run validation commands. -- [x] Archive the completed SDD notes. diff --git a/docs/archives/legacy-agentpresenter-architecture.md b/docs/archives/legacy-agentpresenter-architecture.md deleted file mode 100644 index 2f710276e..000000000 --- a/docs/archives/legacy-agentpresenter-architecture.md +++ /dev/null @@ -1,320 +0,0 @@ -# DeepChat 整体架构概览 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -本文档从高层视角介绍 DeepChat 的系统架构,帮助开发者快速理解项目结构和组件关系。 - -> **Note (2026-03-09):** 本文档描述的是原始 AgentPresenter 架构。当前主入口是 `agentSessionPresenter` + `agentRuntimePresenter`(formerly `newAgentPresenter` + `deepchatAgentPresenter`),详见 [P0 Implementation Summary](./P0_IMPLEMENTATION_SUMMARY.md)。 - -## 🏗️ 核心组件关系 - -```mermaid -graph TB - subgraph "Main Process - 主进程" - EventBus[EventBus
事件总线] - - subgraph "会话管理层" - SessionP[SessionPresenter
会话生命周期] - SessionMgr[SessionManager
会话上下文] - MsgMgr[MessageManager
消息管理] - ConvMgr[ConversationManager
会话管理] - end - - subgraph "Agent 编排器层" - AgentP[AgentPresenter
Agent编排入口] - AgentLoop[agentLoopHandler
Agent Loop主循环] - StreamGen[streamGenerationHandler
流生成协调] - LoopOrch[loopOrchestrator
循环编排] - ToolCallProc[toolCallProcessor
工具调用处理] - LLMEvent[llmEventHandler
LLM事件处理] - PermHandler[permissionHandler
权限协调] - end - - subgraph "工具路由层" - ToolP[ToolPresenter
统一工具定义] - ToolMapper[ToolMapper
工具名称路由] - McpP[McpPresenter
MCP集成] - AgentToolMgr[AgentToolManager
Agent工具管理] - end - - subgraph "其他 Presenter" - LLMProviderP[LLMProviderPresenter
LLM提供商] - ConfigP[ConfigPresenter
配置管理] - WindowP[WindowPresenter
窗口管理] - TabP[TabPresenter
标签管理] - SQLiteP[SQLitePresenter
数据库] - end - - subgraph "渲染进程通信" - Renderer[渲染进程
Vue 3 + Pinia] - end - end - - EventBus --> SessionP - EventBus --> AgentP - EventBus --> McpP - EventBus --> ConfigP - EventBus --> LLMProviderP - - SessionP --> SessionMgr - SessionP --> MsgMgr - SessionP --> ConvMgr - - AgentP --> AgentLoop - AgentP --> StreamGen - AgentP --> PermHandler - AgentP --> SessionP - - AgentLoop --> LoopOrch - AgentLoop --> ToolCallProc - - StreamGen --> LLMEvent - StreamGen --> AgentLoop - - ToolCallProc --> ToolP - - ToolP --> ToolMapper - ToolP --> McpP - ToolP --> AgentToolMgr - - AgentLoop --> LLMProviderP - StreamGen --> LLMProviderP - - SessionMgr -.状态分享.-> AgentP - - EventBus -.事件推送.-> Renderer - Renderer -.IPC调用.-> SessionP - Renderer -.IPC调用.-> AgentP - - classDef layer1 fill:#e3f2fd - classDef layer2 fill:#fff3e0 - classDef layer3 fill:#f3e5f5 - classDef layer4 fill:#e8f5e9 - - class EventBus layer1 - class SessionP,SessionMgr,MsgMgr,ConvMgr layer2 - class AgentP,AgentLoop,StreamGen,LoopOrch,ToolCallProc,LLMEvent,PermHandler layer3 - class ToolP,ToolMapper,McpP,AgentToolMgr layer4 - class LLMProviderP,ConfigP,WindowP,TabP,SQLiteP layer1 -``` - -## 📐 分层架构 - -### 1. 会话管理层 - -**职责**:管理对话会话的完整生命周期、消息持久化、标签页绑定 - -| 组件 | 文件位置 | 行数 | 核心职责 | -|------|---------|------|---------| -| SessionPresenter | `src/main/presenter/sessionPresenter/index.ts` | 900 | 会话 CRD、消息 CRD、标签绑定、分支 | -| SessionManager | `src/main/presenter/sessionPresenter/session/sessionManager.ts` | 245 | 会话上下文解析、运行时状态、workspace 路径解析 | -| MessageManager | `src/main/presenter/sessionPresenter/managers/messageManager.ts` | ~400 | 消息持久化、变体处理、上下文获取 | -| ConversationManager | `src/main/presenter/sessionPresenter/managers/conversationManager.ts` | ~500 | 会话 CRUD、fork、子会话、标签绑定 | - -**关键数据结构**: -- `SessionContext` - 会话运行时状态(status, resolved, runtime) -- `SessionContextResolved` - 已解析的会话配置(chatMode, providerId, modelId, workspace) -- `SessionStatus` - 'idle' \| 'generating' \| 'paused' \| 'waiting_permission' \| 'error' - -### 2. Agent 编排器层 - -**职责**:管理 Agent Loop、LLM 流式响应、工具调用、权限协调 - -| 组件 | 历史模块标识 | 行数 | 核心职责 | -|------|---------|------|---------| -| AgentPresenter | `legacy agent runtime / agentPresenter/index.ts` | 472 | Agent 编排入口,sendMessage/cancelLoop/continueLoop | -| agentLoopHandler | `legacy agent runtime / loop/agentLoopHandler.ts` | 670 | Agent Loop 主循环(while 循环) | -| streamGenerationHandler | `legacy agent runtime / streaming/streamGenerationHandler.ts` | 645 | 流生成协调,准备上下文、启动 Stream | -| loopOrchestrator | `legacy agent runtime / loop/loopOrchestrator.ts` | ~30 | Loop 状态管理 | -| toolCallProcessor | `legacy agent runtime / loop/toolCallProcessor.ts` | 445 | 工具调用执行、结果处理 | -| llmEventHandler | `legacy agent runtime / streaming/llmEventHandler.ts` | ~400 | 标准化 LLM 事件到内部格式 | -| permissionHandler | `legacy agent runtime / permission/permissionHandler.ts` | ~600 | 权限请求响应协调 | -| messageBuilder | `legacy agent runtime / message/messageBuilder.ts` | ~285 | 提示词构建、上下文压缩 | - -**关键流程**: -1. 用户发送消息 → `AgentPresenter.sendMessage()` -2. 创建助手消息 → `SessionManager.startLoop()` 状态设为 `generating` -3. `StreamGenerationHandler` 准备上下文 → 启动 LLM Stream -4. `AgentLoopHandler` 的主 while 循环处理: - - 调用 `provider.coreStream()` 获取标准化事件流 - - 处理 text/reasoning/tool_call_start/tool_call_chunk/tool_call_end 事件 - - 遇到 tool_call_end 时执行 `ToolCallProcessor` - - 执行工具后继续循环或结束 - -### 3. 工具路由层 - -**职责**:统一管理所有工具(MCP + Agent)、工具名称解析、路由分发 - -| 组件 | 历史模块标识 | 行数 | 核心职责 | -|------|---------|------|---------| -| ToolPresenter | `src/main/presenter/toolPresenter/index.ts` | 161 | 统一工具定义接口、工具调用路由 | -| ToolMapper | `src/main/presenter/toolPresenter/toolMapper.ts` | ~100 | 工具名→来源映射(mcp/agent) | -| McpPresenter | `src/main/presenter/mcpPresenter/index.ts` | ~500 | MCP 服务器管理、工具定义、工具调用 | -| AgentToolManager | `legacy agent runtime / acp/agentToolManager.ts` | 577 | Agent 文件系统 + Browser 工具 | -| AgentFileSystemHandler | `legacy agent runtime / acp/agentFileSystemHandler.ts` | 960 | 文件系统工具实现 | - -**工具来源**: -1. **MCP 工具**:外部 MCP 服务器提供,通过 `McpPresenter` 管理 -2. **Agent 工具**: - - 文件系统工具(read_file, write_file, list_directory 等) - - Yo Browser 工具 - -**路由机制**: -- `ToolPresenter.getAllToolDefinitions()` 收集所有工具 -- `ToolMapper.registerTools()` 按工具名注册来源(mcp/agent) -- 名称冲突时优先 MCP -- `ToolPresenter.callTool()` 根据 `ToolMapper` 路由到对应处理器 - -### 4. 事件通信层 - -**职责**:主进程内事件广播、主进程→渲染进程事件推送 - -| 组件 | 文件位置 | 行数 | 核心职责 | -|------|---------|------|---------| -| EventBus | `src/main/eventbus.ts` | 152 | 统一事件发射和接收 | -| events.ts | `src/main/events.ts` | 263 | 事件常量定义 | - -**通信模式**: -- `sendToMain(eventName, ...args)` - 仅主进程内部 -- `sendToRenderer(eventName, SendTarget, ...args)` - 主→渲染进程 -- `sendToTab(tabId, eventName, ...args)` - 精确到特定标签 -- `sendToWindow(windowId, eventName, ...args)` - 窗口级别 - -**关键事件类别**: -- `STREAM_EVENTS` - 流生成事件(response, end, error) -- `CONVERSATION_EVENTS` - 会话事件(list_updated, activated, message_generated) -- `CONFIG_EVENTS` - 配置变更(setting_changed, provider_changed) -- `MCP_EVENTS` - MCP 状态(server_started, tool_call_result) -- `TAB_EVENTS` - 标签页事件(closed, renderer-ready) - -### 5. 多窗口管理层 - -| 组件 | 文件位置 | 行数 | 核心职责 | -|------|---------|------|---------| -| WindowPresenter | `src/main/presenter/windowPresenter/index.ts` | ~300 | BrowserWindow 生命周期 | -| TabPresenter | `src/main/presenter/tabPresenter/index.ts` | ~400 | WebContentsView 管理、跨窗口拖拽 | - -## 🔄 关键数据流 - -### 消息发送流程 - -```mermaid -sequenceDiagram - participant User as 用户 - participant Renderer as 渲染进程 - participant AgentP as AgentPresenter - participant StreamGen as StreamGenerationHandler - participant SessionMgr as SessionManager - participant AgentLoop as agentLoopHandler - participant LLMProvider as LLMProviderPresenter - participant ToolP as ToolPresenter - - User->>Renderer: 发送消息 - Renderer->>AgentP: sendMessage(agentId, content) - AgentP->>AgentP: 创建用户消息到数据库 - AgentP->>SessionMgr: startLoop(agentId, messageId) - Note over SessionMgr: status = 'generating' - AgentP->>AgentP: 创建助手消息 - AgentP->>StreamGen: startStreamCompletion() - StreamGen->>StreamGen: 准备上下文(用户消息、历史消息、搜索结果) - StreamGen->>AgentLoop: startStreamCompletion() - AgentLoop->>LLMProvider: provider.coreStream(messages, tools) - - loop Agent Loop - LLMProvider-->>AgentLoop: 流式 LLMCoreStreamEvent - AgentLoop->>AgentLoop: 处理 text/tool_call_start/tool_call_end - alt 有工具调用 - AgentLoop->>ToolP: callTool(toolCall) - ToolP-->>AgentLoop: 工具执行结果 - AgentLoop->>AgentLoop: 添加 tool_result 到上下文 - AgentLoop->>LLMProvider: 继续下一次 LLM 调用 - else 无工具调用 - AgentLoop->>AgentLoop: 提示 completion - end - end - - AgentLoop-->>Renderer: 通过 EventBus 发送流事件 -``` - -### 会话上下文解析 - -```typescript -// SessionManager.getSession(conversationId) -// → SessionManager.resolveSession(conversationId) -// → resolveSessionContext({ -// settings: conversation.settings, -// fallbackChatMode: 'chat', -// modelConfig: modelConfig -// }) - -// 返回 SessionContextResolved: -{ - chatMode: 'chat' | 'agent' | 'acp agent', - providerId: string, - modelId: string, - supportsVision: boolean, - supportsFunctionCall: boolean, - agentWorkspacePath: string | null, // agent 模式才有 - enabledMcpTools?: string[], - acpWorkdirMap?: Record // acp agent 模式 -} -``` - -### 工具调用路由 - -```typescript -// agentLoopHandler 获取工具定义 -const toolDefs = await toolPresenter.getAllToolDefinitions({ - enabledMcpTools, - chatMode, - supportsVision, - agentWorkspacePath -}) -// → 组合 MCP 工具 + Agent 文件系统工具 + Browser 工具 - -// LLM 返回 tool_call 后 -const response = await toolPresenter.callTool({ - id: toolCallId, - type: 'function', - function: { name, arguments: string }, - server: { name, icons, description } -}) -// → ToolMapper.getToolSource(name) -// → 若 'mcp' → mcpPresenter.callTool() -// → 若 'agent' → agentToolManager.callTool() -``` - -## 📁 核心文件位置速查 - -**会话管理**: -- SessionPresenter: `src/main/presenter/sessionPresenter/index.ts:1-900` -- SessionManager: `src/main/presenter/sessionPresenter/session/sessionManager.ts:1-245` -- MessageManager: `src/main/presenter/sessionPresenter/managers/messageManager.ts` -- ConversationManager: `src/main/presenter/sessionPresenter/managers/conversationManager.ts` - -**Agent 系统**: -- AgentPresenter: `legacy agent runtime / agentPresenter/index.ts` -- Agent Loop: `legacy agent runtime / loop/agentLoopHandler.ts` -- Stream Generation: `legacy agent runtime / streaming/streamGenerationHandler.ts` -- Message Builder: `legacy agent runtime / message/messageBuilder.ts` - -**工具系统**: -- ToolPresenter: `src/main/presenter/toolPresenter/index.ts:1-161` -- ToolMapper: `src/main/presenter/toolPresenter/toolMapper.ts` -- AgentToolManager: `legacy agent runtime / acp/agentToolManager.ts` -- AgentFileSystemHandler: `legacy agent runtime / acp/agentFileSystemHandler.ts` -- McpPresenter: `src/main/presenter/mcpPresenter/index.ts` - -**事件系统**: -- EventBus: `src/main/eventbus.ts:1-152` -- 事件常量: `src/main/events.ts:1-263` - -## 📚 深入阅读 - -- **会话管理详情**: [architecture/session-management.md](../architecture/session-management.md) -- **Agent 系统详解**: [architecture/agent-system.md](../architecture/agent-system.md) -- **工具系统详解**: [architecture/tool-system.md](../architecture/tool-system.md) -- **事件系统详解**: [architecture/event-system.md](../architecture/event-system.md) -- **核心流程**: [FLOWS.md](../FLOWS.md) -- **MCP 集成**: [architecture/mcp-integration.md](../architecture/mcp-integration.md) diff --git a/docs/archives/legacy-agentpresenter-flows.md b/docs/archives/legacy-agentpresenter-flows.md deleted file mode 100644 index b4e5249e4..000000000 --- a/docs/archives/legacy-agentpresenter-flows.md +++ /dev/null @@ -1,654 +0,0 @@ -# DeepChat 核心流程 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -本文档使用时序图详细描述 DeepChat 的关键业务流程,帮助开发者理解运行时行为。 - -> **Note (2026-03-09):** 本文档描述的是原始 AgentPresenter 流程。当前主流程入口是 `agentSessionPresenter` + `agentRuntimePresenter`(formerly `newAgentPresenter` + `deepchatAgentPresenter`);核心流程类似但入口不同。详见 [P0 Implementation Summary](./P0_IMPLEMENTATION_SUMMARY.md)。 - -## 1. 发送消息完整流程 - -```mermaid -sequenceDiagram - autonumber - participant User as 用户 - participant UI as ChatInput/ChatView.vue - participant Store as chatStore.sendMessage() - participant IPC as presenter:call (IPC) - participant AgentP as AgentPresenter.sendMessage() - participant MsgMgr as MessageManager - participant StreamGen as StreamGenerationHandler - participant SessionMgr as SessionManager - participant AgentLoop as agentLoopHandler - participant ToolP as ToolPresenter - participant LLM as LLMProviderPresenter - participant EventBus as EventBus - - User->>UI: 输入内容并点击发送 - UI->>Store: handleSend(message) - Store->>IPC: presenter:call(agentPresenter.sendMessage) - IPC->>AgentP: sendMessage(agentId, content) - - Note over AgentP,MsgMgr: 1. 创建用户消息 - AgentP->>MsgMgr: sendMessage(agentId, content, 'user') - MsgMgr-->>AgentP: userMessage - - Note over AgentP,MsgMgr: 2. 创建助手消息(初始为空) - AgentP->>MsgMgr: sendMessage(agentId, '[]', 'assistant') - MsgMgr-->>AgentP: assistantMessage - - Note over AgentP,SessionMgr: 3. 启动 Agent Loop - AgentP->>SessionMgr: startLoop(agentId, assistantMessage.id) - SessionMgr->>SessionMgr: status = 'generating' - - Note over AgentP,StreamGen: 4. 启动流生成 - AgentP->>StreamGen: startStreamCompletion(agentId) - StreamGen->>StreamGen: prepareConversationContext() - StreamGen->>StreamGen: processUserMessageContent() - alt 启用搜索 - StreamGen->>StreamGen: 执行搜索获取相关信息 - end - StreamGen->>StreamGen: preparePromptContent(上下文+搜索+图片) - - Note over StreamGen,AgentLoop: 5. 启动 Agent Loop - StreamGen->>AgentLoop: startStreamCompletion() - AgentLoop->>ToolP: getAllToolDefinitions() - ToolP-->>AgentLoop: toolDefs (MCP + Agent) - - AgentLoop->>LLM: provider.coreStream(messages, tools, modelConfig) - - loop Agent Loop 主循环 - Note over AgentLoop: 循环状态: toolCallCount < MAX_TOOL_CALLS - - LLM-->>AgentLoop: stream event (text/reasoning/tool_call/permission) - - alt text 事件 - AgentLoop->>EventBus: send STREAM_EVENTS.RESPONSE { content } - else reasoning 事件 - AgentLoop->>EventBus: send STREAM_EVENTS.RESPONSE { reasoning_content } - else tool_call_start 事件 - AgentLoop->>EventBus: send { tool_call: 'start', name, id } - else tool_call_chunk 事件 - AgentLoop->>EventBus: send { tool_call: 'update', params增量 } - else tool_call_end 事件 - Note over AgentLoop: 工具参数完整 - AgentLoop->>EventBus: send { tool_call: 'update', 完整params } - - alt ACP Provider - Note over AgentLoop: ACP 直接返回执行结果 - AgentLoop->>EventBus: send { tool_call: 'end', response } - else 非 ACP - Note over AgentLoop: 需要本地执行工具 - AgentLoop->>AgentLoop: currentToolCalls.push({id, name, arguments}) - end - else permission 事件 - AgentLoop->>EventBus: send { tool_call: 'permission-required' } - AgentLoop->>AgentLoop: needContinue = false (等待用户响应) - Note over AgentLoop: 退出循环等待用户批准 - end - - alt stop event - AgentLoop->>AgentLoop: 检查 stop_reason - alt tool_use - Note over AgentLoop: 继续循环 - else end/max_tokens - Note over AgentLoop: 结束循环 - Note over AgentLoop: 需要 break - end - end - end - - alt 有工具调用需要执行 - Note over AgentLoop,ToolP: 执行工具调用 - AgentLoop->>ToolP: callTool(toolCall[0]) - ToolP->>ToolP: ToolMapper 路由 - ToolP->>ToolP: 执行工具 (MCP 或 Agent) - ToolP-->>AgentLoop: toolResponse - AgentLoop->>EventBus: send { tool_call: 'running' } - AgentLoop->>EventBus: send { tool_call: 'end', response } - - Note over AgentLoop,AgentLoop: 添加工具结果到上下文 - AgentLoop->>AgentLoop: conversationMessages.push(tool_result) - AgentLoop->>AgentLoop: toolCallCount++ - AgentLoop->>AgentLoop: 继续下一次 LLM 调用 - end - - loop 继续循环 - AgentLoop->>LLM: coreStream (带工具结果) - end - - AgentLoop->>EventBus: send STREAM_EVENTS.END - AgentLoop->>SessionMgr: status = 'idle' -``` - -**关键文件位置**: -- AgentPresenter.sendMessage: `src/main/presenter/agentPresenter/index.ts:139-176` -- SessionManager.startLoop: `src/main/presenter/sessionPresenter/session/sessionManager.ts:140-150` -- StreamGenerationHandler.startStreamCompletion: `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts:54-179` -- agentLoopHandler.startStreamCompletion: `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts:145-668` - -## 2. 渲染与流式更新流程(含 Minimap) - -```mermaid -sequenceDiagram - autonumber - participant UI as ChatInput/ChatView (Renderer) - participant Store as chatStore (Renderer) - participant IPC as presenter:call (IPC) - participant AgentP as AgentPresenter (Main) - participant StreamGen as StreamGenerationHandler (Main) - participant LLM as LLMProviderPresenter (Main) - participant LLMH as LLMEventHandler (Main) - participant Sched as StreamUpdateScheduler (Main) - participant List as MessageList (Renderer) - - UI->>Store: send(message) - Store->>IPC: presenter:call(agentPresenter.sendMessage) - IPC->>AgentP: sendMessage(agentId, content) - AgentP->>StreamGen: generateAIResponse + startStreamCompletion - StreamGen->>LLM: startStreamCompletion() - LLM-->>LLMH: stream chunks - LLMH->>Sched: enqueueDelta(content/tool_call/usage) - Sched-->>Store: STREAM_EVENTS.RESPONSE (init/delta) - Store-->>List: update messageItems - LLMH-->>Sched: flushAll(final) - Sched-->>Store: STREAM_EVENTS.RESPONSE (final) - LLMH-->>Store: STREAM_EVENTS.END/ERROR -``` - -**关键文件位置**: -- chatStore.sendMessage + stream handlers: `src/renderer/src/stores/chat.ts` -- Presenter IPC: `src/renderer/api/legacy/presenters.ts`, `src/main/presenter/index.ts` -- AgentPresenter.sendMessage: `src/main/presenter/agentPresenter/index.ts` -- StreamGenerationHandler.startStreamCompletion: `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts` -- LLMEventHandler + StreamUpdateScheduler: `src/main/presenter/agentPresenter/streaming/llmEventHandler.ts`, `src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts` -- MessageList: `src/renderer/src/components/chat/MessageList.vue` - -## 3. Agent Loop 详细流程 - -```mermaid -sequenceDiagram - autonumber - participant StreamGen as StreamGenerationHandler - participant AgentLoop as agentLoopHandler - participant LLM as LLMProvider - participant ToolP as ToolPresenter - participant EventBus as EventBus - - StreamGen->>AgentLoop: startStreamCompletion() - - activate AgentLoop - AgentLoop->>AgentLoop: 初始化循环变量 - Note right of AgentLoop: conversationMessages, needContinue, toolCallCount - - loop while (needContinueConversation) - AgentLoop->>AgentLoop: 获取工具定义 (getAllToolDefinitions) - AgentLoop->>ToolP: getAllToolDefinitions({chatMode, workspace}) - ToolP-->>AgentLoop: toolDefs[] - - AgentLoop->>LLM: coreStream(conversationMessages, filteredToolDefs) - - loop 处理流事件 - LLM-->>AgentLoop: event (LLMCoreStreamEvent) - - alt event.type == 'text' - AgentLoop->>EventBus: send { content } - AgentLoop->>AgentLoop: currentContent += event.content - else event.type == 'reasoning' - AgentLoop->>EventBus: send { reasoning_content } - AgentLoop->>AgentLoop: currentReasoning += event.reasoning_content - else event.type == 'tool_call_start' - AgentLoop->>EventBus: send { tool_call: 'start', name, id } - AgentLoop->>AgentLoop: currentToolChunks[id] = {name, arguments_chunk: ''} - else event.type == 'tool_call_chunk' - AgentLoop->>EventLoop: send { tool_call: 'update', args } - AgentLoop->>AgentLoop: currentToolChunks[id].arguments_chunk += chunk - else event.type == 'tool_call_end' - AgentLoop->>AgentLoop: 完整合并参数 - alt providerId == 'acp' - Note over AgentLoop: ACP 已执行,直接返回结果 - AgentLoop->>EventBus: send { tool_call: 'end', response } - else 非 ACP - Note over AgentLoop: 需要执行工具 - AgentLoop->>AgentLoop: currentToolCalls.push({id, name, arguments}) - end - else event.type == 'permission' - AgentLoop->>EventBus: send { tool_call: 'permission-required' } - AgentLoop->>AgentLoop: 循环退出,等待用户响应 - else event.type == 'stop' - AgentLoop->>AgentLoop: 检查 stop_reason - alt stop_reason == 'tool_use' - Note over AgentLoop: needContinue = true - else 其他 - Note over AgentLoop: needContinue = false - end - end - end - - Note over AgentLoop: 添加 assistant 消息到上下文 - AgentLoop->>AgentLoop: conversationMessages.push({role: 'assistant', content: currentContent}) - - alt needContinue && currentToolCalls.length > 0 - Note over AgentLoop: 执行工具调用 - AgentLoop->>ToolP: 批量调用工具 - loop 执行每个工具 - ToolP-->>AgentLoop: toolResult - AgentLoop->>EventBus: 发送工具执行事件 - AgentLoop->>AgentLoop: conversationMessages.push(tool_result) - end - AgentLoop->>AgentLoop: toolCallCount++ - end - end - deactivate AgentLoop - - AgentLoop->>EventBus: send STREAM_EVENTS.END {userStop} -``` - -**关键代码位置**: -- agentLoopHandler 主循环: `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts:223-626` - -## 4. 工具调用路由流程 - -```mermaid -sequenceDiagram - autonumber - participant AgentLoop as agentLoopHandler - participant ToolP as ToolPresenter - participant Mapper as ToolMapper - participant McpP as McpPresenter - participant AgentToolMgr as AgentToolManager - participant FsHandler as AgentFileSystemHandler - - AgentLoop->>ToolP: callTool({id, function: {name, arguments}, server}) - - ToolP->>Mapper: getToolSource(name) - Mapper-->>ToolP: source ('mcp' or 'agent') - - alt source == 'mcp' - ToolP->>McpP: callTool(request) - Note over McpP: MCP 工具执行 - McpP-->>McpP: 获取工具定义 - McpP-->>McpP: 权限检查 - McpP->>McpP: 调用 MCP 服务器 - McpP-->>ToolP: toolResponse - else source == 'agent' - ToolP->>AgentToolMgr: callTool(name, args, conversationId) - - alt 工具名以 filesystem 开头 - AgentToolMgr->>FsHandler: read_file/write_file/list_directory - Note over FsHandler: 路径安全检查
执行文件操作 - FsHandler-->>AgentToolMgr: fileResult - else 工具是 browser 相关 - AgentToolMgr->>AgentToolMgr: 调用 Browser 工具 - AgentToolMgr-->>AgentToolMgr: browserResult - end - - AgentToolMgr-->>ToolP: toolResponse - end - - ToolP-->>AgentLoop: {content, rawData} -``` - -**工具定义收集流程**: - -```typescript -// 1. ToolPresenter.getAllToolDefinitions() -async getAllToolDefinitions({chatMode, supportsVision, agentWorkspacePath}) { - // 2. 获取 MCP 工具 - const mcpDefs = await mcpPresenter.getAllToolDefinitions() - this.mapper.registerTools(mcpDefs, 'mcp') - - // 3. chatMode != 'chat' 时获取 Agent 工具 - if (chatMode !== 'chat') { - const agentDefs = await agentToolManager.getAllToolDefinitions() - - // 4. 过滤名称冲突(优先 MCP) - const filtered = agentDefs.filter(t => !mapper.hasTool(t.name)) - this.mapper.registerTools(filtered, 'agent') - - return [...mcpDefs, ...filtered] - } - - return mcpDefs -} -``` - -**关键文件位置**: -- ToolPresenter: `src/main/presenter/toolPresenter/index.ts:49-99` -- ToolMapper: `src/main/presenter/toolPresenter/toolMapper.ts` -- AgentToolManager: `src/main/presenter/agentPresenter/acp/agentToolManager.ts` -- AgentFileSystemHandler: `src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts` - -## 5. 权限请求与响应流程(Batch-level Permission + Resume Lock) - -### 完整流程 - -```mermaid -sequenceDiagram - autonumber - participant AgentLoop as agentLoopHandler - participant ToolProc as toolCallProcessor - participant EventBus as EventBus - participant UI as PermissionDialog.vue - participant PermHandler as permissionHandler - participant SessionMgr as SessionManager - participant ToolP as ToolPresenter - participant McpP as McpPresenter - - Note over AgentLoop: Agent Loop 遇到权限请求 - AgentLoop->>ToolProc: process(toolCalls) - - Note over ToolProc: Step 1: 批量预检查权限 - ToolProc->>ToolProc: batchPreCheckPermissions() - - loop 遍历每个 toolCall - ToolProc->>ToolP: callTool(request) - ToolP->>McpP: callTool(request) - McpP->>McpP: checkToolPermission() - - alt 需要权限请求 - McpP-->>ToolP: requiresPermission: true - ToolP-->>ToolProc: permission required - ToolProc->>EventBus: send {tool_call: 'permission-required', ...} - - Note over SessionMgr: 添加到 pendingPermissions 队列 - ToolProc->>SessionMgr: addPendingPermission({messageId, toolCallId, ...}) - else 权限已授予 - McpP->>McpP: 执行工具 - McpP-->>ToolP: toolResult - ToolP-->>ToolProc: toolResult - end - end - - alt 有待处理权限 - ToolProc->>AgentLoop: 暂停,等待用户响应 - EventBus->>UI: 显示权限请求对话框 - UI->>User: 显示权限请求 - - User->>UI: 点击"允许"或"拒绝" - UI->>PermHandler: handlePermissionResponse(messageId, toolCallId, granted, permissionType) - - Note over PermHandler: Step 2: 批量更新权限块 - PermHandler->>PermHandler: updatePermissionBlocks() - Note over PermHandler: canBatchUpdate: 相同 tool_call.id 的权限批量更新 - - Note over SessionMgr: Step 3: 从队列移除 - PermHandler->>SessionMgr: removePendingPermission(conversationId, messageId, toolCallId) - - Note over PermHandler: Step 4: 获取 Resume Lock - PermHandler->>SessionMgr: acquirePermissionResumeLock(conversationId, messageId) - - Note over PermHandler: Step 5: 批准权限 - alt permissionType == 'command' - PermHandler->>PermHandler: CommandPermissionService.approve() - else agent-filesystem - PermHandler->>PermHandler: FilePermissionService.approve() - else deepchat-settings - PermHandler->>PermHandler: SettingsPermissionService.approve() - else MCP 权限 - PermHandler->>McpP: grantPermission(serverName, permissionType, remember) - else ACP 权限 - PermHandler->>PermHandler: handleAcpPermissionFlow() - end - - Note over PermHandler: Step 6: 恢复工具执行(CRITICAL SECTION) - PermHandler->>PermHandler: resumeToolExecutionAfterPermissions() - - Note over PermHandler: 6a: 验证 Resume Lock - PermHandler->>SessionMgr: getPermissionResumeLock(conversationId) - SessionMgr-->>PermHandler: currentLock - - alt Lock 无效或过期 - PermHandler->>SessionMgr: releasePermissionResumeLock(conversationId) - PermHandler->>PermHandler: 跳过执行 - else Lock 有效 - Note over PermHandler: 6b: 重新加载消息状态 - PermHandler->>PermHandler: 从 DB 刷新 generating state - - Note over PermHandler: 6c: SYNCHRONOUS FLUSH - PermHandler->>PermHandler: flushStreamUpdates(messageId) - - Note over PermHandler: 6d: 执行工具(Lock 保持) - loop 遍历已授权工具 - PermHandler->>ToolP: callTool() - ToolP->>McpP: callTool() - McpP-->>ToolP: toolResult - ToolP-->>PermHandler: toolResult - end - - Note over PermHandler: 6e: 再次 FLUSH - PermHandler->>PermHandler: flushStreamUpdates(messageId) - - Note over PermHandler: 6f: 检查是否还有更多权限 - PermHandler->>PermHandler: hasPendingPermissionsInMessage() - - alt 还有更多权限 - PermHandler->>SessionMgr: releasePermissionResumeLock(conversationId) - PermHandler->>UI: 通知前端更新 - else 所有权限已处理 - PermHandler->>PermHandler: continueAfterToolsExecuted() - PermHandler->>SessionMgr: releasePermissionResumeLock(conversationId) - PermHandler->>AgentLoop: 继续 Agent Loop - end - end - end -``` - -### 关键机制说明 - -#### 1. Batch-level Permission Update - -```typescript -// 同一个 tool_call 的多个权限块可以批量更新 -function canBatchUpdate(target, granted, grantedType): boolean { - // 必须相同状态: pending - // 必须相同类型: tool_call_permission - // 必须相同 server - // CRITICAL: 必须相同 tool_call.id(防止误批准其他工具) - // 权限层级必须满足: grantedType >= targetType -} -``` - -#### 2. Resume Lock(MessageId-level) - -```typescript -// 获取锁 -acquirePermissionResumeLock(conversationId: string, messageId: string): boolean - -// 验证锁(防止过期/错误的恢复) -getPermissionResumeLock(conversationId: string): {messageId, timestamp} | null - -// 释放锁(单一出口点) -releasePermissionResumeLock(conversationId: string): void - -// CRITICAL SECTION 保证: -// - 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) -``` - -#### 3. Pending Permissions Queue - -```typescript -// 支持多个并发权限请求 -interface PendingPermission { - messageId: string - toolCallId: string - permissionType: string - serverName: string - timestamp: number -} - -// SessionManager 管理队列 -pendingPermissions: PendingPermission[] - -// 队列操作 -addPendingPermission(conversationId, permission) -removePendingPermission(conversationId, messageId, toolCallId) -getNextPendingPermission(conversationId): PendingPermission | undefined -``` - -#### 4. Synchronous Flush - -```typescript -// 工具执行前同步刷新 UI 状态 -await llmEventHandler.flushStreamUpdates(messageId) - -// 保证: -// - 所有 tool_call 块已持久化到 DB -// - 前端 UI 状态已同步 -// - 断点恢复时状态一致 -``` - -### 权限类型层级 - -| 类型 | 层级 | 适用场景 | -|------|------|---------| -| `all` | 3 | 授予全部权限 | -| `write` | 2 | 写入操作(write_file, delete_file) | -| `read` | 1 | 读取操作(read_file, list_directory) | -| `command` | 0 | 命令执行(精确匹配) | - -**权限升级规则**:`all` > `write` > `read`,授予高级权限自动满足低级权限需求。 - -**关键文件位置**: -- PermissionHandler: `src/main/presenter/agentPresenter/permission/permissionHandler.ts` -- ToolCallProcessor: `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` -- SessionManager: `src/main/presenter/agentPresenter/session/sessionManager.ts` - -## 6. 会话生命周期 - -```mermaid -stateDiagram-v2 - [*] --> 未创建: 用户打开聊天界面 - - 未创建 --> 激活: 创建会话 (createConversation) - 未创建 --> 激活: 从列表选择会话 - - 激活 --> 生成中: 用户发送消息 (sendMessage) - - 生成中 --> 生成中: Agent Loop 循环执行工具 - 生成中 --> 等待权限: 工具需要权限 (permission-required) - 生成中 --> 已完成: LLM 完成(无工具或达到最大调用次数) - 生成中 --> 已取消: 用户停止生成 - - 等待权限 --> 生成中: 用户批准权限 - 等待权限 --> 已取消: 用户拒绝权限 - - 已完成 --> 激活: 用户继续对话 - 已完成 --> 已完成: 用户查看历史 - - 已取消 --> 激活: 用户重新发送消息 - - 激活 --> 暂停: 切换到其他 Tab - 暂停 --> 激活: 切换回该 Tab - - 激活 --> 分支: 用户选择分支 (forkConversation) - - 分支 --> 激活: 新建子会话(部分历史) - - 激活 --> 已删除: 用户删除会话 - - 已删除 --> [*] -``` - -**会话创建与绑定流程**: - -```mermaid -sequenceDiagram - participant UI as 聊天界面 - participant SessionP as SessionPresenter - participant ConvMgr as ConversationManager - participant SessionMgr as SessionManager - - UI->>SessionP: createConversation(title, settings, tabId) - SessionP->>ConvMgr: createConversation(title, settings, tabId) - ConvMgr->>ConvMgr: 持久化到 SQLite - ConvMgr-->>SessionP: conversationId - ConvMgr->>ConvMgr: setActiveConversation(conversationId, tabId) - Note over ConvMgr: 绑定到 tab - - UI->>SessionP: getActiveConversation(tabId) - SessionP->>ConvMgr: getActiveConversation(tabId) - ConvMgr-->>UI: conversation - - Note over UI,SessionP: 首次发送消息时 - UI->>SessionP: sendMessage(conversationId, content) - SessionP->>SessionMgr: getSession(conversationId) - Note over SessionMgr: 解析 SessionContextResolved - Note over SessionMgr: chatMode, providerId, modelId, workspace -``` - -**会话分支(Fork)流程**: - -```mermaid -sequenceDiagram - participant UI as 聊天界面 - participant SessionP as SessionPresenter - participant ConvMgr as ConversationManager - participant MsgMgr as MessageManager - - UI->>SessionP: forkConversation(conversationId, messageId, newTitle) - SessionP->>ConvMgr: forkConversation() - ConvMgr->>ConvMgr: 创建新会话 - ConvMgr->>MsgMgr: 复制消息到 targetMessageId(含变体选择) - Note over ConvMgr,MsgMgr: 只复制到目标消息及其父消息 - ConvMgr->>ConvMgr: 更新父会话关系 (parentConversationId, parentMessageId) - ConvMgr-->>UI: newConversationId -``` - -**关键文件位置**: -- ConversationManager: `src/main/presenter/sessionPresenter/managers/conversationManager.ts` -- forkConversation: `src/main/presenter/sessionPresenter/managers/conversationManager.ts:818-861` -- SessionManager.getSession: `src/main/presenter/sessionPresenter/session/sessionManager.ts:35-61` - -## 7. 继续生成(Continue)流程 - -```mermaid -sequenceDiagram - autonumber - participant UI as ChatView - participant AgentP as AgentPresenter - participant PermHandler as PermissionHandler - participant StreamGen as StreamGenerationHandler - participant AgentLoop as agentLoopHandler - participant McpP as McpPresenter - - UI->>AgentP: continueLoop(messageId) - - Note over AgentP,AgentP: 1. 检查是否是 maximum_tool_calls_reached - AgentP->>AgentP: createContinueMessage(agentId) - AgentP->>AgentP: sendMessage(agentId, '{"text":"continue"}', 'user') - AgentP->>AgentP: generateAIResponse 创建空助手消息 - - AgentP->>PermHandler: 继续之前的工具调用执行 - - alt 有待执行的工具调用 - PermHandler->>PermHandler: 解析最后 action block - PermHandler->>McpP: callTool(toolCall) - McpP-->>PermHandler: toolResponse - PermHandler->>EventBus: 发送 tool_call 事件 (running, end) - end - - AgentP->>PermHandler: 从断点继续 - PermHandler->>StreamGen: continueStreamCompletion(conversationId, messageId) - - Note over StreamGen: 2. 准备上下文 - StreamGen->>StreamGen: 准备历史消息(含工具执行结果) - StreamGen->>StreamGen: preparePromptContent(userContent='continue') - - StreamGen->>AgentLoop: startStreamCompletion(continue) - AgentLoop->>AgentLoop: 继续正常 LLM 调用流程 - AgentLoop->>UI: 流式返回内容 -``` - -**关键文件位置**: -- AgentPresenter.continueLoop: `src/main/presenter/agentPresenter/index.ts:178-204` -- StreamGenerationHandler.continueStreamCompletion: `src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts:181-350` - ---- - -> 💡 **提示**:所有时序图均基于当前实际代码结构绘制,代码位置标注了文件的 approximate 行数,方便快速定位。 - diff --git a/docs/archives/legacy-agentpresenter-retirement/plan.md b/docs/archives/legacy-agentpresenter-retirement/plan.md deleted file mode 100644 index c784e44b2..000000000 --- a/docs/archives/legacy-agentpresenter-retirement/plan.md +++ /dev/null @@ -1,30 +0,0 @@ -# Legacy AgentPresenter Retirement Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Execution Order - -1. migrate retained ACP helpers to `src/main/presenter/llmProviderPresenter/acp/` -2. migrate retained agent tools to `src/main/presenter/toolPresenter/agentTools/` -3. migrate retained message-formatting helpers to `src/main/presenter/sessionPresenter/` -4. remove legacy presenter/public type exposure and runtime wiring -5. archive retired source/tests -6. refresh active docs and cleanup specs -7. run format, i18n, lint, typecheck, tests, and cleanup guard - -## Design Rules - -1. keep compatibility-only data paths separate from active runtime -2. do not leave migration shims in the old folder if a live module owns the code now -3. prefer neutral/internal helper names over `legacy*` names when the function remains active -4. archive before delete when historical traceability matters - -## Verification - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- `pnpm run typecheck` -- targeted Vitest suites for provider/tools/presenter changes -- `node scripts/agent-cleanup-guard.mjs` diff --git a/docs/archives/legacy-agentpresenter-retirement/spec.md b/docs/archives/legacy-agentpresenter-retirement/spec.md deleted file mode 100644 index c1390e3ba..000000000 --- a/docs/archives/legacy-agentpresenter-retirement/spec.md +++ /dev/null @@ -1,56 +0,0 @@ -# Legacy AgentPresenter Retirement - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Summary - -Retire the live legacy `AgentPresenter -> SessionManager -> streaming/permission/loop` runtime -chain and make the current `agentSessionPresenter + agentRuntimePresenter` path the only active chat -execution flow. - -## Scope - -In scope: - -- remove live runtime wiring to legacy `AgentPresenter` -- remove public `agentPresenter` / `sessionPresenter` presenter exposure -- remove legacy `startStreamCompletion()` provider bridge -- migrate still-needed ACP helpers into `llmProviderPresenter/acp/` -- migrate still-needed agent tools into `toolPresenter/agentTools/` -- migrate retained message-formatting helpers into `sessionPresenter/` -- retire source and tests from the active tree and preserve history in docs -- document the retirement in active docs and specs - -Out of scope: - -- deleting legacy import support -- deleting old `conversations/messages` tables -- removing all historical specs that mention retired paths - -## Compatibility Boundary - -The boundary kept after retirement: - -- `LegacyChatImportService` -- legacy import hook / status tracking -- old `conversations/messages` tables as import-only or export-facing sources -- `SessionPresenter` as a main-internal compatibility/data adapter - -## Historical Preservation Policy - -Retired live code must leave the active tree. - -Historical structure is preserved in `docs/archives/` and related cleanup specs. Files still used by -active runtime are migrated into live modules instead of being kept as dormant source snapshots. - -## Acceptance - -The retirement is done when: - -1. live `src/main` / `src/shared` / `src/renderer` code no longer imports legacy runtime folders -2. renderer no longer has public `agentPresenter` / `sessionPresenter` dependency -3. `ILlmProviderPresenter.startStreamCompletion()` is removed -4. migrated ACP / agent tool helpers compile and tests pass from their new locations -5. retired source/tests no longer exist in the active tree, and their historical shape is preserved in docs -6. active docs describe the current architecture and link historical docs as archive material diff --git a/docs/archives/legacy-agentpresenter-retirement/tasks.md b/docs/archives/legacy-agentpresenter-retirement/tasks.md deleted file mode 100644 index c0570335a..000000000 --- a/docs/archives/legacy-agentpresenter-retirement/tasks.md +++ /dev/null @@ -1,41 +0,0 @@ -# Legacy AgentPresenter Retirement Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Runtime Retirement - -- [x] remove live `AgentPresenter` runtime ownership from `src/main/presenter/index.ts` -- [x] remove public `agentPresenter` / `sessionPresenter` presenter exposure -- [x] remove `ILlmProviderPresenter.startStreamCompletion()` -- [x] stop `SessionPresenter` from reading legacy runtime in-memory session state - -## Migration - -- [x] move ACP helpers to `src/main/presenter/llmProviderPresenter/acp/` -- [x] move agent tools to `src/main/presenter/toolPresenter/agentTools/` -- [x] move retained message formatter helper to `src/main/presenter/sessionPresenter/messageFormatter.ts` -- [x] update imports and tests to the new paths - -## Archive - -- [x] archive retired `src/main/presenter/agentPresenter/` -- [x] archive retired legacy presenter type definitions -- [x] archive retired legacy tests -- [x] add archive README for provenance and intent - -## Docs - -- [x] add retirement spec/plan/tasks -- [x] replace active architecture and flow docs with current runtime descriptions -- [x] link legacy architecture/flows as archive-only docs -- [x] update `agent-cleanup` checkpoint docs to final state - -## Verification - -- [x] `pnpm run format` -- [x] `pnpm run i18n` -- [x] `pnpm run lint` -- [x] `pnpm run typecheck` -- [x] targeted Vitest suites -- [x] `node scripts/agent-cleanup-guard.mjs` diff --git a/docs/archives/legacy-llm-provider-runtime-retirement/plan.md b/docs/archives/legacy-llm-provider-runtime-retirement/plan.md deleted file mode 100644 index 4372ec8d6..000000000 --- a/docs/archives/legacy-llm-provider-runtime-retirement/plan.md +++ /dev/null @@ -1,23 +0,0 @@ -# Legacy Provider Runtime Retirement Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Outcome - -Legacy provider runtime retirement is complete and no rollback path remains in the codebase. - -## Executed Plan - -1. Remove runtime selection and make AI SDK the only request runtime. -2. Collapse provider implementations onto shared AI SDK helpers. -3. Remove legacy MCP tool conversion surface from presenter interfaces. -4. Delete obsolete provider SDK dependencies and refresh lockfiles. -5. Archive the migration history and point readers to the final legacy-code commit. - -## Exit Conditions - -- No remaining source imports of `openai`, `@anthropic-ai/sdk`, `@google/genai`, `together-ai`, or `@aws-sdk/client-bedrock-runtime` -- No `DEEPCHAT_LLM_RUNTIME` or `llmRuntimeMode` references remain -- Main-process provider tests validate AI SDK-only behavior -- Documentation explicitly marks the rollback path as retired diff --git a/docs/archives/legacy-llm-provider-runtime-retirement/spec.md b/docs/archives/legacy-llm-provider-runtime-retirement/spec.md deleted file mode 100644 index 7f4d0f6b5..000000000 --- a/docs/archives/legacy-llm-provider-runtime-retirement/spec.md +++ /dev/null @@ -1,48 +0,0 @@ -# Legacy Provider Runtime Retirement Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Status - -Completed on `2026-04-11`. - -The hidden rollback window is closed. `llmProviderPresenter` now runs on AI SDK only. - -## Goal - -Retire the legacy provider runtime and remove obsolete provider SDK dependencies without changing upper-layer contracts: - -- `BaseLLMProvider` -- `LLMProviderPresenter` -- `LLMCoreStreamEvent` -- existing provider IDs, model configs, conversation history, and `function_call_record` compatibility - -## Scope - -- Remove `DEEPCHAT_LLM_RUNTIME` -- Remove config key `llmRuntimeMode` -- Delete `src/main/presenter/llmProviderPresenter/aiSdk/runtimeMode.ts` -- Remove legacy-only provider branches, stream parsers, and MCP conversion ports -- Keep provider-managed responsibilities that still matter: - - `ollama` local model management - - `@aws-sdk/client-bedrock` model discovery - -## Runtime State After Retirement - -- Single runtime: `ai-sdk` -- No hidden fallback -- No provider-specific MCP conversion APIs exposed from presenters -- Vendor-specific request body customization is handled via AI SDK provider options mapping - -## Historical Anchors - -- AI SDK migration landed in commit `4c8345a7` -- Legacy provider implementation can be inspected at commit `3add4093b46f15072d5ec3a65c8097e23b4907c4` - -## Compatibility Commitments - -- `LLMCoreStreamEvent` names and fields remain unchanged -- Provider IDs and provider settings remain unchanged -- Existing message history and `function_call_record` remain reusable -- Routing providers (`new-api`, `zenmux`) stay as thin delegates over migrated providers diff --git a/docs/archives/legacy-llm-provider-runtime-retirement/tasks.md b/docs/archives/legacy-llm-provider-runtime-retirement/tasks.md deleted file mode 100644 index b9bda380a..000000000 --- a/docs/archives/legacy-llm-provider-runtime-retirement/tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# Legacy Provider Runtime Retirement Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -- [x] Delete `src/main/presenter/llmProviderPresenter/aiSdk/runtimeMode.ts` -- [x] Remove `DEEPCHAT_LLM_RUNTIME` and `llmRuntimeMode` -- [x] Convert provider request paths to AI SDK-only implementations -- [x] Remove provider-specific MCP tool conversion interfaces from presenter ports -- [x] Replace legacy SDK type imports with local neutral types where still needed -- [x] Remove obsolete provider SDK dependencies from `package.json` -- [x] Rewrite provider tests around AI SDK runtime helpers and delegate routing -- [x] Archive legacy runtime history and document the last legacy-code commit -- [x] Run `pnpm install` -- [x] Run `pnpm run format` -- [x] Run `pnpm run i18n` -- [x] Run `pnpm run lint` -- [x] Run `pnpm run typecheck` -- [x] Run targeted provider tests for the migrated AI SDK-only paths -- [ ] Run `pnpm run test:main` diff --git a/docs/archives/legacy-llm-provider-runtime.md b/docs/archives/legacy-llm-provider-runtime.md deleted file mode 100644 index cb806a075..000000000 --- a/docs/archives/legacy-llm-provider-runtime.md +++ /dev/null @@ -1,41 +0,0 @@ -# Legacy LLM Provider Runtime Archive - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Summary - -DeepChat previously maintained two low-level provider runtimes under `llmProviderPresenter`: - -- the original provider-specific SDK implementations -- the newer shared AI SDK runtime - -That rollback window is now closed. The active codebase only keeps the AI SDK runtime. - -## Timeline - -- AI SDK migration landed in commit `4c8345a7` -- Legacy runtime retirement and dependency cleanup landed after the migration stabilized - -## Where To Find The Old Provider Implementation - -Use commit `3add4093b46f15072d5ec3a65c8097e23b4907c4` to inspect the historical provider implementation and legacy runtime code. - -That commit is the canonical source for: - -- legacy provider request code -- legacy stream parsing branches -- provider-specific MCP conversion APIs -- legacy rollback-path wiring - -## Current State - -- no `DEEPCHAT_LLM_RUNTIME` -- no `llmRuntimeMode` -- no legacy provider SDK fallback branches in active providers -- no provider-specific MCP conversion APIs exposed from presenters - -For current implementation details, read: - -- [docs/archives/ai-sdk-runtime/spec.md](../archives/ai-sdk-runtime/spec.md) -- [docs/archives/legacy-llm-provider-runtime-retirement/spec.md](../archives/legacy-llm-provider-runtime-retirement/spec.md) diff --git a/docs/archives/mac-computer-use/README.md b/docs/archives/mac-computer-use/README.md deleted file mode 100644 index 55921bc4f..000000000 --- a/docs/archives/mac-computer-use/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# macOS Computer Use Handoff - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -本文档集描述 DeepChat 集成 macOS Computer Use 的规格、架构和实施计划。目标是让后续 -macOS 开发人员可以在 Mac 机器上继续实现,不需要重新调研 `trycua/cua` 和 -`zats/permiso` 的集成边界。 - -调研日期:2026-04-26。 - -## Current Decisions - -- 从 `vendor/cua-driver/source` 的 DeepChat-owned CUA driver fork 源码集成。 -- 上游基线和 cherry-pick policy 记录在 `vendor/cua-driver/upstream.json`;`trycua/cua` 只作为按需吸收 bug fix、macOS compatibility fix、security fix 的来源。 -- macOS-only;Windows/Linux 不打包、不注册、不展示可用能力。 -- 默认 opt-in;用户在设置中显式开启后才注册并启动 Computer Use。 -- 权限身份使用 DeepChat 自己的 helper: - - Bundle display name: `DeepChat Computer Use` - - Bundle identifier: `com.wefonk.deepchat.computeruse` -- 最终交付仍然是一个 DeepChat Electron app,helper 作为 nested app 打进 - `DeepChat.app`。 -- CUA MCP 入口使用 `cua-driver mcp`。 -- 权限引导参考 `zats/permiso`:打开 System Settings 隐私面板,并在系统设置窗口上方显示 - 非激活 overlay 指引。 - -## Document Map - -- [spec.md](spec.md): 用户目标、范围、验收标准、非目标。 -- [plan.md](plan.md): DeepChat 主进程、renderer、MCP、权限状态的数据流和接口设计。 -- [packaging.md](packaging.md): CUA 源码 vendoring、Swift build、架构区分、codesign、 - notarization、CI 接入。 -- [permissions-ux.md](permissions-ux.md): permiso 风格权限引导、设置页 UI、ASCII UX 草图。 -- [tasks.md](tasks.md): 建议 PR 拆分、依赖顺序、验收点。 -- [references.md](references.md): 外部项目链接、当前版本事实、关键源码依据。 - -## Implementation Rule - -本批文档不实现功能代码。后续实现时应按 SDD 流程保持 spec -> plan -> tasks -> PR 的可追踪性, -并在每个 PR 中更新对应文档。 diff --git a/docs/archives/mac-computer-use/packaging.md b/docs/archives/mac-computer-use/packaging.md deleted file mode 100644 index 3eb452b4e..000000000 --- a/docs/archives/mac-computer-use/packaging.md +++ /dev/null @@ -1,277 +0,0 @@ -# Packaging, Codesign, and CI Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Source Layout - -Recommended source layout: - -```text -vendor/ - cua-driver/ - upstream.json - source/ - Package.swift - Sources/... -runtime/ - computer-use/ - cua-driver/ - current/ - DeepChat Computer Use.app/ -resources/ - skills/ - cua-driver/ - SKILL.md -``` - -The implementation uses a DeepChat-owned vendored source snapshot. `vendor/cua-driver/source` is the -build source of truth, with DeepChat changes committed directly in Swift source. -`vendor/cua-driver/upstream.json` records the upstream base and cherry-pick policy: - -```json -{ - "sourceKind": "deepchat-owned-fork", - "upstreamRepo": "https://github.com/trycua/cua.git", - "upstreamSubdir": "libs/cua-driver", - "tag": "cua-driver-v0.0.15", - "commit": "8a4c51337cfdc91a1818ee2f92ceb427272a6247", - "version": "0.0.15", - "updatedAt": "2026-05-01", - "forkPolicy": "Build from the DeepChat-maintained local source snapshot. Cherry-pick upstream fixes only when they directly improve the bundled DeepChat Computer Use helper.", - "lastCherryPick": { - "sourceTag": "cua-driver-v0.0.15", - "sourceCommit": "8a4c51337cfdc91a1818ee2f92ceb427272a6247", - "appliedAt": "2026-05-01" - } -} -``` - -Build requirements: - -- The source snapshot is present in `vendor/cua-driver/source`. -- The upstream commit/tag, fork policy, and last cherry-pick source are recorded in `upstream.json`. -- DeepChat source changes are reviewed as normal repository diffs. -- Release builds depend on local source and never on upstream binary release assets. -- The bundled plugin skill lives in `plugins/cua/skills/cua-driver`; upstream skill files under the - vendor snapshot are reference material. -- The `cua-driver` skill declares `platforms: [darwin]`; built-in skill installation skips it on - other platforms. -- The `cua-driver` skill declares `metadata.deepchatFeature = computer-use`; SkillPresenter hides - that managed skill while Computer Use is disabled. - -## Build Script - -Add a build script such as: - -```text -node scripts/build-cua-driver.mjs --arch arm64 -node scripts/build-cua-driver.mjs --arch x64 -``` - -Responsibilities: - -- Verify host platform is macOS. -- Verify Xcode/Swift toolchain is available. -- Map `arm64 -> arm64`, `x64 -> x86_64`. -- Build CUA Driver with Swift release configuration: - - `swift build -c release --arch --product cua-driver --package-path vendor/cua-driver/source --scratch-path ` -- Wrap binary into `DeepChat Computer Use.app`. -- Patch `Info.plist`: - - `CFBundleIdentifier = com.wefonk.deepchat.computeruse` - - `CFBundleName = DeepChat Computer Use` - - `CFBundleDisplayName = DeepChat Computer Use` - - `CFBundleExecutable = cua-driver` - - `LSUIElement = true` - - `LSMinimumSystemVersion = 14.0` -- Copy app resources/icons. -- Remove stale staged helper before writing new one. -- Verify binary architecture with `lipo -archs` or `file`. -- Avoid `git clone`, `git fetch`, source copying, and runtime patching during normal builds. -- Keep Windows and Linux packages clean by removing both the Computer Use runtime and the bundled - `cua-driver` skill during `afterPack`. - -## Skill Packaging - -When macOS Computer Use is enabled and the managed MCP server is enabled, DeepChat auto-pins the -bundled `cua-driver` skill into the agent system prompt. When Computer Use is disabled, the managed -skill is hidden from skill listing, skill viewing, and prompt loading. This gives the model the CUA -workflow, snapshot-before-action rules, and visual fallback guidance only while the feature is -active. - -The source of truth for DeepChat skill content is `plugins/cua/skills/cua-driver`. Keep it MCP-only and -tailored to DeepChat's plugin runtime; do not replace it with upstream CLI-oriented skill text. - -## Upstream CUA Patch Requirements - -CUA Driver currently assumes `CuaDriver.app` in some relaunch paths. DeepChat maintains the vendored -source with these local changes: - -- App name and bundle id used by LaunchServices relaunch. -- Any hardcoded `CuaDriver` app lookup. -- First-run permission UI labels. -- Self-update command behavior. -- Telemetry defaults. -- DeepChat-specific permission probe command. -- Non-blocking permission startup. -- Background click dispatch behavior verified for DeepChat integration. - -The target behavior is that all macOS TCC prompts and System Settings rows display -`DeepChat Computer Use`. - -## Upstream Updates - -Move the upstream base only when a developer intentionally cherry-picks a fix that improves the -bundled DeepChat Computer Use helper. - -Update flow: - -1. Read the current base from `vendor/cua-driver/upstream.json`. -2. Fetch the old base and target commit from upstream into a temporary clone. -3. Generate a binary patch from old upstream `libs/cua-driver` to current - `vendor/cua-driver/source`. -4. Build a temporary candidate repository with old upstream as the first commit and new upstream as - the second commit. -5. Apply the DeepChat delta with `git apply --3way --whitespace=nowarn`. -6. On success, copy the merged candidate source into `vendor/cua-driver/source` and update the - upstream base plus `lastCherryPick` in `upstream.json`. -7. On conflict, copy conflicted source files with conflict markers into `vendor/cua-driver/source` - during real update runs, print the conflict list, and leave manual resolution to the reviewer. - -Conflict handling rules: - -- Resolve conflicts in `vendor/cua-driver/source` as normal source conflicts. -- Keep DeepChat identity, permission probe, non-blocking permission startup, telemetry defaults, and - background click dispatch behavior unless upstream adds an equivalent implementation. -- Keep DeepChat's MCP-only skill and policy surface unless the change explicitly updates DeepChat - plugin behavior. -- Run the helper build and package validation after resolving conflicts. - -## Runtime Placement - -DeepChat already copies `runtime/` into: - -```text -DeepChat.app/Contents/Resources/app.asar.unpacked/runtime -``` - -Stage the helper at: - -```text -runtime/computer-use/cua-driver/current/DeepChat Computer Use.app -``` - -The packaged binary path becomes: - -```text -DeepChat.app/Contents/Resources/app.asar.unpacked/runtime/computer-use/cua-driver/current/DeepChat Computer Use.app/Contents/MacOS/cua-driver -``` - -Only the target arch helper should exist in `current/` for a given build artifact. - -## Electron Builder Hooks - -Extend `scripts/afterPack.js` for macOS: - -- Locate staged nested helper inside the app output. -- Sign nested helper before outer app signing completes. -- Use a dedicated helper entitlements plist. -- Fail fast if helper is missing on macOS release builds. -- Skip helper validation on Windows/Linux. -- Exclude `vendor/**` from Electron app files; packaged apps should receive the staged helper from - `runtime/` only. - -Recommended helper entitlements: - -```xml -com.apple.security.automation.apple-events - -``` - -Do not add unrelated entitlements unless a failing macOS validation proves they are required. - -DeepChat outer app entitlements should remain minimal. Add `com.apple.security.automation.apple-events` -to the outer app only if DeepChat itself sends Apple Events outside the helper. - -## Codesign - -Release signing should use the same Developer ID Application identity already used for DeepChat. - -Recommended validation commands in CI: - -```text -codesign --verify --deep --strict --verbose=2 "" -codesign --verify --deep --strict --verbose=2 "" -spctl -a -vvv -t exec "" -``` - -Expected signing order: - -```text -build helper source -stage helper under runtime -electron-builder afterPack signs nested helper -electron-builder signs DeepChat.app -afterSign notarizes DeepChat.app -``` - -## CI Changes - -Current GitHub mac build matrix already separates `x64` and `arm64`. Add helper build before -`pnpm run build` or before `electron-builder`: - -```text -pnpm run installRuntime:mac:${{ matrix.arch }} -pnpm run build:cua-driver:mac:${{ matrix.arch }} -pnpm run build -pnpm exec electron-builder --mac --${{ matrix.arch }} --publish=never -``` - -Local package scripts should mirror CI: - -```text -build:mac:arm64 -> build helper arm64 -> build renderer/main -> electron-builder --mac --arm64 -build:mac:x64 -> build helper x64 -> build renderer/main -> electron-builder --mac --x64 -``` - -## Architecture Validation - -For arm64: - -```text -lipo -archs ".../DeepChat Computer Use.app/Contents/MacOS/cua-driver" -# expected: arm64 -``` - -For x64: - -```text -lipo -archs ".../DeepChat Computer Use.app/Contents/MacOS/cua-driver" -# expected: x86_64 -``` - -Fail the build if: - -- The helper is missing. -- The helper contains the wrong architecture. -- The helper is unsigned in release mode. -- Codesign verification fails. -- Notarization fails. - -## Local Development - -On non-macOS machines: - -- Build script should print a clear unsupported message and exit without modifying runtime artifacts. -- Unit tests for path resolution can run with platform mocks. -- Full helper build, TCC permission flow, and codesign validation require macOS. - -On macOS development machines: - -- Allow ad-hoc signing for local debug builds. -- Keep release signing strict. -- Document how to reset TCC grants during manual testing: - - `tccutil reset Accessibility com.wefonk.deepchat.computeruse` - - `tccutil reset ScreenCapture com.wefonk.deepchat.computeruse` - -Use the reset commands only in manual developer docs, never inside app code. diff --git a/docs/archives/mac-computer-use/permissions-ux.md b/docs/archives/mac-computer-use/permissions-ux.md deleted file mode 100644 index 00c33ebf7..000000000 --- a/docs/archives/mac-computer-use/permissions-ux.md +++ /dev/null @@ -1,155 +0,0 @@ -# Permissions and UX Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Permission Model - -Computer Use needs two macOS TCC grants: - -- Accessibility: required for input control and accessibility tree access. -- Screen Recording: required for screenshots, screen/window inspection, and ScreenCaptureKit probes. - -The app must not imply these permissions can be bypassed. Missing permissions should be treated as normal -setup state, not as a generic error. - -## Permission Identity - -System Settings must show: - -```text -DeepChat Computer Use -``` - -This requires the helper bundle identity to be stable: - -```text -CFBundleIdentifier: com.wefonk.deepchat.computeruse -CFBundleDisplayName: DeepChat Computer Use -``` - -Do not keep upstream `com.trycua.driver` for the integrated helper, because users would see `CuaDriver` -instead of DeepChat in System Settings. - -## Permission Guide Behavior - -Use the permiso interaction model: - -- Open the target System Settings privacy pane via `x-apple.systempreferences`. -- Locate the visible System Settings window. -- Display a passive, non-activating overlay near the relevant settings content. -- Poll permission status until granted. -- Advance from Accessibility to Screen Recording automatically when possible. -- Close the overlay after both grants are detected. - -Recommended panels: - -```text -Accessibility: -x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Accessibility - -Screen Recording: -x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ScreenCapture -``` - -## Settings Card - -Initial macOS disabled state: - -```text -Settings > MCP -+--------------------------------------------------+ -| Computer Use (macOS) [Off] | -| Let DeepChat operate local apps after approval. | -| Helper: Not running | -| Accessibility: Not requested | -| Screen Recording: Not requested | -| | -| [Open Permission Guide] [Check Again] | -+--------------------------------------------------+ -``` - -Enabled but missing permissions: - -```text -Settings > MCP -+--------------------------------------------------+ -| Computer Use (macOS) [On] | -| Helper: Ready | -| Accessibility: Missing | -| Screen Recording: Granted | -| MCP: Waiting for permissions | -| | -| [Open Permission Guide] [Check Again] | -+--------------------------------------------------+ -``` - -Enabled and ready: - -```text -Settings > MCP -+--------------------------------------------------+ -| Computer Use (macOS) [On] | -| Helper: Ready | -| Accessibility: Granted | -| Screen Recording: Granted | -| MCP: Running | -| | -| [Restart Service] [Check Again] | -+--------------------------------------------------+ -``` - -Unsupported platform: - -```text -Settings > MCP -+--------------------------------------------------+ -| Computer Use | -| Available on macOS only. | -+--------------------------------------------------+ -``` - -## Permission Overlay Sketch - -```text -System Settings > Privacy & Security > Accessibility -+--------------------------------------------------------------+ -| Accessibility | -| | -| [ ] DeepChat Computer Use | -| | -| +---------------------------------------------+ | -| | Enable DeepChat Computer Use in this list. | | -| | DeepChat will continue when permission is on.| | -| +---------------------------------------------+ | -+--------------------------------------------------------------+ -``` - -For Screen Recording, use the same shape and text adapted to screen capture. - -## Copy Guidelines - -User-facing copy should be short and direct: - -- "Computer Use lets DeepChat inspect and operate local apps after you approve tool calls." -- "Accessibility is required for clicks, typing, shortcuts, and accessibility tree access." -- "Screen Recording is required for screenshots and screen state." -- "DeepChat cannot use Computer Use until both permissions are granted." - -Avoid claiming the feature is always safe. Instead, make the permission boundaries visible. - -## Interaction Rules - -- Opening the guide should not auto-enable action tools. -- Toggling Computer Use off should stop/restart the MCP server so tools disappear from active sessions. -- If global MCP is disabled, the card should say Computer Use is configured but MCP is off. -- If the helper exits because permissions are missing, show the permission guide action rather than a raw stderr block. -- If System Settings cannot be found for overlay positioning, fall back to opening the pane and showing an in-app checklist. - -## Accessibility of the UI - -- Status labels must not rely only on color. -- Buttons must have clear labels and keyboard focus states. -- The overlay should be passive and should not intercept System Settings interaction. -- Renderer strings must use i18n keys. - diff --git a/docs/archives/mac-computer-use/plan.md b/docs/archives/mac-computer-use/plan.md deleted file mode 100644 index 36cadd9cb..000000000 --- a/docs/archives/mac-computer-use/plan.md +++ /dev/null @@ -1,231 +0,0 @@ -# macOS Computer Use Implementation Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Architecture Overview - -DeepChat 通过一个 macOS nested helper app 承载 CUA Driver。Electron main process 负责: - -- 发现 helper 路径和架构。 -- 查询 helper 权限状态。 -- 维护 Computer Use enable 状态。 -- 在启用时注册/更新内置 stdio MCP server。 -- 把 renderer 设置页操作映射到 typed route。 - -Renderer 只展示状态和触发操作,不直接拼接 helper 路径或执行系统命令。 - -```text ---------------------+ typed route +--------------------------+ -| Renderer Settings | -------------------------> | ComputerUsePresenter | -+--------------------+ +-----------+--------------+ - | - | helper path/status - v - +--------------------------+ - | DeepChat Computer Use.app | - | Contents/MacOS/cua-driver | - +-------------+------------+ - | - stdio MCP: cua-driver mcp | - v - +--------------------------+ - | McpPresenter/McpClient | - +--------------------------+ -``` - -## Main Process Components - -### ComputerUsePresenter - -Add a mac-only presenter under `src/main/presenter/computerUsePresenter`. - -Responsibilities: - -- Return unsupported status on non-macOS. -- Resolve packaged helper path: - - dev: `runtime/computer-use/cua-driver/current/DeepChat Computer Use.app` - - packaged: `app.asar.unpacked/runtime/computer-use/cua-driver/current/DeepChat Computer Use.app` -- Resolve binary path: - - `DeepChat Computer Use.app/Contents/MacOS/cua-driver` -- Read persisted enable state from config. -- Query permissions by invoking a helper status/check command or a small DeepChat wrapper command. -- Register or unregister `deepchat/computer-use` MCP server when enabled state changes. -- Restart MCP server after permission changes or helper updates. - -Do not let renderer provide arbitrary executable paths. - -### Typed Routes - -Add a new typed route group, for example `computerUse.routes.ts`. - -Recommended methods: - -- `computerUse.getStatus(): Promise` -- `computerUse.setEnabled(enabled: boolean): Promise` -- `computerUse.openPermissionGuide(target?: ComputerUsePermissionTarget): Promise` -- `computerUse.checkPermissions(): Promise` -- `computerUse.restartMcpServer(): Promise` - -Recommended shared types: - -```typescript -type ComputerUsePlatform = 'darwin' | 'unsupported' -type ComputerUsePermissionName = 'accessibility' | 'screenRecording' -type ComputerUsePermissionState = 'granted' | 'missing' | 'unknown' -type ComputerUseMcpState = 'notRegistered' | 'registered' | 'running' | 'error' - -interface ComputerUseStatus { - platform: ComputerUsePlatform - available: boolean - enabled: boolean - arch: 'arm64' | 'x64' | 'unknown' - helperPath?: string - helperVersion?: string - permissions: Record - mcpServer: ComputerUseMcpState - lastError?: string -} -``` - -The exact file names can follow the current route/client naming conventions. - -## MCP Integration - -Add a built-in server only on macOS and only when Computer Use is enabled: - -```json -{ - "deepchat/computer-use": { - "type": "stdio", - "command": "", - "args": ["mcp"], - "env": { - "DEEPCHAT_COMPUTER_USE": "1", - "CUA_DRIVER_AUTO_UPDATE": "0", - "CUA_DRIVER_TELEMETRY": "0" - }, - "descriptions": "DeepChat built-in macOS computer use service", - "icons": "computer-use", - "autoApprove": [], - "enabled": true, - "source": "deepchat", - "sourceId": "computer-use" - } -} -``` - -The existing MCP config model does not distinguish built-in stdio servers from user-added stdio servers. -Implementation should add a stable built-in marker using `source: 'deepchat'` and `sourceId`, then update -renderer grouping logic if needed. - -## Tool Permission Policy - -CUA tools need explicit classification instead of relying only on generic name heuristics. - -Recommended read/status tools: - -- `check_permissions` -- `list_apps` -- `list_windows` -- `get_screen_size` -- `get_window_state` -- `get_accessibility_tree` -- `get_cursor_position` -- `screenshot` - -Recommended action/write tools: - -- `launch_app` -- `click` -- `right_click` -- `scroll` -- `type_text` -- `type_text_chars` -- `press_key` -- `hotkey` -- `set_value` -- `set_agent_cursor_enabled` -- `set_agent_cursor_motion` -- `set_agent_cursor_style` -- recording/config mutation tools -- `zoom` -- `page` - -Default server `autoApprove` stays empty. If the user selects DeepChat `full_access`, DeepChat can skip -its own per-tool prompts, but macOS Accessibility and Screen Recording grants are still required. - -Rejected design: do not add a public `set_electron_accessibility` tool. The driver already attempts -Electron/Chromium AX enablement inside `get_window_state` by setting `AXManualAccessibility`, -`AXEnhancedUserInterface`, registering an AXObserver, and pumping the run loop before walking the tree. -If Electron still returns a sparse AX tree, the agent should use `page`, `electron_debugging_port`, -`screenshot(window_id)`, and window-local pixel actions. - -## State Flow - -Enable flow: - -```text -User toggles Computer Use on - -> renderer calls computerUse.setEnabled(true) - -> presenter resolves helper and checks platform - -> presenter persists enabled=true - -> presenter ensures MCP server config - -> presenter starts/restarts MCP server if global MCP is enabled - -> presenter returns current status -``` - -Permission flow: - -```text -User clicks Open Permission Guide - -> renderer calls computerUse.openPermissionGuide('all') - -> presenter launches helper permission UI - -> helper opens System Settings pane - -> helper shows permiso-style overlay - -> presenter/renderer polls checkPermissions - -> UI updates to granted/missing -``` - -MCP startup flow: - -```text -McpClient spawns packaged cua-driver with args ['mcp'] - -> CUA checks TCC permissions - -> if missing, process exits with clear stderr - -> presenter maps failure into ComputerUseStatus.lastError - -> UI offers permission guide -``` - -## Renderer UI Placement - -Preferred placement: existing MCP settings area, because Computer Use is implemented as an MCP server and -already depends on MCP enablement. If product wants stronger discoverability, add a dedicated -`Settings > Computer Use` subsection later; do not start with two separate settings pages. - -Renderer should: - -- Show card only on macOS, or show a disabled unsupported message on other platforms. -- Use i18n keys for all user-facing strings. -- Never expose command/path editing for this built-in server. -- Link to MCP state if global MCP is disabled. - -## Error Handling - -Surface these states distinctly: - -- `unsupported`: platform is not macOS. -- `missingHelper`: helper app is not packaged or cannot be found. -- `archMismatch`: packaged binary does not match expected arch. -- `missingPermissions`: one or more TCC grants missing. -- `mcpStartFailed`: helper exists but `cua-driver mcp` exits or fails handshake. -- `codesignInvalid`: dev/release build validation found an invalid helper signature. - -## Security Notes - -- Do not auto-enable Computer Use. -- Do not auto-approve action tools by default. -- Do not allow model-generated input to change helper command path. -- Do not bypass macOS TCC or store private permission workarounds. -- Disable helper self-update. Updates should come from DeepChat release packages. -- Keep permission identity stable across releases so TCC grants persist. diff --git a/docs/archives/mac-computer-use/references.md b/docs/archives/mac-computer-use/references.md deleted file mode 100644 index b964fa5a6..000000000 --- a/docs/archives/mac-computer-use/references.md +++ /dev/null @@ -1,91 +0,0 @@ -# External References and Research Notes - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Research date: 2026-05-01. - -## CUA - -- Repository: https://github.com/trycua/cua -- CUA Driver docs: https://cua.ai/docs/cua-driver/guide/getting-started/introduction -- Latest GitHub release observed: https://github.com/trycua/cua/releases/tag/cua-driver-v0.0.15 - -Observed release facts: - -- Latest release tag: `cua-driver-v0.0.15` -- Published: 2026-04-30 -- Assets observed: - - `cua-driver-0.0.15-darwin-arm64.pkg.tar.gz` - - `cua-driver-0.0.15-darwin-arm64.tar.gz` - - generic darwin aliases -- No x64 release asset was observed, so DeepChat must build from source for x64 support. - -Key source facts from `libs/cua-driver`: - -- `Package.swift` declares macOS 14 minimum. -- Products include: - - executable `cua-driver` - - library `CuaDriverCore` - - library `CuaDriverServer` -- `cua-driver mcp` runs the stdio MCP server. -- `cua-driver serve` runs a long-lived daemon over a Unix socket. -- `cua-driver mcp-config` prints an MCP config that uses command `` and args `['mcp']`. -- Permissions use: - - `AXIsProcessTrusted()` - - `AXIsProcessTrustedWithOptions(...)` - - `CGRequestScreenCaptureAccess()` - - `SCShareableContent.current` -- Upstream helper entitlement includes `com.apple.security.automation.apple-events`. -- Upstream app wrapper uses a stable bundle id because TCC grants are tied to app identity. - -Integration implication: - -- DeepChat should use `cua-driver mcp` for MCP. -- DeepChat should maintain a local DeepChat-owned driver fork with DeepChat identity and ship the branded - `DeepChat Computer Use.app`. -- DeepChat should build helper source for both `arm64` and `x86_64`. -- DeepChat should disable helper self-update so app updates remain controlled by DeepChat releases. -- DeepChat should cherry-pick upstream changes only when they directly improve bundled helper - reliability, macOS compatibility, or security. - -## Permiso - -- Repository: https://github.com/zats/permiso - -Key source facts: - -- `PermisoPanel` uses System Settings URLs: - - `Privacy_Accessibility` - - `Privacy_ScreenCapture` - - `x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?` -- `SettingsWindowLocator` finds System Settings with bundle id `com.apple.systempreferences`. -- Window location uses `CGWindowListCopyWindowInfo`. -- `OverlayWindowController` creates a passive `NSPanel` with `.nonactivatingPanel`. -- Overlay window level uses `.statusBar`. - -Integration implication: - -- DeepChat can copy the UX pattern without copying unrelated app structure. -- Permission guide should open System Settings, locate its window, and show a passive overlay. -- If overlay placement fails, fallback to regular in-app checklist. - -## DeepChat Existing Architecture - -Relevant current facts: - -- App id: `com.wefonk.deepchat`. -- mac artifact naming already includes `${arch}`. -- `electron-builder.yml` already copies `runtime/` to `app.asar.unpacked/runtime`. -- mac builds already run separate `x64` and `arm64` matrix jobs. -- `afterPack` is `scripts/afterPack.js`. -- `afterSign` is `scripts/notarize.js`. -- Existing MCP config supports `stdio`, `sse`, `http`, and `inmemory`. -- Built-in mac server `deepchat/apple-server` already exists as an `inmemory` platform-specific server. -- New renderer-main capabilities should use typed route/client instead of new direct legacy presenter calls. - -Integration implication: - -- Runtime placement can reuse existing `runtime/` packaging behavior. -- Signing should be added to the existing `afterPack` mac branch. -- Computer Use should be represented as a DeepChat-owned built-in MCP server, not a user-editable custom server. diff --git a/docs/archives/mac-computer-use/spec.md b/docs/archives/mac-computer-use/spec.md deleted file mode 100644 index 9b110e86f..000000000 --- a/docs/archives/mac-computer-use/spec.md +++ /dev/null @@ -1,89 +0,0 @@ -# macOS Computer Use Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Summary - -DeepChat 需要在 macOS 上提供 Computer Use 能力,让模型可以通过内置工具读取屏幕状态、 -枚举窗口、截屏,并在用户授权后执行点击、输入、快捷键等本机操作。该能力仅在 macOS 有效, -并通过 DeepChat 内置的 `DeepChat Computer Use.app` helper 实现。 - -用户不需要单独安装 CUA Driver。DeepChat macOS 安装包需要包含匹配当前架构的 helper: -`arm64` 构建包含 arm64 helper,`x64` 构建包含 x86_64 helper。 - -## Goals - -- 在 macOS 上内置 source-built CUA Driver,并通过 MCP 接入 DeepChat agent tool flow。 -- 让用户明确开启 Computer Use 后才启用该能力。 -- 用 DeepChat 品牌身份请求 macOS 权限,避免系统设置中出现不明来源的 `CuaDriver`。 -- 提供清晰的 Accessibility 和 Screen Recording 权限引导。 -- 保持 Windows/Linux 行为不变。 -- 保持最终交付物为一个 DeepChat app,不要求用户安装额外 app 或命令行工具。 - -## User Stories - -- 作为 macOS 用户,我可以在设置中看到 Computer Use 入口,并理解它需要本机控制权限。 -- 作为 macOS 用户,我可以一键打开权限引导,按步骤授予 Accessibility 和 Screen Recording。 -- 作为 macOS 用户,我开启 Computer Use 后,agent 可以使用屏幕读取和本机操作工具。 -- 作为隐私敏感用户,我不启用 Computer Use 时,DeepChat 不启动 helper、不注册相关 MCP 工具。 -- 作为 Windows/Linux 用户,我不会看到可误用的 Computer Use 启用入口。 -- 作为发布工程师,我可以分别构建 macOS x64 和 arm64 包,并确认 helper 架构正确。 - -## Functional Requirements - -- Settings: - - macOS 显示 Computer Use 设置卡片。 - - 默认状态为 disabled。 - - 卡片展示 helper 状态、MCP 状态、Accessibility 权限、Screen Recording 权限。 - - 卡片提供 enable/disable、open permission guide、check again 操作。 -- Permissions: - - 权限身份必须是 `DeepChat Computer Use`。 - - 缺少任一必需权限时,MCP server 不应静默失败;UI 应展示明确状态。 - - 权限引导必须覆盖 Accessibility 和 Screen Recording。 -- MCP: - - DeepChat 内置 MCP server key 使用 `deepchat/computer-use`。 - - server type 使用 `stdio`。 - - command 指向 packaged helper binary。 - - args 使用 `['mcp']`。 - - `autoApprove` 默认空数组。 - - CUA action tools 需要走 DeepChat 现有 permission flow。 -- Packaging: - - macOS build 必须从 vendored source 构建 helper。 - - macOS release artifact 只包含目标架构 helper。 - - helper 必须被签名并随 DeepChat app 一起 notarized。 - -## Non-Goals - -- 不支持 Windows/Linux Computer Use。 -- 不提供独立的 CUA Driver 安装器。 -- 不允许通过下载上游 release binary 完成正式构建。 -- 不在本阶段实现 remote desktop、远程控制或多人协作。 -- 不绕过 macOS TCC;用户必须在系统设置中授予权限。 - -## Acceptance Criteria - -- On macOS: - - Fresh install 后 Computer Use disabled,helper 未启动,MCP 工具未注册。 - - 用户启用后,如果权限缺失,状态显示 missing,并能打开权限引导。 - - 用户授予 Accessibility 和 Screen Recording 后,状态变为 granted。 - - `deepchat/computer-use` server 可以启动,工具列表包含 CUA MCP tools。 - - `check_permissions`、`list_windows`、`screenshot` 可用。 - - action tool 如 `click`、`type_text` 在默认权限模式下触发 DeepChat permission prompt。 -- On non-macOS: - - 不打包 helper。 - - 不注册 `deepchat/computer-use`。 - - 设置页不显示可启用的 Computer Use 卡片,或只显示 unavailable 状态。 -- Packaging: - - mac arm64 artifact 内 helper binary 为 arm64。 - - mac x64 artifact 内 helper binary 为 x86_64。 - - `codesign --verify --deep --strict` 对 helper 和外层 `DeepChat.app` 通过。 - - release notarization 仍通过。 - -## Constraints - -- DeepChat app id 当前为 `com.wefonk.deepchat`。 -- Helper bundle id 固定为 `com.wefonk.deepchat.computeruse`,避免 TCC grant 随版本变化丢失。 -- CUA Driver 当前 `Package.swift` 声明最低 macOS 14;本功能最低 macOS 14。 -- 新 renderer-main 能力应使用 typed route/client,不新增 `useLegacyPresenter()` 调用。 - diff --git a/docs/archives/mac-computer-use/tasks.md b/docs/archives/mac-computer-use/tasks.md deleted file mode 100644 index f68d92833..000000000 --- a/docs/archives/mac-computer-use/tasks.md +++ /dev/null @@ -1,117 +0,0 @@ -# Implementation Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -This task list is intended for macOS developers taking over the implementation. Prefer small PRs in this -order so packaging, permissions, and UI can be reviewed independently. - -## Phase 1: Source and Build Scaffold - -- Add vendored CUA Driver source under `vendor/cua-driver/source`, pinned to `cua-driver-v0.0.13` - in `vendor/cua-driver/upstream.json`. -- Commit DeepChat helper bundle identity, app name, relaunch behavior, self-update disablement, - permission probe, non-blocking permission startup, background click dispatch, and telemetry default - changes directly in vendored Swift source. -- Add upstream update script that exports the DeepChat delta and applies it to a requested upstream - tag/commit with `git apply --3way`. -- Add mac-only helper build script with `--arch arm64|x64`. -- Stage helper into `runtime/computer-use/cua-driver/current/DeepChat Computer Use.app`. -- Add architecture validation for staged helper. - -Done when: - -- A macOS developer can build both helper architectures from source. -- The staged helper has correct app name, bundle id, and binary architecture. - -## Phase 2: Packaging and Signing - -- Update mac package scripts to build the helper before Electron packaging. -- Update GitHub mac build matrix to call the helper build script. -- Extend `scripts/afterPack.js` to sign and validate the nested helper on macOS. -- Add helper entitlements plist. -- Add CI validation for helper presence, architecture, codesign, and outer app codesign. - -Done when: - -- mac arm64 artifact contains only arm64 helper. -- mac x64 artifact contains only x86_64 helper. -- Release signing and notarization still pass. - -## Phase 3: Main Process Integration - -- Add `ComputerUsePresenter`. -- Add typed routes and renderer API client. -- Add shared status and permission types. -- Implement helper path resolution for dev and packaged modes. -- Implement mac-only enable state persistence. -- Implement permission status checks. -- Register/unregister `deepchat/computer-use` MCP server when enabled changes. - -Done when: - -- Unit tests cover platform gating, path resolution, missing helper, enable persistence, and MCP config. -- Non-mac platforms return unsupported and never register the server. - -## Phase 4: MCP Permission Hardening - -- Add CUA-specific tool classification for read/status vs action/write tools. -- Ensure `autoApprove` defaults to empty for `deepchat/computer-use`. -- Ensure built-in stdio server cannot be edited into an arbitrary command by renderer UI. -- Surface MCP startup failures as Computer Use status errors. - -Done when: - -- Default permission mode prompts before action tools. -- `full_access` behavior is consistent with existing DeepChat agent permission semantics. -- Missing macOS TCC grants are surfaced as setup state. - -## Phase 5: Permission Guide - -- Implement or integrate a small Swift permission guide layer based on permiso concepts. -- Open Accessibility and Screen Recording panes with `x-apple.systempreferences` URLs. -- Locate System Settings window and show a passive overlay. -- Poll permission status and auto-advance between required grants. -- Add fallback in-app checklist when overlay placement is unavailable. - -Done when: - -- Fresh macOS install can complete both grants through the guide. -- System Settings shows `DeepChat Computer Use`. -- Closing the guide leaves clear status in DeepChat. - -## Phase 6: Renderer Settings UI - -- Add Computer Use card to MCP settings on macOS. -- Add unsupported state for non-mac if the settings surface needs it. -- Add i18n keys for all user-facing strings. -- Wire enable/disable, open guide, check again, and restart actions. -- Hide raw executable path from normal UI. - -Done when: - -- UI matches the states in `permissions-ux.md`. -- Renderer tests cover disabled, missing permissions, ready, MCP disabled, and unsupported states. - -## Phase 7: End-to-End Validation - -- Manual test on macOS arm64. -- Manual test on macOS x64 or Rosetta-backed Intel environment. -- Validate: - - enable Computer Use - - grant permissions - - run `check_permissions` - - run `list_windows` - - run `screenshot` - - run one controlled action tool with a prompt -- Run repo quality gates: - - `pnpm run format` - - `pnpm run i18n` - - `pnpm run lint` - - targeted Vitest suites - - mac package build - -Done when: - -- All acceptance criteria in `spec.md` pass. -- Known macOS limitations are documented in release notes or user docs. diff --git a/docs/archives/mac-computer-use/test-plan.md b/docs/archives/mac-computer-use/test-plan.md deleted file mode 100644 index 39fedd6d7..000000000 --- a/docs/archives/mac-computer-use/test-plan.md +++ /dev/null @@ -1,106 +0,0 @@ -# Test Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Unit Tests - -Main process: - -- `ComputerUsePresenter` returns unsupported on non-macOS. -- Helper path resolution maps dev and packaged app paths correctly. -- Architecture detection maps `process.arch` to expected helper architecture. -- Missing helper produces `available=false` and a specific error. -- Enable/disable persists state and updates MCP server config. -- Permission parser maps helper output to `granted`, `missing`, or `unknown`. -- MCP server config uses `command=` and `args=['mcp']`. -- Built-in stdio server is marked as DeepChat-owned and not treated as a user custom server. - -Tool permissions: - -- CUA read/status tools are classified as read. -- CUA action tools are classified as write/action. -- Default `autoApprove=[]` does not silently approve action tools. -- Existing non-CUA MCP permission behavior is unchanged. - -Packaging script tests: - -- Build script reads `vendor/cua-driver/source` and does not run upstream clone/fetch during builds. -- `vendor/cua-driver/upstream.json` missing required fields fails with a clear error. -- Update script `--dry-run` generates the DeepChat delta and applies it to a local upstream test repo. -- Update script `--dry-run` reports conflict files when upstream and DeepChat edit the same source. -- `--arch arm64` maps to Swift arch `arm64`. -- `--arch x64` maps to Swift arch `x86_64`. -- Non-macOS build exits with clear unsupported message. -- Wrong or missing architecture validation fails. - -## Renderer Tests - -- macOS disabled card renders correct status and actions. -- Enabled with missing permissions renders permission guide action. -- Ready state renders granted permissions and running MCP status. -- Global MCP disabled state explains that Computer Use is configured but inactive. -- Non-macOS state does not offer an enable action. -- User actions call the typed client, not legacy presenter APIs. -- i18n keys exist for all Computer Use strings. - -## Package Validation - -For each macOS artifact: - -- Confirm helper exists in: - - `DeepChat.app/Contents/Resources/app.asar.unpacked/runtime/computer-use/cua-driver/current/DeepChat Computer Use.app` -- Confirm architecture: - - arm64 build: helper binary reports `arm64` - - x64 build: helper binary reports `x86_64` -- Confirm signing: - - helper passes `codesign --verify --deep --strict` - - outer app passes `codesign --verify --deep --strict` - - outer app passes `spctl -a -vvv -t exec` -- Confirm notarization completes in release flow. - -## Manual macOS Acceptance - -Fresh install: - -- Install DeepChat to `/Applications`. -- Open Settings > MCP. -- Confirm Computer Use is off. -- Confirm no `deepchat/computer-use` tools are active. - -Permission setup: - -- Turn Computer Use on. -- Open permission guide. -- Grant Accessibility for `DeepChat Computer Use`. -- Grant Screen Recording for `DeepChat Computer Use`. -- Restart app if macOS requires it. -- Confirm status becomes granted for both permissions. - -Tool behavior: - -- Run an agent task that calls `check_permissions`. -- Run an agent task that calls `list_windows`. -- Run an agent task that calls `screenshot`. -- Run a controlled action such as clicking a known harmless UI target. -- In default permission mode, confirm action tool prompt appears before execution. - -Disable behavior: - -- Turn Computer Use off. -- Confirm MCP server stops or is unregistered. -- Confirm active tool list no longer includes CUA tools. - -TCC reset regression: - -- Reset Accessibility and ScreenCapture grants manually with `tccutil`. -- Relaunch DeepChat. -- Confirm UI returns to missing permission state. - -## Risks to Watch During Testing - -- macOS TCC may attribute permissions to the parent Electron app if helper is launched incorrectly. -- Upstream CUA relaunch logic may still search for `CuaDriver.app` unless patched. -- Screen Recording permission may require app restart on some macOS versions. -- Intel/x64 build may fail if Swift package or dependencies assume arm64-only paths. -- Nested helper signing order can break notarization if helper is modified after signing. diff --git a/docs/archives/multi-window-cleanup/plan.md b/docs/archives/multi-window-cleanup/plan.md deleted file mode 100644 index b710b7356..000000000 --- a/docs/archives/multi-window-cleanup/plan.md +++ /dev/null @@ -1,10 +0,0 @@ -# Multi-Window Cleanup Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -1. Rename renderer entry from `shell` to `browser` and delete tooltip overlay. -2. Convert YoBrowser from single-window multi-tab to multi-window single-page. -3. Remove tab shortcuts and tab UI, keep only multi-window behavior. -4. Short-circuit tab-dependent legacy MCP helpers. -5. Run format, i18n, lint, typecheck, targeted tests, and build. diff --git a/docs/archives/multi-window-cleanup/spec.md b/docs/archives/multi-window-cleanup/spec.md deleted file mode 100644 index ae0d5aa48..000000000 --- a/docs/archives/multi-window-cleanup/spec.md +++ /dev/null @@ -1,51 +0,0 @@ -# Multi-Window Cleanup Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Goal - -Remove the last shell and multi-tab browser architecture remnants and converge on a clean -multi-window model: - -- app windows render the chat UI directly -- browser windows render dedicated browser chrome directly -- each browser window owns exactly one page `WebContentsView` -- tooltip overlay is removed completely - -## Decisions - -### Window model - -- `WindowPresenter` exposes explicit app-window and browser-window creation APIs. -- `createShellWindow` remains only as a deprecated compatibility wrapper. -- Browser windows load `src/renderer/browser/index.html`. -- Chat windows load `src/renderer/index.html#/chat`. - -### YoBrowser model - -- `YoBrowserPresenter` manages multiple browser windows. -- Each browser window has one browser chrome renderer and one page view. -- There is no tab list, no tab activation, no tab reordering, and no tab shortcuts. -- Browser APIs use `windowId` for addressing. - -### Renderer model - -- The old `src/renderer/shell` entry is renamed to `src/renderer/browser`. -- Browser chrome keeps only: - - window controls - - address bar - - navigation controls - - create-new-browser-window action -- Tooltip overlay entry/runtime is deleted. - -### Legacy handling - -- `conversationSearchServer` and `meetingServer` are intentionally disabled until they are rebuilt - against the window-native architecture. -- Deprecated browser tool names remain as thin aliases only in the handler. - -## Non-Goals - -- Rebuilding every legacy session/thread abstraction in this pass. -- Introducing new UI entities beyond the required browser-window state. diff --git a/docs/archives/multi-window-cleanup/tasks.md b/docs/archives/multi-window-cleanup/tasks.md deleted file mode 100644 index 610663895..000000000 --- a/docs/archives/multi-window-cleanup/tasks.md +++ /dev/null @@ -1,14 +0,0 @@ -# Multi-Window Cleanup Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -- [x] Move renderer entry from `shell` to `browser` -- [x] Delete tooltip overlay entry from renderer tree -- [ ] Update build aliases and inputs -- [ ] Simplify browser renderer chrome -- [ ] Rewrite YoBrowser window model -- [ ] Split app-window and browser-window creation -- [ ] Remove tab shortcuts from main/settings -- [ ] Disable legacy MCP tab helpers -- [ ] Verify format, i18n, lint, typecheck, tests, build diff --git a/docs/archives/new-agent/plan.md b/docs/archives/new-agent/plan.md deleted file mode 100644 index d1d3b36f4..000000000 --- a/docs/archives/new-agent/plan.md +++ /dev/null @@ -1,414 +0,0 @@ -# New Agent Architecture v0 — Implementation Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. Current Implementation Baseline - -### 1.1 LLM Provider (reuse as-is) - -- `src/main/presenter/llmProviderPresenter/baseProvider.ts` — `BaseLLMProvider` with `coreStream()` returning `AsyncGenerator` -- `src/shared/types/core/llm-events.ts` — `LLMCoreStreamEvent` discriminated union (text, reasoning, tool_call_start, error, usage, stop, etc.) -- Provider instances managed by `LLMProviderPresenter.getProviderInstance(providerId)` - -### 1.2 Event System (extend, not replace) - -- `src/main/eventbus.ts` — `EventBus` singleton with `sendToRenderer(event, target, payload)` -- `src/main/events.ts` — existing event constants (CONVERSATION_EVENTS, STREAM_EVENTS) -- `src/renderer/src/events.ts` — renderer-side event constant mirrors - -### 1.3 Presenter Registration - -- `src/main/presenter/index.ts` — singleton `Presenter` class, IPC handler at line ~358 does `presenter[name as keyof Presenter]` -- `src/shared/types/presenters/legacy.presenters.d.ts` — `IPresenter` interface -- `src/renderer/api/legacy/presenters.ts` — proxy-based IPC caller - -### 1.4 SQLite - -- `src/main/presenter/sqlitePresenter/index.ts` — shared SQLite instance -- `src/main/presenter/sqlitePresenter/tables/` — table definitions with migration support -- Migration pattern: each table file exports `createTable()` + `migrations` array - -### 1.5 Config - -- `src/main/presenter/configPresenter/index.ts` — `getSetting()`, `getEnabledProviders()`, model defaults -- Default provider/model resolved via `configPresenter.getSetting('DEFAULT_PROVIDER_ID')` and `configPresenter.getSetting('DEFAULT_MODEL_ID')` - -## 2. Design Decisions - -### 2.1 Naming: `agentSessionPresenter` not `agentPresenter` - -The old `agentPresenter` still exists and must keep working. The new one is registered as `agentSessionPresenter` in IPresenter to avoid name collision. Renderer calls `useLegacyPresenter('agentSessionPresenter')`. When old UI is removed, rename to `agentPresenter`. - -### 2.2 Database: new tables in same chat.db - -No separate DB file. New tables coexist with old tables in `chat.db`: -- `new_sessions` — agentPresenter's thin session registry -- `new_projects` — project directory history -- `deepchat_sessions` — agentRuntimePresenter's session config -- `deepchat_messages` — agentRuntimePresenter's messages - -Prefix `new_` on `sessions` and `projects` to avoid any conflict with potential future SQLite reserved words or collisions. - -### 2.3 Stream handling: reuse LLMCoreStreamEvent, transform to LLMAgentEventData - -agentRuntimePresenter consumes `LLMCoreStreamEvent` from `coreStream()` and: -1. Accumulates content into `AssistantMessageBlock[]` structure -2. Persists structured JSON content to `deepchat_messages` (batched DB writes every 600ms) -3. Transforms to `LLMAgentEventData` format with `conversationId` + `eventId` for routing -4. Emits via EventBus (batched renderer flush every 120ms) — agentPresenter relays to renderer - -This means the renderer's stream handling code receives the same event format. Same `LLMAgentEventData` structure. - -### 2.4 v0 message content: structured JSON from the start - -Even in v0, messages use structured JSON content — not plain text. This avoids a migration when adding tool calls and thinking in later versions. - -**User messages** are stored as serialized `UserMessageContent`: -``` -{ text: "user input", files: [], links: [], search: false, think: false } -``` - -**Assistant messages** are stored as serialized `AssistantMessageBlock[]`: -``` -[{ type: "content", content: "LLM response text", status: "success", timestamp: 1234567890 }] -``` - -v0 only produces `content` and `reasoning_content` block types. Later versions add `tool_call`, `search`, `error`, etc. - -### 2.5 v0 message assembly: single user message, no context - -v0 sends exactly one message to the LLM: - -``` -messages = [ - { role: 'user', content: input.message } -] -``` - -No system prompt, no conversation history, no tool definitions. Just raw user message → LLM → streamed response. Multi-turn context assembly is v1. - -### 2.6 v0 model resolution - -Use the global default provider/model from configPresenter unless CreateSessionInput specifies overrides: - -1. `input.providerId ?? configPresenter.getSetting('DEFAULT_PROVIDER_ID')` -2. `input.modelId ?? configPresenter.getSetting('DEFAULT_MODEL_ID')` - -Per-session model config (temperature, system prompt, etc.) is not planned. Sessions use provider/model defaults from configPresenter. - -### 2.7 Message status lifecycle - -Messages use a three-state lifecycle: `pending` → `sent` | `error` - -- `pending` — message is being generated (stream in progress) -- `sent` — generation completed successfully -- `error` — generation failed or app crashed during generation - -**Crash recovery**: On app startup, any `deepchat_messages` rows with `status = 'pending'` are updated to `status = 'error'`. This handles the case where the app was killed during streaming. - -### 2.8 Stream batching - -Two independent batching intervals to balance responsiveness and write pressure: - -- **Renderer flush**: every 120ms — accumulate stream deltas and flush to renderer via EventBus -- **DB flush**: every 600ms — batch-write accumulated content to `deepchat_messages` table - -On stream end, both flush immediately with final content. - -## 3. Architecture: Module Breakdown - -### 3.1 Shared Types (`src/shared/types/`) - -**`agent-interface.d.ts`** — the protocol every agent implements: - -- `IAgentImplementation` interface with: `initSession`, `destroySession`, `getSessionState`, `processMessage`, `cancelGeneration`, `getMessages`, `getMessageIds`, `getMessage` -- v0 subset only — permissions, retry, edit added in later versions - -**`chat-types.d.ts`** — data model types: - -- `Agent`, `Session`, `SessionStatus`, `CreateSessionInput`, `ChatMessage`, `UserMessageContent`, `AssistantMessageBlock`, `MessageMetadata`, `Project` - -**`presenters/agent-session.presenter.d.ts`** — IPC-facing interface: - -- `IAgentSessionPresenter` — what the renderer can call: `createSession`, `sendMessage`, `getSessionList`, `getSession`, `getMessages`, `getMessageIds`, `getMessage`, `activateSession`, `deactivateSession`, `getActiveSession`, `getAgents`, `deleteSession` - -**`presenters/project.presenter.d.ts`** — IPC-facing interface: - -- `IProjectPresenter` — `getProjects`, `getRecentProjects`, `selectDirectory` - -### 3.2 New DB Tables (`src/main/presenter/sqlitePresenter/tables/`) - -**`newSessions.ts`** — thin session registry - -| Column | Type | Notes | -|--------|------|-------| -| id | TEXT PRIMARY KEY | UUID | -| agent_id | TEXT NOT NULL | `'deepchat'` for v0 | -| title | TEXT NOT NULL | | -| project_dir | TEXT | nullable | -| is_pinned | INTEGER DEFAULT 0 | | -| created_at | INTEGER NOT NULL | epoch ms | -| updated_at | INTEGER NOT NULL | epoch ms | - -**`newProjects.ts`** — project directory history - -| Column | Type | Notes | -|--------|------|-------| -| path | TEXT PRIMARY KEY | filesystem path | -| name | TEXT NOT NULL | directory basename | -| icon | TEXT DEFAULT NULL | base64 icon data | -| last_accessed_at | INTEGER NOT NULL | epoch ms | - -**`deepchatSessions.ts`** — deepchat agent session config - -| Column | Type | Notes | -|--------|------|-------| -| id | TEXT PRIMARY KEY | same ID as new_sessions | -| provider_id | TEXT NOT NULL | | -| model_id | TEXT NOT NULL | | - -**`deepchatMessages.ts`** — deepchat agent messages - -| Column | Type | Notes | -|--------|------|-------| -| id | TEXT PRIMARY KEY | UUID | -| session_id | TEXT NOT NULL | FK to new_sessions | -| order_seq | INTEGER NOT NULL | monotonic ordering within session | -| role | TEXT NOT NULL | `'user'` or `'assistant'` | -| content | TEXT NOT NULL | JSON string: `UserMessageContent` or `AssistantMessageBlock[]` | -| status | TEXT DEFAULT 'pending' | `'pending'`, `'sent'`, `'error'` | -| is_context_edge | INTEGER DEFAULT 0 | marks context window boundary (unused in v0) | -| metadata | TEXT DEFAULT '{}' | JSON string: token usage, timing, model info | -| created_at | INTEGER NOT NULL | epoch ms | -| updated_at | INTEGER NOT NULL | epoch ms | - -Index: `CREATE INDEX idx_deepchat_messages_session ON deepchat_messages(session_id, order_seq)` - -### 3.3 agentPresenter (`src/main/presenter/agentSessionPresenter/`) - -**`index.ts`** — main presenter class implementing `IAgentSessionPresenter` - -Owns: -- `sessionManager` — thin CRUD over `new_sessions` table -- `messageManager` — proxy that resolves agentId then delegates to agent -- `agentRegistry` — `Map`, populated in constructor with agentRuntimePresenter - -Methods (IPC-facing): -- `createSession(input, webContentsId)` → sessionManager.create() + agent.initSession() + agent.processMessage() + emit ACTIVATED -- `sendMessage(sessionId, content)` → resolve agent → agent.processMessage() -- `getSessionList(params)` → sessionManager.list() + enrich with agent.getSessionState() -- `getSession(sessionId)` → sessionManager.get() + agent.getSessionState() -- `getMessages(sessionId)` → resolve agent → agent.getMessages() -- `getMessageIds(sessionId)` → resolve agent → agent.getMessageIds() -- `getMessage(messageId)` → resolve agent → agent.getMessage() (needs sessionId lookup) -- `activateSession(webContentsId, sessionId)` → update window binding + emit ACTIVATED -- `deactivateSession(webContentsId)` → clear window binding + emit DEACTIVATED -- `getActiveSession(webContentsId)` → return bound session -- `getAgents()` → agentRegistry.getAll() -- `deleteSession(sessionId)` → agent.destroySession() + sessionManager.delete() - -Event relay: -- Listen to agent events (callback or EventEmitter pattern) -- Re-emit as SESSION_EVENTS / STREAM_EVENTS via EventBus to renderer -- All STREAM_EVENTS carry `conversationId` for renderer routing - -**`sessionManager.ts`** — thin session registry - -- `create(id, agentId, title, projectDir)` → INSERT into `new_sessions` -- `get(id)` → SELECT from `new_sessions`, returns thin record -- `list(filters)` → SELECT with optional WHERE agent_id / project_dir, ORDER BY updated_at DESC -- `update(id, fields)` → UPDATE `new_sessions` -- `delete(id)` → DELETE from `new_sessions` -- Window bindings: in-memory `Map` for webContentsId → sessionId - -**`messageManager.ts`** — proxy - -- `getMessages(sessionId)` → resolve agentId from sessionManager → agentRegistry.get(agentId).getMessages(sessionId) -- Same pattern for `getMessageIds`, `getMessage` - -**`agentRegistry.ts`** — agent discovery - -- `register(agentId, implementation)` — called in constructor -- `resolve(agentId)` → returns `IAgentImplementation` or throws -- `getAll()` → returns `Agent[]` list (for v0: just deepchat) - -### 3.4 agentRuntimePresenter (`src/main/presenter/agentRuntimePresenter/`) - -**`index.ts`** — implements `IAgentImplementation` - -Constructor receives: `llmProviderPresenter`, `configPresenter`, `sqlitePresenter` - -Initialization: -- Run crash recovery: `UPDATE deepchat_messages SET status = 'error' WHERE status = 'pending'` - -State: in-memory `Map` for runtime status - -Methods: -- `initSession(sessionId, config)` → INSERT into `deepchat_sessions`, set runtime status to `'idle'` -- `destroySession(sessionId)` → DELETE from `deepchat_sessions` + `deepchat_messages`, remove from in-memory map -- `getSessionState(sessionId)` → return `{ status, providerId, modelId }` from in-memory + DB -- `processMessage(sessionId, content)` → persist user message (status `'sent'`) → create assistant message (status `'pending'`) → call LLM → stream with batching → finalize assistant message (status `'sent'`) → emit events -- `cancelGeneration(sessionId)` → abort stream (v0: basic abort signal support) -- `getMessages(sessionId)` → SELECT from `deepchat_messages` WHERE session_id ORDER BY order_seq -- `getMessageIds(sessionId)` → SELECT id from `deepchat_messages` WHERE session_id ORDER BY order_seq -- `getMessage(messageId)` → SELECT from `deepchat_messages` WHERE id - -**`streamHandler.ts`** — LLM stream consumer - -Responsibility: consume `AsyncGenerator`, build structured `AssistantMessageBlock[]` content, persist with batching, emit events. - -Flow: -1. Get provider instance via `llmProviderPresenter.getProviderInstance(providerId)` -2. Build messages array (v0: just the user message as plain text) -3. Call `provider.coreStream(messages, modelId, modelConfig, temperature, maxTokens, tools=[])` -4. Iterate the async generator, accumulating into `AssistantMessageBlock[]`: - - On `text` event → append to current `content` block, queue renderer flush - - On `reasoning` event → append to current `reasoning_content` block, queue renderer flush - - On `usage` event → store in metadata - - On `stop` event → finalize all blocks, flush DB, set message status to `'sent'`, emit `stream:end` - - On `error` event → add `error` block, set message status to `'error'`, emit `stream:error` -5. Renderer batching: flush accumulated deltas every 120ms as `stream:response` with `LLMAgentEventData` (includes `conversationId`, `eventId`) -6. DB batching: write accumulated content JSON to `deepchat_messages` every 600ms -7. On stream end or error, flush both immediately - -**`sessionStore.ts`** — deepchat session persistence - -- CRUD over `deepchat_sessions` table (columns: id, provider_id, model_id) - -**`messageStore.ts`** — deepchat message persistence - -- CRUD over `deepchat_messages` table -- `createUserMessage(sessionId, orderSeq, content)` → INSERT with role='user', status='sent', content=JSON string of `UserMessageContent` -- `createAssistantMessage(sessionId, orderSeq)` → INSERT with role='assistant', status='pending', content='[]' -- `updateAssistantContent(messageId, contentJson)` → UPDATE content (batched) -- `finalizeAssistantMessage(messageId, contentJson, metadataJson)` → UPDATE content, status='sent', metadata -- `setMessageError(messageId, errorJson)` → UPDATE status='error', content with error block appended -- `recoverPendingMessages()` → UPDATE status='error' WHERE status='pending' (called on startup) -- `getNextOrderSeq(sessionId)` → SELECT MAX(order_seq) + 1 - -### 3.5 projectPresenter (`src/main/presenter/projectPresenter/`) - -**`index.ts`** — implements `IProjectPresenter` - -Constructor receives: `sqlitePresenter`, `devicePresenter` - -Methods: -- `getProjects()` → SELECT * FROM new_projects ORDER BY last_accessed_at DESC -- `getRecentProjects(limit)` → SELECT * FROM new_projects ORDER BY last_accessed_at DESC LIMIT ? -- `selectDirectory()` → `devicePresenter.selectDirectory()`, if selected upsert into new_projects, return path - -### 3.6 Presenter Registration (`src/main/presenter/index.ts`) - -3 touchpoints: - -1. Import and add to IPresenter: `agentSessionPresenter: IAgentSessionPresenter`, `projectPresenter: IProjectPresenter` -2. Add class properties -3. Instantiate in constructor: - - `this.agentRuntimePresenter = new AgentRuntimePresenter(this.llmProviderPresenter, this.configPresenter, this.sqlitePresenter)` - - `this.agentSessionPresenter = new AgentSessionPresenter(this.agentRuntimePresenter, this.configPresenter, this.sqlitePresenter, this.eventBus)` - - `this.projectPresenter = new ProjectPresenter(this.sqlitePresenter, this.devicePresenter)` - -Note: `agentRuntimePresenter` is NOT exposed on IPresenter — it's internal. Only `agentSessionPresenter` and `projectPresenter` are IPC-accessible. - -### 3.7 Events (`src/main/events.ts`) - -Add: - -``` -SESSION_EVENTS = { - LIST_UPDATED: 'session:list-updated', - ACTIVATED: 'session:activated', - DEACTIVATED: 'session:deactivated', - STATUS_CHANGED: 'session:status-changed', -} -``` - -STREAM_EVENTS reused as-is — same event names, same payload format. All stream events include `conversationId` for renderer-side routing. - -### 3.8 Renderer Stores (`src/renderer/src/stores/ui/`) - -**`session.ts`** — rewrite - -- Uses `useLegacyPresenter('agentSessionPresenter')` -- State: `sessions: Session[]`, `activeSessionId`, `groupMode` -- Actions: `fetchSessions()`, `createSession(input)`, `selectSession(id)`, `closeSession()` -- Listens to: `SESSION_EVENTS.LIST_UPDATED`, `SESSION_EVENTS.ACTIVATED`, `SESSION_EVENTS.DEACTIVATED`, `SESSION_EVENTS.STATUS_CHANGED` - -**`message.ts`** — new - -- Uses `useLegacyPresenter('agentSessionPresenter')` -- State: `messageIds: string[]`, `messageCache: Map`, `isStreaming: boolean`, `streamingBlocks: AssistantMessageBlock[]` -- Actions: `loadMessages(sessionId)`, `getMessage(id)` -- Listens to: `STREAM_EVENTS.RESPONSE` (update streaming blocks from `LLMAgentEventData`), `STREAM_EVENTS.END` (finalize), `STREAM_EVENTS.ERROR` -- Filters events by `conversationId` matching active session - -**`agent.ts`** — rewrite - -- Uses `useLegacyPresenter('agentSessionPresenter')` -- State: `agents: Agent[]`, `selectedAgentId` -- Actions: `fetchAgents()`, `selectAgent(id)` - -**`project.ts`** — rewrite - -- Uses `useLegacyPresenter('projectPresenter')` -- State: `projects: Project[]`, `selectedProjectPath` -- Actions: `fetchProjects()`, `selectProject(path)`, `openFolderPicker()` - -**`draft.ts`** — new - -- State: `providerId`, `modelId`, `projectDir`, `agentId`, `reasoningEffort` -- Actions: `toCreateInput(message)` → `CreateSessionInput`, `reset()` - -### 3.9 NewThreadPage Integration - -- Imports `useSessionStore`, `useDraftStore`, `useMessageStore` -- On submit: `draftStore.toCreateInput(text)` → `sessionStore.createSession(input)` -- Session ACTIVATED event triggers message loading -- messageStore receives STREAM_EVENTS, filters by `conversationId`, and displays streaming blocks - -## 4. Test Strategy - -### 4.1 Unit Tests - -**agentPresenter:** -- `sessionManager.create/get/list/update/delete` — CRUD against in-memory SQLite -- `agentRegistry.register/resolve/getAll` — correct routing -- `createSession` → calls sessionManager.create + agent.initSession + agent.processMessage - -**agentRuntimePresenter:** -- `processMessage` → creates user message (JSON content), calls LLM, creates assistant message (JSON blocks) -- `streamHandler` — given mock `AsyncGenerator`, verify: block accumulation, batched DB writes at 600ms, renderer flush at 120ms, final flush on stop -- `messageStore` — CRUD operations against in-memory SQLite, verify JSON content round-trip -- `messageStore.recoverPendingMessages()` — pending rows updated to error -- `messageStore.getNextOrderSeq()` — correct sequence calculation - -**projectPresenter:** -- `getRecentProjects` — returns correct order and limit -- `selectDirectory` — upserts on new selection - -### 4.2 Integration Tests - -- End-to-end: `agentSessionPresenter.createSession()` → verify new_sessions row + deepchat_sessions row + deepchat_messages rows (with valid JSON content) + events emitted with conversationId -- Coexistence: old `sessionPresenter.createSession()` still works — old tables unaffected -- Crash recovery: insert pending message, reinitialize presenter, verify status changed to error - -## 5. Risks & Mitigation - -| Risk | Impact | Mitigation | -|------|--------|------------| -| LLM provider internal API changes | Cannot call `coreStream()` directly | Check `BaseLLMProvider` is stable; if not, use `llmProviderPresenter` public method | -| Stream event format mismatch | Renderer can't display response | Reuse exact `LLMAgentEventData` format — same as old agentPresenter emits | -| DB migration conflicts with old tables | Data corruption | New tables use distinct names (`new_sessions`, `deepchat_*`) — zero overlap | -| Performance of batched message writes | Streaming feels laggy | DB flush every 600ms, renderer flush every 120ms — matches old `StreamUpdateScheduler` intervals | -| JSON content parsing errors | Messages unreadable | Validate JSON on write, wrap parse in try/catch on read, store raw text as fallback error block | - -## 6. Quality Gate - -- [ ] `pnpm run format` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] Unit tests pass for all new modules -- [ ] Integration test: create session + stream response end-to-end -- [ ] Old UI regression: `sessionPresenter.getSessionList()` returns same results as before - diff --git a/docs/archives/new-agent/spec.md b/docs/archives/new-agent/spec.md deleted file mode 100644 index 2334eb871..000000000 --- a/docs/archives/new-agent/spec.md +++ /dev/null @@ -1,216 +0,0 @@ -# New Agent Architecture v0 — Minimal Single-Turn Chat - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -Replace the old sessionPresenter + agentPresenter architecture with an agent-centric model. v0 delivers the minimum working system: a user can create a session, send one message, and see a streamed LLM response — all through the new architecture with new DB tables and new presenters. - -v0 is the foundation. It proves the architecture end-to-end. Subsequent versions (v1–v5) add multi-turn, tool calling, permissions, config, and ACP support incrementally. - -## Background - -See [Full-Stack Mismatch Analysis](../../architecture/new-ui-store-presenter-mismatch.md) for the complete problem analysis and architectural decisions, including: -- Why agents are the organizing principle (not sessions) -- Why new presenters coexist with old ones (not wrap/translate) -- The ownership model: agents own their data, agentPresenter owns the registry -- The "build new, grow forward" strategy - -## Goals - -1. **Agent interface protocol** — define the unified contract all agents implement -2. **agentPresenter** — router, thin session registry, event relay -3. **agentRuntimePresenter** — single-turn chat: message → LLM → streamed response → persist -4. **projectPresenter** — thin project directory CRUD -5. **New DB tables** — `new_sessions`, `new_projects`, `deepchat_sessions`, `deepchat_messages` -6. **New renderer stores** — sessionStore, messageStore, agentStore, projectStore, draftStore -7. **NewThreadPage integration** — create session and see streamed response through new architecture - -## Non-Goals (deferred to later versions) - -- Multi-turn conversation context assembly (v1) -- Tool calling / MCP integration (v2) -- Permission and question flows (v3) -- ACP agent support (v5) -- Migration / backfill from old tables (separate task) -- Modifying or removing any old presenter or old UI code -- i18n for new components (follow-up) - -## Scope Boundary - -### New files to create - -**Main process:** -- `src/shared/types/agent-interface.d.ts` — agent interface protocol -- `src/shared/types/chat-types.d.ts` — Agent, Session, Project, CreateSessionInput, ChatMessage, MessageBlock types -- `src/main/presenter/agentSessionPresenter/index.ts` — agentPresenter (router) -- `src/main/presenter/agentSessionPresenter/sessionManager.ts` — thin session registry -- `src/main/presenter/agentSessionPresenter/messageManager.ts` — message proxy -- `src/main/presenter/agentSessionPresenter/agentRegistry.ts` — agent discovery + routing -- `src/main/presenter/agentRuntimePresenter/index.ts` — deepchat agent implementation -- `src/main/presenter/agentRuntimePresenter/sessionStore.ts` — agent-owned session persistence -- `src/main/presenter/agentRuntimePresenter/messageStore.ts` — agent-owned message persistence -- `src/main/presenter/agentRuntimePresenter/streamHandler.ts` — LLM stream → message persistence + event emission -- `src/main/presenter/projectPresenter/index.ts` — project CRUD -- `src/main/presenter/sqlitePresenter/tables/` — new table definitions -- `src/main/events.ts` — add SESSION_EVENTS - -**Renderer:** -- `src/renderer/src/stores/ui/session.ts` — rewrite -- `src/renderer/src/stores/ui/message.ts` — new -- `src/renderer/src/stores/ui/agent.ts` — rewrite -- `src/renderer/src/stores/ui/project.ts` — rewrite -- `src/renderer/src/stores/ui/draft.ts` — new - -### Existing files to modify - -- `src/shared/types/presenters/legacy.presenters.d.ts` — add to IPresenter interface -- `src/main/presenter/index.ts` — register new presenters (3 touchpoints) -- `src/renderer/src/pages/NewThreadPage.vue` — wire to new stores -- `src/renderer/src/events.ts` — add SESSION_EVENTS mirror - -### Explicitly NOT modified - -- All old presenters (sessionPresenter, old agentPresenter, threadPresenter) -- Old DB tables (conversations, messages) -- Legacy chatStore (`stores/chat.ts`) -- Old UI components that use legacy stores (ChatPage stays on old stores for now) -- LLM provider implementations (reused as-is) -- mcpPresenter (reused as-is, wired in v2) - -## Acceptance Criteria - -### Functional Requirements - -- [ ] Agent interface protocol defined as TypeScript types in `src/shared/types/` -- [ ] agentPresenter registered in Presenter class, accessible via `useLegacyPresenter('agentSessionPresenter')` -- [ ] agentPresenter.sessionManager creates session records in new `new_sessions` table -- [ ] agentPresenter.agentRegistry returns `[{ id: 'deepchat', name: 'DeepChat', type: 'deepchat', enabled: true }]` -- [ ] agentPresenter routes `sendMessage()` to agentRuntimePresenter based on session's agentId -- [ ] agentRuntimePresenter calls LLM provider's `coreStream()` and receives `LLMCoreStreamEvent` stream -- [ ] agentRuntimePresenter persists user message and assistant message in `deepchat_messages` table with structured JSON content -- [ ] agentRuntimePresenter emits stream events (response/end/error) via EventBus with `conversationId` for routing -- [ ] agentPresenter relays all stream events to renderer -- [ ] Stream events batched: 120ms flush to renderer, 600ms flush to DB -- [ ] On app restart, any messages with `status = 'pending'` are marked as `'error'` (crash recovery) -- [ ] projectPresenter reads/writes `new_projects` table -- [ ] NewThreadPage creates a session and displays streamed response through new architecture -- [ ] sessionStore displays session list in sidebar from new `new_sessions` table -- [ ] messageStore displays messages from new `deepchat_messages` table -- [ ] Old UI continues to work via old presenters — zero regression - -### Non-Functional Requirements - -- [ ] New tables created alongside old tables in same chat.db (no separate DB file) -- [ ] All new types in `src/shared/types/` — no type definitions in presenter files -- [ ] `pnpm run typecheck` passes -- [ ] `pnpm run lint` passes -- [ ] `pnpm run format` passes -- [ ] Unit tests for agentPresenter routing, agentRuntimePresenter stream handling, sessionManager CRUD - -## Constraints - -- LLM provider HTTP clients are reused directly — agentRuntimePresenter calls `BaseLLMProvider.coreStream()` and consumes the `AsyncGenerator` -- New tables live in the same SQLite database (`chat.db`) alongside old tables — no separate DB file -- IPC routing is dynamic (`presenter[name]`) — no route registration needed beyond the 3 touchpoints -- v0 sends a single user message and gets a single assistant response — no multi-turn context, no tool use -- Sessions use default model config from `configPresenter` — no per-session config overrides (by design, not deferred) - -## Data Model (v0 subset) - -### Agent - -- `id: string` — `'deepchat'` (only agent in v0) -- `name: string` — `'DeepChat'` -- `type: 'deepchat' | 'acp'` — `'deepchat'` -- `enabled: boolean` — `true` - -### Session (UI-facing) - -- `id: string` -- `title: string` -- `status: 'idle' | 'generating' | 'error'` — (`'waiting'` deferred to v3) -- `agentId: string` -- `projectDir: string | null` -- `providerId: string` -- `modelId: string` -- `isPinned: boolean` -- `createdAt: number` -- `updatedAt: number` - -### CreateSessionInput - -- `agentId: string` -- `message: string` -- `projectDir?: string` -- `providerId?: string` -- `modelId?: string` - -### Message (UI-facing, v0 subset) - -- `id: string` -- `sessionId: string` -- `orderSeq: number` — monotonic ordering within a session -- `role: 'user' | 'assistant'` -- `content: string` — JSON string in DB. For user: serialized `UserMessageContent`. For assistant: serialized `AssistantMessageBlock[]` -- `status: 'pending' | 'sent' | 'error'` — `pending` = generation in progress, `sent` = complete, `error` = failed or crash recovery -- `metadata: string` — JSON string for token usage, timing, model info -- `createdAt: number` -- `updatedAt: number` - -### UserMessageContent (stored as JSON) - -- `text: string` — the user's input text -- `files: MessageFile[]` — attached files (empty in v0) -- `links: string[]` — attached links (empty in v0) -- `search: boolean` — whether web search was requested -- `think: boolean` — whether thinking/reasoning was requested - -### AssistantMessageBlock (stored as JSON array) - -Each block has a `type` discriminator. v0 uses these block types: - -- `content` — text content from LLM response. Fields: `type`, `content`, `status`, `timestamp` -- `reasoning_content` — thinking/reasoning output. Fields: `type`, `content`, `status`, `timestamp`, `reasoning_time` -- `error` — error information. Fields: `type`, `content`, `status`, `timestamp` - -Additional block types added in later versions: `tool_call` (v2), `search` (v2), `action` (v2), `image` (v2) - -### MESSAGE_METADATA (stored in metadata JSON field) - -- `totalTokens: number` -- `inputTokens: number` -- `outputTokens: number` -- `generationTime: number` -- `firstTokenTime: number` -- `tokensPerSecond: number` -- `model?: string` -- `provider?: string` - -### Project - -- `path: string` -- `name: string` -- `icon: string | null` — base64 icon data -- `lastAccessedAt: number` - -## Event System (v0 subset) - -### SESSION_EVENTS (emitted by agentPresenter) - -- `session:list-updated` — session list changed -- `session:activated` — `{ webContentsId, sessionId }` -- `session:deactivated` — `{ webContentsId }` -- `session:status-changed` — `{ sessionId, status }` - -### STREAM_EVENTS (relayed from agents, unchanged format) - -- `stream:response` — streaming chunk, carries `LLMAgentEventData` with `conversationId` and `eventId` for routing -- `stream:end` — streaming complete, carries `{ conversationId }` -- `stream:error` — streaming failed, carries `{ conversationId, error }` - -## Open Questions - -None. All architectural decisions resolved in the mismatch analysis document. - diff --git a/docs/archives/new-agent/tasks.md b/docs/archives/new-agent/tasks.md deleted file mode 100644 index 6dc12d250..000000000 --- a/docs/archives/new-agent/tasks.md +++ /dev/null @@ -1,116 +0,0 @@ -# New Agent Architecture — Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## T0 Shared Types & Events - -- [x] Create `src/shared/types/agent-interface.d.ts` — `IAgentImplementation`, `Agent`, `Session`, `SessionStatus`, `CreateSessionInput`, `ChatMessageRecord`, `UserMessageContent`, `AssistantMessageBlock`, `MessageMetadata`, `Project` (merged chat-types into this file) -- [x] Create `src/shared/types/presenters/agent-session.presenter.d.ts` — `IAgentSessionPresenter` interface -- [x] Create `src/shared/types/presenters/project.presenter.d.ts` — `IProjectPresenter` interface -- [x] Export new types from `src/shared/types/presenters/index.d.ts` -- [x] Add `SESSION_EVENTS` to `src/main/events.ts` (list-updated, activated, deactivated, status-changed) -- [x] Add `SESSION_EVENTS` mirror to `src/renderer/src/events.ts` - -## T1 New DB Tables - -- [x] Create `src/main/presenter/sqlitePresenter/tables/newSessions.ts` — `new_sessions` table (no provider_id/model_id — agent owns those) -- [x] Create `src/main/presenter/sqlitePresenter/tables/newProjects.ts` — `new_projects` table with `icon` column -- [x] Create `src/main/presenter/sqlitePresenter/tables/deepchatSessions.ts` — `deepchat_sessions` table (id, provider_id, model_id only — no per-session config columns) -- [x] Create `src/main/presenter/sqlitePresenter/tables/deepchatMessages.ts` — `deepchat_messages` table with order_seq, JSON content, status (pending/sent/error), is_context_edge, metadata; index on (session_id, order_seq) -- [x] Register new tables in `sqlitePresenter/index.ts` (initTables + migrate array) - -## T2 agentRuntimePresenter - -- [x] Create `messageStore.ts` — CRUD over `deepchat_messages` -- [x] Create `sessionStore.ts` — CRUD over `deepchat_sessions` -- [x] Create `index.ts` — implements `IAgentImplementation`, wires sessionStore + messageStore + llmProviderPresenter, runs crash recovery on init -- [x] Unit tests: processMessage, recoverPendingMessages (`agentRuntimePresenter.test.ts`) - -## T3 agentPresenter (agentSessionPresenter) - -- [x] Create `src/main/presenter/agentSessionPresenter/agentRegistry.ts` — register/resolve/getAll -- [x] Create `src/main/presenter/agentSessionPresenter/sessionManager.ts` — CRUD over `new_sessions`, in-memory window bindings (webContentsId → sessionId) -- [x] Create `src/main/presenter/agentSessionPresenter/messageManager.ts` — proxy resolves agentId then delegates to agent -- [x] Create `src/main/presenter/agentSessionPresenter/index.ts` — implements `IAgentSessionPresenter`, wires sessionManager + messageManager + agentRegistry + event relay (all stream events carry conversationId) -- [x] Unit tests: sessionManager CRUD + window bindings (`test/main/presenter/agentSessionPresenter/sessionManager.test.ts`) -- [x] Unit tests: agentRegistry register/resolve/getAll/has (`test/main/presenter/agentSessionPresenter/agentRegistry.test.ts`) -- [x] Unit tests: messageManager delegation (`test/main/presenter/agentSessionPresenter/messageManager.test.ts`) -- [x] Unit tests: createSession → verify sessionManager.create + agent.initSession + agent.processMessage called (`test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts`) -- [x] Unit tests: sendMessage → verify agent routing (`test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts`) - -## T4 projectPresenter - -- [x] Create `src/main/presenter/projectPresenter/index.ts` — implements `IProjectPresenter`, CRUD over `new_projects`, selectDirectory via devicePresenter -- [x] Unit tests: getProjects, getRecentProjects (order + limit), selectDirectory (`test/main/presenter/projectPresenter/projectPresenter.test.ts`) - -## T5 Presenter Registration - -- [x] Add `IAgentSessionPresenter` and `IProjectPresenter` to `IPresenter` interface in `src/shared/types/presenters/legacy.presenters.d.ts` -- [x] Add properties and constructor instantiation in `src/main/presenter/index.ts` -- [x] Verify: `useLegacyPresenter('agentSessionPresenter')` and `useLegacyPresenter('projectPresenter')` callable from renderer - -## T6 Renderer Stores - -- [x] Rewrite `src/renderer/src/stores/ui/session.ts` — uses `agentSessionPresenter`, listens to `SESSION_EVENTS`, uses `webContentsId` for activation -- [x] Create `src/renderer/src/stores/ui/message.ts` — uses `agentSessionPresenter`, listens to `STREAM_EVENTS`, filters by conversationId, maintains streamingBlocks as AssistantMessageBlock[] -- [x] Rewrite `src/renderer/src/stores/ui/agent.ts` — uses `agentSessionPresenter.getAgents()` -- [x] Rewrite `src/renderer/src/stores/ui/project.ts` — uses `projectPresenter` -- [x] Create `src/renderer/src/stores/ui/draft.ts` — pre-session config, toCreateInput() - -## T7 NewThreadPage Integration - -- [x] Update `src/renderer/src/pages/NewThreadPage.vue` — wire to new stores (removed `title` from CreateSessionInput, title derived from message in presenter) -- [x] Update `src/renderer/src/views/ChatTabView.vue` — `deriveFromSessions` → `fetchProjects` -- [x] Verify: type message → submit → session created → streaming response displayed with structured blocks -- [x] Verify: session appears in sidebar via sessionStore - -## T8 Quality Gate & Verification - -- [x] `pnpm run typecheck` — passes -- [x] `pnpm run lint` — passes (0 warnings, 0 errors) -- [x] `pnpm run format` — passes -- [x] Unit tests: all new modules passing -- [x] Integration test: createSession end-to-end — new_sessions row + deepchat_sessions row + deepchat_messages rows (valid JSON content) + events with conversationId (`test/main/presenter/agentSessionPresenter/integration.test.ts`) -- [x] Integration test: crash recovery — insert pending message, reinit, verify status = error (`test/main/presenter/agentSessionPresenter/integration.test.ts`) -- [x] Verify old UI regression: old `sessionPresenter` / `chatStore` still functional — zero impact -- [x] Manual verify: run `pnpm run dev`, create session via NewThreadPage, see streamed response - ---- - -## v1: Multi-Turn Context Assembly (complete) - -- [x] Create `contextBuilder.ts` — context assembly + truncation -- [x] Modify `processMessage` in `index.ts` — wire context builder -- [x] Unit tests for context builder (`contextBuilder.test.ts`) -- [x] Update `agentRuntimePresenter.test.ts` — mock `getDefaultSystemPrompt`, verify multi-turn messages -- [x] Update `integration.test.ts` — verify multi-turn flow end-to-end -- [x] Quality gate: typecheck, lint, format, tests - -## v2: Tool Calling / MCP Integration (complete) - -- [x] Fetch MCP tool definitions via `ToolPresenter.getAllToolDefinitions()` and pass to `coreStream` -- [x] `tool_call_start/chunk/end` events create `tool_call` blocks in the stream -- [x] `stop_reason: 'tool_use'` triggers tool execution via `ToolPresenter.callTool()` -- [x] Tool results appended as `role: 'tool'` messages, loop re-invokes `coreStream` -- [x] Multi-turn tool loop works (multiple rounds of tool calls) -- [x] Max tool calls limit (128) stops the loop -- [x] Abort signal cancels the loop mid-execution -- [x] Tool call blocks rendered with name, params, and response -- [x] Interleaved thinking support for deepseek-reasoner / kimi-k2-thinking / glm-4.7 -- [x] Quality gate: typecheck, lint, format, tests -- [x] Fix: stop passing sessionId as conversationId to tool definitions (new agent doesn't use skills) - -## v3: Stream Processing Refactor (complete) - -- [x] Create `src/shared/utils/throttle.ts` — reusable trailing-edge throttle utility -- [x] Create `types.ts` — `StreamState`, `IoParams`, `ProcessParams`, `createState()` -- [x] Create `accumulator.ts` — pure `accumulate(state, event)` block mutations -- [x] Create `echo.ts` — interval-based flush to renderer (120ms) + DB (600ms) with throttle -- [x] Create `dispatch.ts` — `executeTools()`, `finalize()`, `finalizeError()` -- [x] Create `process.ts` — unified `processStream()` loop, single code path for tools and no-tools -- [x] Update `index.ts` — replace `handleStream`/`agentLoop` with single `processStream()` call -- [x] Delete `streamHandler.ts` and `agentLoop.ts` -- [x] Tests: `throttle.test.ts` (7), `accumulator.test.ts` (14), `echo.test.ts` (5), `dispatch.test.ts` (14), `process.test.ts` (9), updated `agentRuntimePresenter.test.ts` (19) -- [x] Quality gate: typecheck, lint, format, 89 tests passing - diff --git a/docs/archives/new-agent/v1-spec.md b/docs/archives/new-agent/v1-spec.md deleted file mode 100644 index 20dbd9a3e..000000000 --- a/docs/archives/new-agent/v1-spec.md +++ /dev/null @@ -1,62 +0,0 @@ -# New Agent Architecture v1 — Multi-Turn Context Assembly - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -v0 proved the new agent architecture end-to-end but sends only the latest user message to the LLM — no conversation history, no system prompt. Every message starts a fresh, context-free conversation. v1 adds multi-turn context assembly so the LLM sees the full conversation history within token limits. - -## Goals - -1. **System prompt injection** — prepend the user's configured default system prompt to every LLM call -2. **Conversation history** — include all prior sent messages in the LLM context -3. **Context window truncation** — drop oldest user+assistant pairs when history exceeds available tokens - -## Non-Goals (deferred) - -- Tool calling context (v2) -- `is_context_edge` usage (future) -- Per-session system prompts (future) -- Vision/image content in messages (future) -- Streaming context or partial messages - -## Data Model - -No new DB tables or columns. Reuses existing: -- `deepchat_messages` table — fetch prior messages by session -- `configPresenter.getDefaultSystemPrompt()` — system prompt retrieval -- `ModelConfig.contextLength` — context window size - -## Context Assembly Algorithm - -1. Fetch all messages for session via `messageStore.getMessages(sessionId)` -2. Filter to `status === 'sent'` only (skip pending/error messages) -3. Exclude the just-inserted new user message (it hasn't been marked sent yet — it's the latest with status 'sent' but we pass `newUserContent` explicitly) -4. Convert each `ChatMessageRecord` → `ChatMessage`: - - **User**: parse JSON `UserMessageContent`, extract `.text` - - **Assistant**: parse JSON `AssistantMessageBlock[]`, concatenate text from `content` and `reasoning_content` blocks -5. Apply truncation to fit within token budget -6. Prepend system prompt (if non-empty): `{ role: 'system', content }` -7. Append new user message: `{ role: 'user', content: newUserContent }` - -## Truncation Strategy - -1. Calculate: `available = contextLength - systemPromptTokens - newUserMessageTokens - reserveForOutput` -2. Sum tokens of all history messages using `approximateTokenSize` from `tokenx` -3. If total exceeds `available`, drop oldest messages from the front until it fits -4. Return trimmed history - -Reserve for output: use `maxTokens` from model config to ensure the model has room to respond. - -## Acceptance Criteria - -- [ ] Multi-turn works: LLM sees prior messages in conversation -- [ ] System prompt injected as first message when non-empty -- [ ] System prompt omitted when empty string -- [ ] Truncation drops oldest messages when history exceeds available tokens -- [ ] Error/pending messages excluded from context -- [ ] Assistant blocks concatenated correctly (content + reasoning_content) -- [ ] `pnpm run typecheck` passes -- [ ] All tests pass -- [ ] `pnpm run lint && pnpm run format` passes diff --git a/docs/archives/new-agent/v2-spec.md b/docs/archives/new-agent/v2-spec.md deleted file mode 100644 index ba1687075..000000000 --- a/docs/archives/new-agent/v2-spec.md +++ /dev/null @@ -1,135 +0,0 @@ -# New Agent Architecture v2 — Tool Calling / MCP Integration - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Status: Complete (superseded by v3 refactor) - -> The v2 implementation has been refactored into the v3 module structure. See `v3-spec.md` for the current architecture. This spec is retained for historical context on design decisions. -> Note: the historical MCP UI resource exploration mentioned below is no longer supported in the current codebase. - -## Overview - -v0 proved single-turn chat, v1 added multi-turn context assembly. The LLM currently receives `tools: []` — no tool definitions, no tool execution. v2 adds MCP tool calling so the LLM can invoke tools and receive results in an agent loop. - -## Goals - -1. **Tool definition discovery** — fetch MCP tool definitions via `ToolPresenter.getAllToolDefinitions()` and pass to `coreStream` -2. **Agent loop** — when `coreStream` stops with `stop_reason: 'tool_use'`, execute tools and re-call `coreStream` with results -3. **Tool execution** — call tools via `ToolPresenter.callTool()`, format results as `role: 'tool'` messages -4. **Tool call rendering** — emit `tool_call` blocks so the renderer displays tool invocations and results -5. **Safety limit** — cap tool calls at MAX_TOOL_CALLS per processMessage invocation - -## Non-Goals (deferred) - -- Permission pre-checking / user approval -- Question tool — halting the loop for user input -- ACP agent tool routing — ACP handles tools internally -- Search result extraction from tool responses -- Tool system prompt injection (`ToolPresenter.buildToolSystemPrompt`) - -## Data Model - -No new DB tables. Changes to existing types: - -- `AssistantBlockType` gains `'tool_call'` variant -- `AssistantMessageBlock` gains optional `tool_call` field for tool metadata -- Tool results are transient within the agent loop's `conversationMessages` array — not persisted as separate DB records (tool_call blocks in the assistant message capture the tool name, params, and response for display) - -## Architecture (current — v3 module structure) - -The v2 goals are implemented in the v3 module structure. The original `streamHandler.ts` + `agentLoop.ts` were refactored into five focused modules: - -``` -agentRuntimePresenter/ - index.ts — session lifecycle + single processStream() call - process.ts — unified loop: stream → accumulate → echo → dispatch - accumulator.ts — accumulate(state, event): pure block mutations - echo.ts — interval-based flush to renderer + DB - dispatch.ts — executeTools() + finalize() + finalizeError() - types.ts — StreamState, IoParams, ProcessParams - contextBuilder.ts — DB records to ChatMessage[], truncation - messageStore.ts — SQLite wrapper - sessionStore.ts — SQLite wrapper -``` - -### Tool Call Flow - -``` -processMessage(sessionId, content) - ├── buildContext(...) → ChatMessage[] - ├── toolPresenter.getAllToolDefinitions(...) → MCPToolDefinition[] - └── processStream(params) - │ - ├── LOOP: - │ ├── coreStream(conversation, model, config, temp, maxTokens, tools) - │ ├── for await (event of stream): accumulate(state, event) - │ │ - │ ├── if stopReason !== 'tool_use' → BREAK - │ │ - │ ├── executeTools(state, conversation, prevBlockCount, ...) - │ │ ├── build assistant message (content + tool_calls + reasoning_content) - │ │ ├── for each tool call: - │ │ │ callTool() → push tool result to conversation → update block - │ │ └── enrich blocks with server info - │ ├── echo.flush() - │ │ - │ └── if toolCallCount > MAX_TOOL_CALLS → BREAK - │ - ├── finalize(state, io) - └── (catch) finalizeError(state, io, err) -``` - -### Stream Event Mapping - -| LLMCoreStreamEvent | Action | -|---|---| -| `tool_call_start` | Create `tool_call` block with `status: 'pending'`, record id + name | -| `tool_call_chunk` | Accumulate arguments into pending tool call | -| `tool_call_end` | Finalize arguments, move to completedToolCalls | -| `stop` with `stop_reason: 'tool_use'` | Break out of stream, enter tool execution | -| `stop` with other reason | Break out of loop, finalize | - -### Tool Call Block Format - -```typescript -{ - type: 'tool_call', - content: '', // unused for tool_call type - status: 'pending' | 'success' | 'error', - timestamp: number, - tool_call: { - id: string, - name: string, - params: string, // JSON arguments - response: string, // tool result text - server_name?: string, - server_icons?: string, - server_description?: string - } -} -``` - -## Key Dependencies - -- `IToolPresenter` — `src/shared/types/presenters/tool.presenter.d.ts` - - `getAllToolDefinitions(context)` → `MCPToolDefinition[]` - - `callTool(request: MCPToolCall)` → `{ content, rawData: MCPToolResponse }` -- `MCPToolDefinition`, `MCPToolCall`, `MCPToolResponse` — `src/shared/types/core/mcp.ts` -- `ChatMessage` with `tool_calls` and `tool_call_id` — `src/shared/types/core/chat-message.ts` -- `StopStreamEvent.stop_reason: 'tool_use'` — `src/shared/types/core/llm-events.ts` -- `ToolPresenter` already instantiated at `src/main/presenter/index.ts:191` - -## Acceptance Criteria - -- [x] Tool definitions passed to `coreStream` when tools are available -- [x] `tool_call_start/chunk/end` events create `tool_call` blocks in the stream -- [x] `stop_reason: 'tool_use'` triggers tool execution via `ToolPresenter.callTool()` -- [x] Tool results appended as `role: 'tool'` messages and loop re-invokes `coreStream` -- [x] Multi-turn tool loop works: LLM calls tools, gets results, calls more tools or produces final answer -- [x] Max tool calls limit (128) stops the loop -- [x] Abort signal cancels the loop mid-execution -- [x] Tool call blocks rendered in the UI with name, params, and response -- [x] `pnpm run typecheck` passes -- [x] All tests pass -- [x] `pnpm run lint && pnpm run format` passes diff --git a/docs/archives/new-agent/v3-spec.md b/docs/archives/new-agent/v3-spec.md deleted file mode 100644 index e894202ad..000000000 --- a/docs/archives/new-agent/v3-spec.md +++ /dev/null @@ -1,275 +0,0 @@ -# New Agent Architecture v3 — Stream Processing Refactor - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Status: Complete - -## Overview - -v2 added MCP tool calling via an agent loop. The implementation worked but had tangled responsibilities: `streamHandler.ts` (336 lines) mixed stream parsing, block accumulation, flush scheduling, and message finalization. `agentLoop.ts` shared ownership of blocks and finalization with `streamHandler`, leading to the `initialBlocks` hack and conditional `!context.initialBlocks` branching. Two code paths diverged in `index.ts` (tools vs no-tools). - -v3 refactored the stream processing into five focused pieces with clear boundaries, no shared ownership, and a single code path. - -## Goals - -1. **Separate stream accumulation from side effects** — event handling is pure block mutation, flush is independent -2. **Single loop, single code path** — no tools-vs-no-tools branching (zero tools = loop runs once) -3. **Throttled flush as a utility** — reusable throttle function, not embedded timers -4. **Each module does one thing** — accumulate, echo, dispatch, process, types -5. **No `initialBlocks` hack** — the loop owns the state, passes it down, nobody round-trips it - -## Non-Goals - -- Changing the block data model or DB schema -- Changing renderer event contracts (STREAM_EVENTS.RESPONSE/END/ERROR) -- Changing the LLMCoreStreamEvent types or provider coreStream interface -- Adding new features (permissions, retry, parallel tool exec) - -## Current Structure - -``` -agentRuntimePresenter/ - index.ts — session lifecycle only (init, destroy, getState, cancel, getMessages) - process.ts — the loop: stream → accumulate → echo → dispatch - accumulator.ts — accumulate(state, event): mutate blocks by event type - echo.ts — start/stop throttled flush to renderer + DB - dispatch.ts — executeTools() + finalize() + finalizeError() - contextBuilder.ts — (unchanged from v1) - messageStore.ts — (unchanged) - sessionStore.ts — (unchanged) - types.ts — StreamState, IoParams, ProcessParams - -src/shared/utils/ - throttle.ts — createThrottle(fn, interval): reusable throttle utility -``` - -## Architecture - -### Shared State - -All modules operate on a single mutable `StreamState` object owned by `process.ts`: - -```typescript -// types.ts -interface ToolCallResult { - id: string - name: string - arguments: string - serverName?: string - serverIcons?: string - serverDescription?: string -} - -interface StreamState { - blocks: AssistantMessageBlock[] - metadata: MessageMetadata - startTime: number - firstTokenTime: number | null - pendingToolCalls: Map - completedToolCalls: ToolCallResult[] - stopReason: 'complete' | 'tool_use' | 'error' | 'abort' | 'max_tokens' - dirty: boolean -} - -interface IoParams { - sessionId: string - messageId: string - messageStore: DeepChatMessageStore - abortSignal: AbortSignal -} - -interface ProcessParams { - io: IoParams - coreStream: (...) => AsyncGenerator - tools: MCPToolDefinition[] - toolPresenter: IToolPresenter | null - modelId: string - modelConfig: ModelConfig - temperature: number - maxTokens: number - messages: ChatMessage[] -} -``` - -### Module Responsibilities - -#### `accumulator.ts` — event → block mutation - -Pure block mutation. No DB, no renderer, no control flow decisions. - -```typescript -function accumulate(state: StreamState, event: LLMCoreStreamEvent): void -``` - -| Event | Action | -|---|---| -| `text` | Append to current content block (coalesce consecutive) | -| `reasoning` | Append to current reasoning_content block | -| `tool_call_start` | Push new tool_call block, add to pendingToolCalls map | -| `tool_call_chunk` | Append to pending args + update block params | -| `tool_call_end` | Finalize args, move to completedToolCalls, remove from pending | -| `usage` | Set metadata fields | -| `stop` | Set stopReason | -| `error` | Push error block (status `'error'`) | - -New blocks are always created with `status: 'pending'`. The accumulator never transitions existing blocks to `'success'` or `'error'` — that's `dispatch.ts`'s job. The only exception is the `error` event handler, which creates a *new* error block with `status: 'error'`. - -Sets `state.dirty = true` on any block mutation. Sets `state.firstTokenTime` on the first `text` or `reasoning` event if not already set. - -#### `echo.ts` — interval-based flush to renderer + DB - -Two `setInterval` timers drive periodic flushing. Each interval callback checks `state.dirty` before doing work. The shared `createThrottle` utility wraps each callback to prevent overlapping flushes if a write takes longer than the interval. - -```typescript -interface EchoHandle { - flush(): void // immediate flush (after tool results) - stop(): void // clear intervals + cancel pending throttles -} - -function startEcho(state: StreamState, io: IoParams): EchoHandle -``` - -- Renderer interval: 120ms — when `state.dirty`, emit `STREAM_EVENTS.RESPONSE` with deep-cloned blocks -- DB interval: 600ms — when `state.dirty`, call `messageStore.updateAssistantContent()` -- `flush()`: immediate renderer + DB write, clears `state.dirty` -- `stop()`: clears both intervals, cancels pending throttles (called in `finally` block of process) - -#### `dispatch.ts` — tool execution + finalization - -```typescript -async function executeTools( - state: StreamState, - conversation: ChatMessage[], - prevBlockCount: number, - tools: MCPToolDefinition[], - toolPresenter: IToolPresenter, - modelId: string, - io: IoParams -): Promise - -function finalize(state: StreamState, io: IoParams): void -function finalizeError(state: StreamState, io: IoParams, error: unknown): void -``` - -Three independent functions in one module. They share no internal state — all coordination goes through `StreamState` and the arguments passed in. - -`executeTools` responsibilities: -- Use `prevBlockCount` to slice `state.blocks` and extract only the current iteration's content/reasoning/tool_call blocks -- Build assistant message from current iteration blocks (content + tool_calls) -- Include `reasoning_content` for interleaved thinking models (deepseek-reasoner, kimi-k2-thinking, glm-4.7) -- Push assistant message to conversation -- For each tool call: check `abortSignal` → call `toolPresenter.callTool()` → push tool result to conversation → update tool_call block response + status -- Enrich tool_call blocks with server info from tool definitions -- Flush to renderer + DB after each tool execution -- Returns the number of tools executed -- Does NOT enforce MAX_TOOL_CALLS (that's the loop's job in `process.ts`) - -`finalize` responsibilities: -- Mark all pending blocks as `'success'` -- Compute metadata using `state.startTime` and `state.firstTokenTime` (generationTime, firstTokenTime, tokensPerSecond) -- Call `messageStore.finalizeAssistantMessage()` -- Emit `STREAM_EVENTS.RESPONSE` (final blocks) + `STREAM_EVENTS.END` - -`finalizeError` responsibilities: -- Push error block -- Mark all pending blocks as `'error'` -- Call `messageStore.setMessageError()` -- Emit `STREAM_EVENTS.ERROR` - -#### `process.ts` — the loop - -Single entry point, single code path. No tools-vs-no-tools branching. - -```typescript -async function processStream(params: ProcessParams): Promise -``` - -``` -processStream(params) - state = createState() - conversation = [...params.messages] - echo = startEcho(state, params.io) - toolCallCount = 0 - - try { - LOOP: - prevBlockCount = state.blocks.length - stream = coreStream(conversation, ...) - reset completedToolCalls + pendingToolCalls - - for await (event of stream): - if aborted → mark blocks error, setMessageError, emit ERROR, return - accumulate(state, event) - - if aborted → break LOOP - if stopReason ≠ 'tool_use' → break LOOP - if no completedToolCalls → break LOOP - if toolCallCount + completedToolCalls > MAX_TOOL_CALLS → break LOOP - - executed = executeTools(state, conversation, prevBlockCount, ...) - toolCallCount += executed - echo.flush() - - if aborted → break LOOP - - finalize(state, params.io) - catch (err): - finalizeError(state, params.io, err) - finally: - echo.stop() -``` - -MAX_TOOL_CALLS = 128. - -#### `index.ts` — session lifecycle - -`processMessage` is thin: build context, resolve provider, construct `ProcessParams`, call `processStream`. - -``` -processMessage(sessionId, content) - ├── resolve provider + model config - ├── buildContext(...) - ├── persist user message + create assistant placeholder - ├── fetch tool definitions (no conversationId — new agent doesn't use skills) - └── processStream(params) ← single call, no branching -``` - -### Throttle Utility - -```typescript -// src/shared/utils/throttle.ts -interface ThrottledFn { - (): void // invoke (throttled — skips if called within interval of last execution) - flush(): void // invoke immediately, reset interval - cancel(): void // cancel pending invocation -} - -function createThrottle(fn: () => void, interval: number): ThrottledFn -``` - -`echo.ts` uses `createThrottle` to wrap its flush callbacks, then drives them via `setInterval`. The throttle prevents overlapping flushes — if a DB write takes >600ms, the next interval tick is a no-op instead of stacking. Available for reuse elsewhere in the codebase. - -## Migration (completed) - -This was a pure refactor. External contracts unchanged: -- `IAgentImplementation` interface: unchanged -- Renderer events (`STREAM_EVENTS.RESPONSE/END/ERROR`): unchanged -- DB schema: unchanged -- Block format (`AssistantMessageBlock`): unchanged -- `contextBuilder.ts`, `messageStore.ts`, `sessionStore.ts`: unchanged - -Files created: `throttle.ts`, `types.ts`, `accumulator.ts`, `echo.ts`, `dispatch.ts`, `process.ts` -Files deleted: `streamHandler.ts`, `agentLoop.ts` -Files modified: `index.ts` - -## Acceptance Criteria - -- [x] All existing behavior preserved (tool calling, reasoning, abort, error handling) -- [x] No `initialBlocks` or conditional caller-detection in any module -- [x] Single code path in `processMessage` (no tools-vs-no-tools branch) -- [x] `streamHandler.ts` and `agentLoop.ts` deleted -- [x] Throttle utility in `src/shared/utils/` and used by `echo.ts` -- [x] All existing tests pass (updated for new module structure) -- [x] `pnpm run typecheck` passes -- [x] `pnpm run lint && pnpm run format` passes diff --git a/docs/archives/new-ui-agent-session/spec.md b/docs/archives/new-ui-agent-session/spec.md deleted file mode 100644 index 6c6f5b72e..000000000 --- a/docs/archives/new-ui-agent-session/spec.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent-Aware Session Creation - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Problem - -`NewThreadPage.onSubmit()` calls `sessionStore.createSession()` without passing `agentId`. The `createSession` action already supports `agentId` in `CreateSessionInput` and handles ACP agent mode, but it is never provided from the UI. - -## Solution - -Import `useAgentStore` in `NewThreadPage.vue` and pass the selected agent to `createSession()`. - -### Flow - -1. User selects an agent in the sidebar (sets `agentStore.selectedAgentId`) -2. User opens NewThreadPage and types a message -3. On submit, `NewThreadPage` reads `agentStore.selectedAgentId` -4. Passes `agentId` to `sessionStore.createSession()` -5. `createSession()` already handles ACP mode: - - If `agentId !== 'deepchat'`: sets `chatMode: 'acp agent'`, `acpWorkdirMap: { [agentId]: projectDir }` - - If `agentId === 'deepchat'` or undefined: standard DeepChat session - -### Key Types - -- `CreateSessionInput.agentId?: string` — from `stores/ui/session.ts` -- `agentStore.selectedAgentId: string | null` — `null` means "All Agents" filter (default to 'deepchat') - -### Files Modified - -- `src/renderer/src/pages/NewThreadPage.vue` — add `useAgentStore` import, pass `agentId` and `providerId`/`modelId` for ACP agents diff --git a/docs/archives/new-ui-agent-store/spec.md b/docs/archives/new-ui-agent-store/spec.md deleted file mode 100644 index 421f5c893..000000000 --- a/docs/archives/new-ui-agent-store/spec.md +++ /dev/null @@ -1,158 +0,0 @@ -# Agent Store Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -Agent Store manages the agent list (DeepChat built-in + ACP agents) and the sidebar agent filter selection. - -## File Location - -`src/renderer/src/stores/ui/agent.ts` - -## Type Definitions - -```typescript -interface UIAgent { - id: string // 'deepchat', 'claude-code-acp', 'codex-acp', custom id - name: string // Display name - type: 'deepchat' | 'builtin-acp' | 'custom-acp' - enabled: boolean -} -``` - -### Mapping from Presenter Types - -DeepChat agent is hardcoded. ACP agents come from two presenter calls: - -```typescript -// Built-in ACP agents (claude-code, codex, kimi) -const builtinAgents: AcpBuiltinAgent[] = await configPresenter.getAcpBuiltinAgents() - -// Custom ACP agents (user-defined) -const customAgents: AcpCustomAgent[] = await configPresenter.getAcpCustomAgents() -``` - -Mapping: - -```typescript -function mapBuiltinAgent(agent: AcpBuiltinAgent): UIAgent { - return { - id: agent.id, - name: agent.name, - type: 'builtin-acp', - enabled: agent.enabled - } -} - -function mapCustomAgent(agent: AcpCustomAgent): UIAgent { - return { - id: agent.id, - name: agent.name, - type: 'custom-acp', - enabled: agent.enabled - } -} -``` - -## Store Design - -```typescript -export const useAgentStore = defineStore('agent', () => { - const configPresenter = useLegacyPresenter('configPresenter') - - // --- State --- - const agents = ref([]) - const selectedAgentId = ref(null) // null = "All Agents" - const loading = ref(false) - const error = ref(null) - - // --- Getters --- - const enabledAgents = computed(() => agents.value.filter(a => a.enabled)) - const selectedAgent = computed(() => agents.value.find(a => a.id === selectedAgentId.value)) - const selectedAgentName = computed(() => selectedAgent.value?.name ?? 'All Agents') - - // --- Actions --- - async function fetchAgents(): Promise - function selectAgent(id: string | null): void - - return { - agents, selectedAgentId, loading, error, - enabledAgents, selectedAgent, selectedAgentName, - fetchAgents, selectAgent - } -}) -``` - -## Actions - -### `fetchAgents(): Promise` - -```typescript -async function fetchAgents() { - loading.value = true - error.value = null - try { - const deepchatAgent: UIAgent = { - id: 'deepchat', - name: 'DeepChat', - type: 'deepchat', - enabled: true // Always enabled - } - - const builtinAgents = await configPresenter.getAcpBuiltinAgents() - const customAgents = await configPresenter.getAcpCustomAgents() - - agents.value = [ - deepchatAgent, - ...builtinAgents.map(mapBuiltinAgent), - ...customAgents.map(mapCustomAgent) - ] - } catch (e) { - error.value = `Failed to load agents: ${e}` - } finally { - loading.value = false - } -} -``` - -### `selectAgent(id: string | null): void` - -Toggle agent filter. Passing the currently selected id deselects it (back to "All"). - -```typescript -function selectAgent(id: string | null) { - selectedAgentId.value = selectedAgentId.value === id ? null : id -} -``` - -## IPC Call Mapping - -| Action | Presenter Call | -|--------|---------------| -| Get built-in ACP agents | `configPresenter.getAcpBuiltinAgents()` | -| Get custom ACP agents | `configPresenter.getAcpCustomAgents()` | - -## Event Listeners - -| Event | Handler | -|-------|---------| -| `CONFIG_EVENTS.SETTING_CHANGED` | Re-fetch agents (ACP config may have changed) | - -Note: There are no dedicated ACP_EVENTS.AGENT_ADDED/REMOVED events in the codebase. Agent changes propagate through `CONFIG_EVENTS.SETTING_CHANGED`. - -## Error Handling - -Errors are caught in `fetchAgents` and stored in `error` ref. The DeepChat agent is always present even if ACP agent fetching fails. - -## Test Points - -1. DeepChat agent is always present in agent list -2. Built-in ACP agents are mapped correctly from `getAcpBuiltinAgents()` -3. Custom ACP agents are mapped correctly from `getAcpCustomAgents()` -4. Disabled agents appear in list but `enabledAgents` filters them out -5. `selectAgent` toggles selection (same id deselects) -6. `selectedAgentName` returns 'All Agents' when nothing selected -7. Error during fetch sets `error` and still includes DeepChat agent - diff --git a/docs/archives/new-ui-chat-components/spec.md b/docs/archives/new-ui-chat-components/spec.md deleted file mode 100644 index 9af0cf90e..000000000 --- a/docs/archives/new-ui-chat-components/spec.md +++ /dev/null @@ -1,217 +0,0 @@ -# Chat Components Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -Chat components handle message display, input, and status configuration during active sessions. Each component's visual design must match its mock counterpart exactly. - -## Historical Reference Map - -| Component | Historical Mock | -|-----------|-----------| -| ChatTopBar | `MockTopBar` | -| MessageList | `MockMessageList` | -| InputBox | `MockInputBox` | -| InputToolbar | `MockInputToolbar` | -| StatusBar | `MockStatusBar` | - -## File Locations - -``` -src/renderer/src/components/chat/ - ChatTopBar.vue - MessageList.vue - ChatInputBox.vue - ChatInputToolbar.vue - ChatStatusBar.vue -``` - ---- - -## 1. ChatTopBar - -**Historical mock reference**: `MockTopBar` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ [folder] project-name > Session Title [Share][...]│ -└─────────────────────────────────────────────────────┘ -``` - -**Props**: -```typescript -interface Props { - title: string - project: string -} -``` - -**Data flow**: Props passed from ChatPage, which reads from `sessionStore.activeSession`. - -**Key behavior**: -- `projectName` computed as `project.split('/').pop()` -- Sticky positioning: `sticky top-0 z-10` -- Window drag region with no-drag on buttons - ---- - -## 2. MessageList - -**Historical mock reference**: `MockMessageList` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ [User message - right-aligned] │ -│ │ -│ [Avatar] [Assistant message] │ -│ [Model name label] │ -│ [Message content] │ -└─────────────────────────────────────────────────────┘ -``` - -**Data flow**: Reads messages from the existing `useChatStore()`. - -```typescript -const chatStore = useChatStore() -const messages = computed(() => chatStore.getCurrentThreadMessages()) -``` - -**IPC call mapping**: - -| Operation | Presenter Call | -|-----------|---------------| -| Load messages | `sessionPresenter.getMessages(conversationId, page, pageSize)` | -| Load message IDs | `sessionPresenter.getMessageIds(conversationId)` | - -Note: The existing `useChatStore` already handles message fetching and caching via `STREAM_EVENTS.RESPONSE` and `STREAM_EVENTS.END`. The new MessageList component consumes from that store. - -**Key behavior**: -- User messages: right-aligned, `bg-muted rounded-2xl` -- Assistant messages: left-aligned with model icon, model name label -- Container: `max-w-3xl mx-auto px-4 py-6 space-y-6` -- Scrollable overflow - ---- - -## 3. ChatInputBox - -**Historical mock reference**: `MockInputBox` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ Ask DeepChat anything, @ to mention files... │ -│ │ -├─────────────────────────────────────────────────────┤ -│ [toolbar slot] │ -└─────────────────────────────────────────────────────┘ -``` - -**Props/Events**: -```typescript -interface Props { - modelValue?: string - placeholder?: string -} - -interface Emits { - (e: 'update:modelValue', value: string): void - (e: 'submit', message: string): void -} -``` - -**Slots**: -- `toolbar`: Slot for InputToolbar - -**Key behavior**: -- Textarea with `min-h-[80px] resize-none border-0 bg-transparent` -- Container: `rounded-xl border bg-card/30 backdrop-blur-lg shadow-sm` -- Enter to submit (shift+enter for newline) - ---- - -## 4. ChatInputToolbar - -**Historical mock reference**: `MockInputToolbar` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ [+] [mic] [send] │ -└─────────────────────────────────────────────────────┘ -``` - -**Events**: -```typescript -interface Emits { - (e: 'send'): void - (e: 'attach'): void -} -``` - -**Key behavior**: -- Attach button: `lucide:plus` icon, ghost variant -- Mic button: `lucide:mic` icon, ghost variant -- Send button: `lucide:arrow-up` icon, `rounded-full`, primary style - ---- - -## 5. ChatStatusBar - -**Historical mock reference**: `MockStatusBar` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ [ModelIcon Model ▼] [Effort ▼] [Permissions ▼] │ -└─────────────────────────────────────────────────────┘ -``` - -**Data flow**: - -| Selector | Read from | Write to | -|----------|-----------|----------| -| Model | `chatStore.chatConfig.modelId` + `modelStore` for list | `sessionPresenter.updateSessionSettings()` | -| Effort (reasoningEffort) | `chatStore.chatConfig.reasoningEffort` | `sessionPresenter.updateSessionSettings()` | -| Permissions | TBD (permission system) | TBD | - -**IPC call mapping**: - -| Operation | Presenter Call | -|-----------|---------------| -| Change model | `sessionPresenter.updateSessionSettings(sessionId, { providerId, modelId })` | -| Change effort | `sessionPresenter.updateSessionSettings(sessionId, { reasoningEffort })` | -| Get model list | `configPresenter.getProviderDb()` or existing `modelStore` | - -**Key behavior**: -- All buttons: `h-6 px-2 gap-1 text-xs text-muted-foreground` -- Model selector shows ModelIcon + model name + chevron-down -- Effort selector shows gauge icon + level + chevron-down -- Permissions selector shows shield icon + level name - ---- - -## Shared Input Flow (NewThread + Chat) - -Both NewThreadPage and ChatPage use the same InputBox + InputToolbar + StatusBar combination. The submit behavior differs: - -| Context | On Submit | -|---------|-----------| -| NewThreadPage | `sessionStore.createSession({ title, message, projectDir, ... })` | -| ChatPage | `agentPresenter.chat(sessionId, message, tabId)` | - -The parent page component handles the submit event and dispatches accordingly. - -## Test Points - -1. ChatTopBar displays project name and title correctly -2. MessageList renders user messages right-aligned and assistant messages left-aligned -3. ChatInputBox emits `submit` on Enter, `update:modelValue` on typing -4. ChatInputToolbar emits `send` on send button click -5. ChatStatusBar model dropdown shows available models -6. ChatStatusBar effort dropdown shows effort levels -7. All component styles match their mock counterparts exactly diff --git a/docs/archives/new-ui-implementation/todo.md b/docs/archives/new-ui-implementation/todo.md deleted file mode 100644 index 950e88c26..000000000 --- a/docs/archives/new-ui-implementation/todo.md +++ /dev/null @@ -1,314 +0,0 @@ -# New UI Implementation Development Tracker - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -This document tracks the development progress of new UI feature implementation. All implementations must match the mock interface exactly. - -**Architecture Design**: [new-ui-implementation-plan.md](../../architecture/new-ui-implementation-plan.md) - -## Historical Reference Map - -| Historical Mock | Purpose | Target Replacement | -|-----------|---------|-------------------| -| `MockWelcomePage` | Welcome page | `pages/WelcomePage.vue` | -| `NewThreadMock` | NewThread page | `pages/NewThreadPage.vue` | -| `MockChatPage` | Chat page | `pages/ChatPage.vue` | -| `MockTopBar` | Top bar | `components/chat/ChatTopBar.vue` | -| `MockMessageList` | Message list | `components/chat/MessageList.vue` | -| `MockInputBox` | Input box | `components/chat/ChatInputBox.vue` | -| `MockInputToolbar` | Input toolbar | `components/chat/ChatInputToolbar.vue` | -| `MockStatusBar` | Status bar | `components/chat/ChatStatusBar.vue` | -| `components/WindowSideBar.vue` | Sidebar | (refactored in place) | -| `useMockViewState` | State management | Replaced by stores | - ---- - -## Specs List - -| Spec | File | -|------|------| -| Page Router | [spec.md](../new-ui-page-state/spec.md) | -| Agent Store | [spec.md](../new-ui-agent-store/spec.md) | -| Session Store | [spec.md](../new-ui-session-store/spec.md) | -| Project Store | [spec.md](../new-ui-project-store/spec.md) | -| Sidebar Components | [spec.md](../new-ui-sidebar/spec.md) | -| Chat Components | [spec.md](../new-ui-chat-components/spec.md) | -| Page Components | [spec.md](../new-ui-pages/spec.md) | -| Agent-Aware Sessions | [spec.md](../new-ui-agent-session/spec.md) | -| Status Bar (Model/Effort) | [spec.md](../new-ui-status-bar/spec.md) | -| Markdown Rendering | [spec.md](../new-ui-markdown-rendering/spec.md) | - ---- - -## Phase 1: Store Layer - -### 1.1 Page Router Store - -- [x] Create `stores/ui/pageRouter.ts` -- [x] Define `PageRoute` type (welcome / newThread / chat) -- [x] Implement `initialize()` — check providers, check active session -- [x] Implement `goToWelcome()`, `goToNewThread()`, `goToChat(sessionId)` -- [x] Implement `currentRoute` and `chatSessionId` getters -- [x] Listen to `CONFIG_EVENTS.PROVIDER_CHANGED` -- [x] Error handling with fallback to newThread - -### 1.2 Session Store - -- [x] Create `stores/ui/session.ts` -- [x] Define `UISession` type with `resolveAgentId()` mapping -- [x] Define `mapSessionStatus()` mapping -- [x] Implement `fetchSessions()` via `sessionPresenter.getSessionList()` -- [x] Implement `createSession()` — create session + send message + navigate -- [x] Implement `selectSession()` — activate session + navigate -- [x] Implement `closeSession()` — unbind tab + navigate to newThread -- [x] Implement `groupByTime()` and `groupByProject()` grouping -- [x] Implement `getFilteredGroups(agentId)` for sidebar -- [x] Implement `toggleGroupMode()` -- [x] Listen to `CONVERSATION_EVENTS.LIST_UPDATED`, `ACTIVATED`, `DEACTIVATED` -- [x] Error handling on all async actions - -### 1.3 Agent Store - -- [x] Create `stores/ui/agent.ts` -- [x] Define `UIAgent` type -- [x] Implement `fetchAgents()` — DeepChat + `getAcpBuiltinAgents()` + `getAcpCustomAgents()` -- [x] Implement `selectAgent(id)` toggle -- [x] Implement `enabledAgents`, `selectedAgent`, `selectedAgentName` getters -- [x] Listen to `CONFIG_EVENTS.SETTING_CHANGED` -- [x] Error handling - -### 1.4 Project Store - -- [x] Create `stores/ui/project.ts` -- [x] Define `UIProject` type -- [x] Implement `deriveFromSessions(sessions)` — aggregate unique projects -- [x] Implement `selectProject(path)` -- [x] Implement `openFolderPicker()` via `devicePresenter.selectDirectory()` -- [x] Error handling - -**Acceptance Criteria**: -- [x] All stores manage state correctly -- [x] IPC calls map to correct presenter methods -- [x] Errors are caught and exposed via `error` ref -- [ ] Unit tests pass - ---- - -## Phase 2: Page Components - -### 2.1 ChatTabView Refactor - -- [x] Remove all legacy ChatView/NewThread/Mock imports -- [x] Remove `useMockViewState` usage -- [x] Route based on `pageRouter.currentRoute` -- [x] Initialize stores on mount (parallel) -- [x] Remove ArtifactDialog margin calculation (move to ChatPage if needed) - -### 2.2 WelcomePage - -- [x] Create `pages/WelcomePage.vue` -- [x] Copy exact layout/classes from `MockWelcomePage.vue` -- [x] Static provider grid (6 items) -- [x] All clicks → `windowPresenter.openOrFocusSettingsTab()` -- [x] Window drag region support - -### 2.3 NewThreadPage - -- [x] Create `pages/NewThreadPage.vue` -- [x] Copy exact layout/classes from `NewThreadMock.vue` -- [x] Project selector from `projectStore` -- [x] Integrate ChatInputBox + ChatInputToolbar -- [x] Integrate ChatStatusBar -- [x] Submit → `sessionStore.createSession()` - -### 2.4 ChatPage - -- [x] Create `pages/ChatPage.vue` -- [x] Copy exact layout/classes from `MockChatPage.vue` -- [x] Props: `sessionId` -- [x] Read session data from `sessionStore.activeSession` -- [x] Integrate ChatTopBar, MessageList, ChatInputBox, ChatInputToolbar, ChatStatusBar -- [x] Submit → `agentPresenter.sendMessage()` - -**Acceptance Criteria**: -- [x] Page layouts match mocks exactly -- [x] Page routing works correctly -- [x] No fallback to legacy ChatView - ---- - -## Phase 3: Chat Components - -### 3.1 ChatTopBar - -- [x] Create `components/chat/ChatTopBar.vue` -- [x] Copy exact layout/classes from `MockTopBar.vue` -- [x] Props: `title`, `project` -- [x] Computed: `projectName` -- [x] Share + More buttons (placeholder actions) - -### 3.2 MessageList - -- [x] Create `components/chat/MessageList.vue` -- [x] Copy exact layout/classes from `MockMessageList.vue` -- [x] Read messages via props (parent provides from store) -- [x] User message style: right-aligned, `bg-muted rounded-2xl` -- [x] Assistant message style: left with avatar + model label - -### 3.3 ChatInputBox - -- [x] Create `components/chat/ChatInputBox.vue` -- [x] Copy exact layout/classes from `MockInputBox.vue` -- [x] v-model support -- [x] Toolbar slot -- [x] Enter to submit, Shift+Enter for newline - -### 3.4 ChatInputToolbar - -- [x] Create `components/chat/ChatInputToolbar.vue` -- [x] Copy exact layout/classes from `MockInputToolbar.vue` -- [x] Attach (+), Mic, Send buttons -- [x] Emit events: `send`, `attach` - -### 3.5 ChatStatusBar - -- [x] Create `components/chat/ChatStatusBar.vue` -- [x] Copy exact layout/classes from `MockStatusBar.vue` -- [x] Model selector → placeholder with mock data (real integration deferred) -- [x] Effort selector → placeholder with mock data -- [x] Permissions selector → placeholder for now - -**Acceptance Criteria**: -- [x] All component styles match mocks exactly -- [x] Components emit correct events -- [ ] StatusBar dropdowns read/write session settings (deferred — uses placeholder data) - ---- - -## Phase 4: Sidebar Data Integration - -### 4.1 Replace Mock Data - -- [x] `mockAgents` → `agentStore.enabledAgents` -- [x] `allSessions` / `mockSessionsByDate` → `sessionStore.getFilteredGroups()` -- [x] `selectedAgentId` → `agentStore.selectedAgentId` -- [x] `groupByProject` → `sessionStore.groupMode` - -### 4.2 Replace Mock State - -- [x] Remove `useMockViewState` import -- [x] `handleNewChat` → `sessionStore.closeSession()` -- [x] `handleSessionClick` → `sessionStore.selectSession(id)` -- [x] Remove debug toggle (welcome page toggle button) - -### 4.3 Sidebar-Specific - -- [x] Keep `collapsed` as local state -- [x] `filteredGroups` computed from `sessionStore.getFilteredGroups(agentStore.selectedAgentId)` - -**Acceptance Criteria**: -- [x] Sidebar displays real session data from stores -- [x] Agent filter works end-to-end -- [x] Time/project grouping toggle works -- [x] Session click navigates to chat page - ---- - -## Phase 5: Integration and Cleanup - -### 5.1 End-to-End Flows - -- [ ] Full session creation flow: NewThread → submit → ChatPage renders with messages -- [ ] Session switching: sidebar click → ChatPage updates -- [ ] New chat: sidebar + button → NewThreadPage -- [ ] Welcome → Settings → Provider added → NewThreadPage - -### 5.2 Error Handling - -- [ ] Display `sessionStore.error` in UI -- [ ] Display `agentStore.error` in UI -- [ ] Handle presenter call failures gracefully - -### 5.3 Performance - -- [ ] Lazy load page components in ChatTabView -- [ ] Virtual scrolling for message list (if needed) - -### 5.4 Internationalization - -- [ ] Add i18n keys for all user-facing strings - -### 5.5 Cleanup - -- [x] Remove `components/mock/MockWelcomePage.vue` from the active tree -- [x] Remove `components/mock/MockChatPage.vue` from the active tree -- [x] Remove `components/mock/MockTopBar.vue` from the active tree -- [x] Remove `components/mock/MockMessageList.vue` from the active tree -- [x] Remove `components/mock/MockInputBox.vue` from the active tree -- [x] Remove `components/mock/MockInputToolbar.vue` from the active tree -- [x] Remove `components/mock/MockStatusBar.vue` from the active tree -- [x] Remove `components/NewThreadMock.vue` from the active tree -- [x] Remove `composables/useMockViewState.ts` from the active tree - ---- - -## Test Coverage - -| Module | Status | -|--------|--------| -| Page Router Store | ⬜ | -| Session Store | ⬜ | -| Agent Store | ⬜ | -| Project Store | ⬜ | -| ChatTabView routing | ⬜ | -| WelcomePage | ⬜ | -| NewThreadPage | ⬜ | -| ChatPage | ⬜ | -| ChatTopBar | ⬜ | -| MessageList | ⬜ | -| ChatInputBox | ⬜ | -| ChatStatusBar | ⬜ | -| Sidebar integration | ⬜ | - ---- - -## Phase 6: Agent-Aware Sessions, Working Settings, Markdown Rendering - -### 6.1 Agent-Aware Session Creation ([spec](../new-ui-agent-session/spec.md)) - -- [x] Import `useAgentStore` in `NewThreadPage.vue` -- [x] Pass `agentId: agentStore.selectedAgentId ?? 'deepchat'` to `sessionStore.createSession()` -- [x] Pass `providerId: 'acp'` and `modelId: agentId` when ACP agent selected -- [ ] Verify: select ACP agent → create session → session has `chatMode: 'acp agent'` - -### 6.2 Working ChatStatusBar ([spec](../new-ui-status-bar/spec.md)) - -- [x] Replace hardcoded model dropdown with `modelStore.enabledModels` data -- [x] Display current model from `chatStore.chatConfig.modelId` via `modelStore.findModelByIdOrName()` -- [x] On model select: `chatStore.updateChatConfig({ providerId, modelId })` -- [x] Replace hardcoded effort dropdown with real `reasoningEffort` / `verbosity` data -- [x] On effort select: `chatStore.updateChatConfig({ reasoningEffort: value })` -- [x] Keep permissions as read-only indicator -- [x] Add `modelStore.initialize()` to `ChatTabView.onMounted` -- [ ] Verify: model dropdown shows real enabled models -- [ ] Verify: changing model updates `chatConfig` and persists - -### 6.3 Markdown Message Rendering ([spec](../new-ui-markdown-rendering/spec.md)) - -- [x] Replace plain-text rendering with `MessageItemAssistant` / `MessageItemUser` -- [x] Remove `getUserText()`, `getAssistantText()` helpers -- [x] Remove direct `ModelIcon` and `useThemeStore` imports (handled by message components) -- [ ] Verify: messages render with full markdown, code blocks, tool calls, etc. - ---- - -## Changelog - -| Date | Update | -|------|--------| -| 2025-02-18 | v2: Rewrote all specs — page router decoupled from session, IPC mapping added, error handling, no legacy fallback | -| 2026-02-18 | v3: Phase 1–4 implemented — 4 stores, 5 chat components, 3 pages, ChatTabView refactored, sidebar integrated with stores. Typecheck/lint/format pass. Phase 5 (E2E testing, error display, i18n, mock cleanup) pending. | -| 2026-02-18 | v4: Phase 6 implemented — agent-aware session creation, working ChatStatusBar (model/effort selectors wired to chatStore + modelStore), markdown rendering via existing MessageItemAssistant/MessageItemUser components. | diff --git a/docs/archives/new-ui-markdown-rendering/spec.md b/docs/archives/new-ui-markdown-rendering/spec.md deleted file mode 100644 index 595175782..000000000 --- a/docs/archives/new-ui-markdown-rendering/spec.md +++ /dev/null @@ -1,45 +0,0 @@ -# Markdown Message Rendering - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Problem - -`MessageList.vue` manually extracts text with `getAssistantText()` and renders it in a plain `
` with `whitespace-pre-wrap`. The existing codebase has a full rendering pipeline that handles markdown, code blocks, tool calls, thinking blocks, permissions, images, and more. - -## Solution - -Replace the plain-text rendering with existing message components: - -- `MessageItemAssistant.vue` — renders all assistant block types (content/markdown, reasoning, tool_call, permission, image, audio, error, plan, question) -- `MessageItemUser.vue` — renders user content with file attachments, edit support, structured content rendering - -## Component Props - -- `MessageItemAssistant`: `:message="(msg as AssistantMessage)"`, `:is-capturing-image="false"` -- `MessageItemUser`: `:message="(msg as UserMessage)"` - -Both work with `useChatStore` internally for `getActiveThreadId()`, variant maps, etc. Since `ChatPage` already activates the session in `chatStore`, these should work. - -## Type References - -From `@shared/chat`: -- `Message` — union type with `role: 'user' | 'assistant'` -- `UserMessage` — Message with `role: 'user'`, `content: UserMessageContent` -- `AssistantMessage` — Message with `role: 'assistant'`, `content: AssistantMessageBlock[]` - -## Rendering Capabilities Unlocked - -- Markdown formatting (headers, lists, bold, italic, links) -- Syntax-highlighted code blocks -- Thinking/reasoning blocks (collapsible) -- Tool call blocks with results -- Permission request blocks -- Image and audio blocks -- Error blocks -- Variant navigation -- Message toolbar (copy, retry, delete) - -## Files Modified - -- `src/renderer/src/components/chat/MessageList.vue` — rewrite to use existing message components diff --git a/docs/archives/new-ui-page-state/spec.md b/docs/archives/new-ui-page-state/spec.md deleted file mode 100644 index 7edf607c2..000000000 --- a/docs/archives/new-ui-page-state/spec.md +++ /dev/null @@ -1,140 +0,0 @@ -# Page Router Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -The page router controls which page is displayed in the main content area. It is a pure routing mechanism — it holds no session data, no titles, no project paths. Those belong to the Session Store. - -## File Location - -`src/renderer/src/stores/ui/pageRouter.ts` - -## Route Definitions - -```typescript -type PageRoute = - | { name: 'welcome' } - | { name: 'newThread' } - | { name: 'chat'; sessionId: string } -``` - -Three routes, each with clear entry/exit conditions: - -| Route | Condition | -|-------|-----------| -| `welcome` | No enabled providers configured | -| `newThread` | Providers exist, no active session | -| `chat` | Active session selected | - -## Store Design - -```typescript -export const usePageRouterStore = defineStore('pageRouter', () => { - const route = ref({ name: 'newThread' }) - const error = ref(null) - - // --- Actions --- - - async function initialize(): Promise - function goToWelcome(): void - function goToNewThread(): void - function goToChat(sessionId: string): void - - // --- Getters --- - - const currentRoute = computed(() => route.value.name) - const chatSessionId = computed(() => - route.value.name === 'chat' ? route.value.sessionId : null - ) - - return { route, error, initialize, goToWelcome, goToNewThread, goToChat, currentRoute, chatSessionId } -}) -``` - -## Actions - -### `initialize(): Promise` - -Called once on ChatTabView mount. Determines the initial route. - -```typescript -async function initialize() { - try { - // 1. Check if any provider is enabled - const hasProviders = await configPresenter.hasEnabledProviders() - if (!hasProviders) { - route.value = { name: 'welcome' } - return - } - - // 2. Check for active session on this tab - const tabId = window.api.getWebContentsId() - const activeSession = await sessionPresenter.getActiveSession(tabId) - if (activeSession) { - route.value = { name: 'chat', sessionId: activeSession.sessionId } - return - } - - // 3. Default to new thread - route.value = { name: 'newThread' } - } catch (e) { - error.value = String(e) - route.value = { name: 'newThread' } - } -} -``` - -### `goToWelcome(): void` - -```typescript -function goToWelcome() { - route.value = { name: 'welcome' } -} -``` - -### `goToNewThread(): void` - -```typescript -function goToNewThread() { - route.value = { name: 'newThread' } -} -``` - -### `goToChat(sessionId: string): void` - -```typescript -function goToChat(sessionId: string) { - route.value = { name: 'chat', sessionId } -} -``` - -## IPC Call Mapping - -| Action | Presenter Call | -|--------|---------------| -| Check providers | `configPresenter.hasEnabledProviders()` (or check provider list length) | -| Get active session | `sessionPresenter.getActiveSession(tabId)` | - -## Event Listeners - -| Event | Handler | -|-------|---------| -| `CONFIG_EVENTS.PROVIDER_CHANGED` | Re-check provider state; if none left → `goToWelcome()` | - -## Relationship to Other Stores - -- **Page Router does NOT read session titles, project paths, or any session detail.** That data is consumed directly by components from the Session Store. -- **Session Store calls `pageRouter.goToChat(id)`** after creating or selecting a session. -- **Session Store calls `pageRouter.goToNewThread()`** after closing a session. - -## Test Points - -1. `initialize()` routes to `welcome` when no providers exist -2. `initialize()` routes to `chat` when an active session exists on this tab -3. `initialize()` routes to `newThread` as default fallback -4. `goToChat` sets route with correct sessionId -5. `goToNewThread` clears route to newThread -6. `goToWelcome` sets route to welcome -7. Error during `initialize()` falls back to `newThread` diff --git a/docs/archives/new-ui-pages/spec.md b/docs/archives/new-ui-pages/spec.md deleted file mode 100644 index fa4738571..000000000 --- a/docs/archives/new-ui-pages/spec.md +++ /dev/null @@ -1,275 +0,0 @@ -# Page Components Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -Three page components driven by the Page Router. No fallback to old ChatView — this is a full replacement. - -## Historical Reference Map - -| Page | Historical Mock | -|------|-----------| -| WelcomePage | `MockWelcomePage` | -| NewThreadPage | `NewThreadMock` | -| ChatPage | `MockChatPage` | - -## File Locations - -``` -src/renderer/src/pages/ - WelcomePage.vue - NewThreadPage.vue - ChatPage.vue -``` - ---- - -## 1. ChatTabView.vue (Refactored) - -**File**: `src/renderer/src/views/ChatTabView.vue` - -Page entry. Routes to the correct page based on Page Router state. No legacy ChatView fallback. - -```vue - - - -``` - -**IPC calls on mount**: - -| Call | Purpose | -|------|---------| -| `pageRouter.initialize()` | Determine initial route | -| `sessionStore.fetchSessions()` | Load session list for sidebar | -| `agentStore.fetchAgents()` | Load agent list for sidebar | - ---- - -## 2. WelcomePage - -**Historical mock reference**: `MockWelcomePage` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ [Logo 16x16] │ -│ │ -│ Welcome to DeepChat Agent │ -│ Connect a model provider to start build │ -│ │ -│ ┌───────┐ ┌───────┐ ┌───────┐ │ -│ │Claude │ │OpenAI │ │DeepSeek│ │ -│ └───────┘ └───────┘ └───────┘ │ -│ ┌───────┐ ┌───────┐ ┌────────┐ │ -│ │Gemini │ │Ollama │ │OpenRouter│ │ -│ └───────┘ └───────┘ └────────┘ │ -│ │ -│ Browse all providers... │ -│ │ -│ ─────────── or connect an agent ─────────── │ -│ │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ [Terminal] Set up an ACP agent │ │ -│ │ Claude Code, Codex, Kimi... │ │ -│ └─────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────┘ -``` - -**Data**: Static provider list (hardcoded, matching mock). - -**IPC**: - -| Action | Presenter Call | -|--------|---------------| -| Click any provider / "Browse all" / ACP agent | `windowPresenter.openOrFocusSettingsTab(windowId)` | - -**Key classes** (from mock): -- Container: `h-full w-full flex flex-col window-drag-region` -- Content: `flex-1 flex flex-col items-center justify-center px-6` -- Logo: `w-16 h-16` -- Title: `text-3xl font-semibold text-foreground mb-2` -- Subtitle: `text-sm text-muted-foreground text-center max-w-md mb-10` -- Provider grid: `grid grid-cols-3 gap-2 w-full max-w-sm mb-4` -- Provider button: `rounded-xl border border-border/60 bg-card/40 px-3 py-4 hover:bg-accent/50` - ---- - -## 3. NewThreadPage - -**Historical mock reference**: `NewThreadMock` (removed from repo) - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ [Logo 14x14] │ -│ │ -│ Build and explore │ -│ │ -│ [folder deepchat v] │ -│ ┌─────────────────────────────────┐ │ -│ │ Ask DeepChat anything... │ │ -│ │ │ │ -│ ├─────────────────────────────────┤ │ -│ │ [+] [mic][send] │ │ -│ └─────────────────────────────────┘ │ -│ │ -│ [Model v] [Effort v] [Permissions v] │ -└─────────────────────────────────────────────────────┘ -``` - -**Data sources**: - -| UI Element | Store | -|------------|-------| -| Project list | `projectStore.projects` | -| Selected project | `projectStore.selectedProjectName` | -| Model/Effort | `chatStore.chatConfig` or defaults from `configPresenter` | - -**Submit flow**: - -```typescript -const handleSubmit = (message: string) => { - sessionStore.createSession({ - title: message.slice(0, 50), - message, - projectDir: projectStore.selectedProject?.path, - providerId: selectedProviderId.value, - modelId: selectedModelId.value, - reasoningEffort: selectedEffort.value - }) -} -``` - -**IPC**: - -| Action | Presenter Call | -|--------|---------------| -| Open folder | `filePresenter.selectDirectory()` (via projectStore) | -| Submit message | `sessionStore.createSession()` → `sessionPresenter.createSession()` + `agentPresenter.chat()` | - ---- - -## 4. ChatPage - -**Historical mock reference**: `MockChatPage` (removed from repo) - -**Props**: -```typescript -interface Props { - sessionId: string -} -``` - -**Layout**: -``` -┌─────────────────────────────────────────────────────┐ -│ ChatTopBar (sticky top) │ -├─────────────────────────────────────────────────────┤ -│ │ -│ MessageList (scroll) │ -│ │ -├─────────────────────────────────────────────────────┤ -│ ChatInputBox + ChatInputToolbar (sticky bottom) │ -│ ChatStatusBar │ -└─────────────────────────────────────────────────────┘ -``` - -**Data sources**: - -```typescript -const sessionStore = useSessionStore() -const chatStore = useChatStore() - -const session = computed(() => sessionStore.activeSession) -const title = computed(() => session.value?.title ?? 'Chat') -const project = computed(() => session.value?.projectDir ?? '') -``` - -**Submit flow**: - -```typescript -const handleSubmit = (message: string) => { - const tabId = window.api.getWebContentsId() - agentPresenter.chat(sessionStore.activeSessionId!, message, tabId) -} -``` - -**IPC**: - -| Action | Presenter Call | -|--------|---------------| -| Send message | `agentPresenter.chat(sessionId, message, tabId)` | -| Update settings | `sessionPresenter.updateSessionSettings(sessionId, settings)` | - -**Key classes** (from mock): -- Container: `h-full overflow-y-auto` -- Input area: `sticky bottom-0 z-10 px-6 pt-3 pb-3` -- Input wrapper: `flex flex-col items-center` - ---- - -## Route Transitions - -``` -┌─────────────┐ Provider configured ┌─────────────┐ -│ Welcome │ ─────────────────────────► │ NewThread │ -└─────────────┘ └──────┬──────┘ - ▲ │ - │ │ Submit message - │ All providers │ (createSession) - │ removed ▼ - │ ┌─────────────┐ - └──────────────────────────────────── │ Chat │ - closeSession └─────────────┘ -``` - -## Test Points - -1. ChatTabView renders correct page based on `pageRouter.currentRoute` -2. All stores initialize on mount -3. WelcomePage provider grid renders 6 providers -4. WelcomePage clicks open settings tab -5. NewThreadPage project selector shows projects from store -6. NewThreadPage submit creates session and navigates to chat -7. ChatPage displays title and project from active session -8. ChatPage submit sends message via agentPresenter diff --git a/docs/archives/new-ui-project-store/spec.md b/docs/archives/new-ui-project-store/spec.md deleted file mode 100644 index 2181590aa..000000000 --- a/docs/archives/new-ui-project-store/spec.md +++ /dev/null @@ -1,133 +0,0 @@ -# Project Store Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -Project Store manages the recent projects list for the NewThread page project selector dropdown. Kept intentionally simple. - -## File Location - -`src/renderer/src/stores/ui/project.ts` - -## Type Definitions - -```typescript -interface UIProject { - name: string // Folder name (last segment of path) - path: string // Full path -} -``` - -## Store Design - -```typescript -export const useProjectStore = defineStore('project', () => { - const filePresenter = useLegacyPresenter('filePresenter') - - // --- State --- - const projects = ref([]) - const selectedProjectPath = ref(null) - const error = ref(null) - - // --- Getters --- - const selectedProject = computed(() => - projects.value.find(p => p.path === selectedProjectPath.value) - ) - const selectedProjectName = computed(() => - selectedProject.value?.name ?? 'Select project' - ) - - // --- Actions --- - function deriveFromSessions(sessions: UISession[]): void - function selectProject(path: string): void - async function openFolderPicker(): Promise - - return { - projects, selectedProjectPath, error, - selectedProject, selectedProjectName, - deriveFromSessions, selectProject, openFolderPicker - } -}) -``` - -## Actions - -### `deriveFromSessions(sessions: UISession[]): void` - -Extract unique project directories from the session list. Called by the Session Store after fetching sessions. - -```typescript -function deriveFromSessions(sessions: UISession[]) { - const seen = new Map() - for (const s of sessions) { - if (s.projectDir && !seen.has(s.projectDir)) { - seen.set(s.projectDir, { - name: s.projectDir.split('/').pop() ?? s.projectDir, - path: s.projectDir - }) - } - } - projects.value = Array.from(seen.values()) - - // Auto-select first project if nothing selected - if (!selectedProjectPath.value && projects.value.length > 0) { - selectedProjectPath.value = projects.value[0].path - } -} -``` - -### `selectProject(path: string): void` - -```typescript -function selectProject(path: string) { - selectedProjectPath.value = path -} -``` - -### `openFolderPicker(): Promise` - -Open native folder picker dialog to add a custom project. - -```typescript -async function openFolderPicker() { - try { - const result = await filePresenter.selectDirectory() - if (result) { - const name = result.split('/').pop() ?? result - // Add to list if not already present - if (!projects.value.some(p => p.path === result)) { - projects.value.unshift({ name, path: result }) - } - selectedProjectPath.value = result - } - } catch (e) { - error.value = `Failed to open folder picker: ${e}` - } -} -``` - -## IPC Call Mapping - -| Action | Presenter Call | -|--------|---------------| -| Open folder picker | `filePresenter.selectDirectory()` | - -## Data Flow - -``` -sessionStore.fetchSessions() - └── projectStore.deriveFromSessions(sessions) - └── projects list updated - └── NewThreadPage project dropdown reflects changes -``` - -## Test Points - -1. `deriveFromSessions` extracts unique projects from sessions -2. `deriveFromSessions` auto-selects first project when none selected -3. `selectProject` updates selectedProjectPath -4. `openFolderPicker` adds new project and selects it -5. Duplicate paths are not added - diff --git a/docs/archives/new-ui-session-store/spec.md b/docs/archives/new-ui-session-store/spec.md deleted file mode 100644 index d7e33fb4f..000000000 --- a/docs/archives/new-ui-session-store/spec.md +++ /dev/null @@ -1,365 +0,0 @@ -# Session Store Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -Session Store is the central owner of all session state: the session list, the active session, grouping/filtering, and session CRUD operations. It coordinates with the Page Router for navigation. - -## File Location - -`src/renderer/src/stores/ui/session.ts` - -## Type Definitions - -### UI Session (derived from presenter Session) - -The presenter returns a rich `Session` object. The UI store maps it to a flattened structure for display: - -```typescript -interface UISession { - id: string - title: string - agentId: string // Derived: see "Agent ID Resolution" below - status: UISessionStatus - projectDir: string // From session.context.agentWorkspacePath - providerId: string - modelId: string - createdAt: number - updatedAt: number -} - -type UISessionStatus = 'completed' | 'working' | 'error' | 'none' -``` - -### Agent ID Resolution - -The presenter `Session` does not have a top-level `agentId`. It is derived: - -```typescript -function resolveAgentId(session: Session): string { - // ACP agent sessions have chatMode 'acp agent' and an acpWorkdirMap - if (session.config.chatMode === 'acp agent') { - const acpMap = session.context.acpWorkdirMap - if (acpMap) { - // The first (or only) key in acpWorkdirMap is the agentId - const agentIds = Object.keys(acpMap) - if (agentIds.length > 0) return agentIds[0] - } - } - return 'deepchat' -} -``` - -### Session Status Mapping - -Map presenter `SessionStatus` to UI display status: - -```typescript -function mapSessionStatus(status: SessionStatus): UISessionStatus { - switch (status) { - case 'generating': - case 'waiting_permission': - case 'waiting_question': - return 'working' - case 'error': - return 'error' - case 'idle': - case 'paused': - return 'none' - default: - return 'none' - } -} -``` - -### Session Group - -```typescript -interface SessionGroup { - label: string // 'Today', 'Yesterday', 'Last Week', or project name - sessions: UISession[] -} - -type GroupMode = 'time' | 'project' -``` - -## Store Design - -```typescript -export const useSessionStore = defineStore('session', () => { - const sessionPresenter = useLegacyPresenter('sessionPresenter') - const pageRouter = usePageRouterStore() - - // --- State --- - const sessions = ref([]) - const activeSessionId = ref(null) - const groupMode = ref('time') - const loading = ref(false) - const error = ref(null) - - // --- Getters --- - const activeSession: ComputedRef - const sessionGroups: ComputedRef - const hasActiveSession: ComputedRef - - // --- Actions --- - async function fetchSessions(): Promise - async function createSession(params: CreateSessionInput): Promise - async function selectSession(sessionId: string): Promise - async function closeSession(): Promise - function toggleGroupMode(): void - function getFilteredGroups(agentId: string | null): SessionGroup[] - - return { - sessions, activeSessionId, groupMode, loading, error, - activeSession, sessionGroups, hasActiveSession, - fetchSessions, createSession, selectSession, closeSession, - toggleGroupMode, getFilteredGroups - } -}) -``` - -## Actions - -### `fetchSessions(): Promise` - -Load all sessions from the presenter. - -```typescript -async function fetchSessions() { - loading.value = true - error.value = null - try { - const result = await sessionPresenter.getSessionList(1, 200) - sessions.value = result.sessions.map(mapToUISession) - } catch (e) { - error.value = `Failed to load sessions: ${e}` - } finally { - loading.value = false - } -} -``` - -### `createSession(params): Promise` - -Create a new session and navigate to it. - -```typescript -interface CreateSessionInput { - title: string - message: string - projectDir?: string - providerId?: string - modelId?: string - agentId?: string // 'deepchat' or ACP agent id - reasoningEffort?: string -} - -async function createSession(params: CreateSessionInput) { - error.value = null - try { - const tabId = window.api.getWebContentsId() - const settings: Partial = {} - - if (params.providerId) settings.providerId = params.providerId - if (params.modelId) settings.modelId = params.modelId - if (params.projectDir) settings.agentWorkspacePath = params.projectDir - if (params.reasoningEffort) settings.reasoningEffort = params.reasoningEffort - - // Determine chat mode from agent - if (params.agentId && params.agentId !== 'deepchat') { - settings.chatMode = 'acp agent' - settings.acpWorkdirMap = { [params.agentId]: params.projectDir ?? null } - } - - const sessionId = await sessionPresenter.createSession({ - title: params.title || 'New Thread', - settings, - tabId - }) - - // Refresh session list and activate - await fetchSessions() - activeSessionId.value = sessionId - pageRouter.goToChat(sessionId) - - // Send the initial message - const agentPresenter = useLegacyPresenter('agentPresenter') - await agentPresenter.chat(sessionId, params.message, tabId) - } catch (e) { - error.value = `Failed to create session: ${e}` - } -} -``` - -### `selectSession(sessionId: string): Promise` - -Switch to an existing session. - -```typescript -async function selectSession(sessionId: string) { - error.value = null - try { - const tabId = window.api.getWebContentsId() - await sessionPresenter.activateSession(tabId, sessionId) - activeSessionId.value = sessionId - pageRouter.goToChat(sessionId) - } catch (e) { - error.value = `Failed to select session: ${e}` - } -} -``` - -### `closeSession(): Promise` - -Deactivate the current session and return to NewThread. - -```typescript -async function closeSession() { - error.value = null - try { - const tabId = window.api.getWebContentsId() - await sessionPresenter.unbindFromTab(tabId) - activeSessionId.value = null - pageRouter.goToNewThread() - } catch (e) { - error.value = `Failed to close session: ${e}` - } -} -``` - -### `toggleGroupMode(): void` - -```typescript -function toggleGroupMode() { - groupMode.value = groupMode.value === 'time' ? 'project' : 'time' -} -``` - -### `getFilteredGroups(agentId: string | null): SessionGroup[]` - -Returns grouped sessions, optionally filtered by agent. Used by the sidebar. - -```typescript -function getFilteredGroups(agentId: string | null): SessionGroup[] { - const grouped = groupMode.value === 'time' - ? groupByTime(sessions.value) - : groupByProject(sessions.value) - - if (agentId === null) return grouped - - return grouped - .map(group => ({ - label: group.label, - sessions: group.sessions.filter(s => s.agentId === agentId) - })) - .filter(group => group.sessions.length > 0) -} -``` - -## Getters - -```typescript -const activeSession = computed(() => - sessions.value.find(s => s.id === activeSessionId.value) -) - -const hasActiveSession = computed(() => activeSessionId.value !== null) - -const sessionGroups = computed(() => getFilteredGroups(null)) -``` - -## Grouping Logic - -### groupByTime - -```typescript -function groupByTime(sessions: UISession[]): SessionGroup[] { - const now = Date.now() - const today = startOfDay(now) - const yesterday = startOfDay(now - 86400000) - const lastWeek = startOfDay(now - 7 * 86400000) - - const groups: Record = { - 'Today': [], - 'Yesterday': [], - 'Last Week': [], - 'Older': [] - } - - for (const s of sessions) { - if (s.updatedAt >= today) groups['Today'].push(s) - else if (s.updatedAt >= yesterday) groups['Yesterday'].push(s) - else if (s.updatedAt >= lastWeek) groups['Last Week'].push(s) - else groups['Older'].push(s) - } - - return Object.entries(groups) - .filter(([, sessions]) => sessions.length > 0) - .map(([label, sessions]) => ({ label, sessions })) -} -``` - -### groupByProject - -```typescript -function groupByProject(sessions: UISession[]): SessionGroup[] { - const projectMap = new Map() - for (const session of sessions) { - const dir = session.projectDir || 'No Project' - if (!projectMap.has(dir)) projectMap.set(dir, []) - projectMap.get(dir)!.push(session) - } - return Array.from(projectMap.entries()).map(([dir, sessions]) => ({ - label: dir.split('/').pop() ?? dir, - sessions - })) -} -``` - -## IPC Call Mapping - -| Action | Presenter Call | -|--------|---------------| -| Fetch sessions | `sessionPresenter.getSessionList(page, pageSize)` | -| Create session | `sessionPresenter.createSession({ title, settings, tabId })` | -| Activate session | `sessionPresenter.activateSession(tabId, sessionId)` | -| Deactivate session | `sessionPresenter.unbindFromTab(tabId)` | -| Send message | `agentPresenter.chat(sessionId, message, tabId)` | -| Get active session | `sessionPresenter.getActiveSession(tabId)` | - -## Event Listeners - -| Event | Handler | -|-------|---------| -| `CONVERSATION_EVENTS.LIST_UPDATED` | Call `fetchSessions()` to refresh list | -| `CONVERSATION_EVENTS.ACTIVATED` | Update `activeSessionId` | -| `CONVERSATION_EVENTS.DEACTIVATED` | Clear `activeSessionId`, `goToNewThread()` | - -## Error Handling - -All async actions catch errors and set `error` ref. Components can display errors via: - -```vue -
- {{ sessionStore.error }} -
-``` - -The `error` state is cleared at the start of each action. - -## Test Points - -1. `fetchSessions` maps presenter Sessions to UISession correctly -2. `resolveAgentId` returns 'deepchat' for agent-mode sessions -3. `resolveAgentId` returns ACP agent id for acp-agent-mode sessions -4. `createSession` creates session, refreshes list, navigates to chat -5. `selectSession` activates session and navigates to chat -6. `closeSession` unbinds tab and navigates to newThread -7. `groupByTime` correctly buckets sessions into Today/Yesterday/Last Week/Older -8. `groupByProject` correctly groups by projectDir -9. `getFilteredGroups` filters by agentId when provided -10. Error states are set on failure and cleared on retry - diff --git a/docs/archives/new-ui-sidebar/spec.md b/docs/archives/new-ui-sidebar/spec.md deleted file mode 100644 index 8f47a8e7d..000000000 --- a/docs/archives/new-ui-sidebar/spec.md +++ /dev/null @@ -1,206 +0,0 @@ -# Sidebar Component Spec - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Overview - -The sidebar displays agent filter icons, session list with grouping, and quick action buttons. All data comes from stores — no mock data. - -## File Location - -`src/renderer/src/components/WindowSideBar.vue` - -## Visual Design (must match mock exactly) - -``` -┌─────────────────────────────────────────────────────┐ -│ Agent Icons (48px) │ Session List (240px) │ -│ ┌─────────────────┐ │ ┌───────────────────────────┐ │ -│ │ [All Agents] │ │ │ Header: Agent Name │ │ -│ │ ──────────── │ │ │ [Group] [+ New] │ │ -│ │ [DeepChat] │ │ ├───────────────────────────┤ │ -│ │ [Claude Code] │ │ │ Today │ │ -│ │ [Codex] │ │ │ - Fix login bug [v] │ │ -│ │ [Kimi] │ │ │ - Refactor auth [~] │ │ -│ │ [My Bot] │ │ ├───────────────────────────┤ │ -│ │ │ │ │ Yesterday │ │ -│ │ │ │ │ - Add dark mode │ │ -│ │ ──────────── │ │ │ - API integration [!] │ │ -│ │ [Collapse] │ │ └───────────────────────────┘ │ -│ │ [Browser] │ │ │ -│ │ [Settings] │ │ │ -│ └─────────────────┘ │ │ -└─────────────────────────────────────────────────────┘ -``` - -### Widths - -- Expanded: `w-[288px]` (48px agent column + 240px session column) -- Collapsed: `w-12` (agent column only) - -## Data Sources (all from stores) - -| UI Element | Store Source | -|------------|-------------| -| Agent icon list | `agentStore.enabledAgents` | -| Selected agent | `agentStore.selectedAgentId` | -| Agent name in header | `agentStore.selectedAgentName` | -| Session groups | `sessionStore.getFilteredGroups(agentStore.selectedAgentId)` | -| Active session | `sessionStore.activeSessionId` | -| Group mode toggle | `sessionStore.groupMode` | - -## Local State - -Only UI-specific state is local to the component: - -```typescript -const collapsed = ref(false) -``` - -Everything else comes from stores. - -## Component Implementation - -### Agent Icons Column - -```vue -
- - - -
- - - - -
-
- - - - - -
-``` - -### Session List Column - -```vue -
- -
- {{ agentStore.selectedAgentName }} -
- - -
-
- - -
- -
- -

No conversations yet

-

Start a new chat to begin

-
- - - -
-
-``` - -## Computed Properties - -```typescript -const filteredGroups = computed(() => - sessionStore.getFilteredGroups(agentStore.selectedAgentId) -) -``` - -## Event Handlers - -```typescript -const handleNewChat = () => { - sessionStore.closeSession() - // closeSession internally calls pageRouter.goToNewThread() -} - -const handleSessionClick = (session: UISession) => { - sessionStore.selectSession(session.id) - // selectSession internally calls pageRouter.goToChat(id) -} - -const openSettings = () => { - const windowId = window.api.getWindowId() - if (windowId != null) { - windowPresenter.openOrFocusSettingsTab(windowId) - } -} - -const onBrowserClick = async () => { - try { - await yoBrowserPresenter.show(true) - } catch (e) { - console.warn('Failed to open browser window.', e) - } -} -``` - -## Styling Reference - -All CSS classes must match the existing `WindowSideBar.vue` mock implementation exactly. Key classes: - -- Agent button selected: `bg-card/50 border-white/70 dark:border-white/20 ring-1 ring-black/10` -- Agent button default: `bg-transparent border-none hover:bg-white/30 dark:hover:bg-white/10` -- Session item active: `bg-accent text-accent-foreground` -- Session item hover: `text-foreground/80 hover:bg-accent/50` -- Status working: `text-primary animate-spin` (loader-2 icon) -- Status completed: `text-green-500` (check icon) -- Status error: `text-destructive` (alert-circle icon) -- Window drag region: `-webkit-app-region: drag` on container, `no-drag` on buttons - -## Test Points - -1. Agent icons render from `agentStore.enabledAgents` -2. Clicking agent icon calls `agentStore.selectAgent(id)` -3. Session list renders from `sessionStore.getFilteredGroups()` -4. Clicking session calls `sessionStore.selectSession(id)` -5. New Chat button calls `sessionStore.closeSession()` -6. Group toggle calls `sessionStore.toggleGroupMode()` -7. Collapse toggle hides session column -8. Empty state shows when no sessions match filter -9. Status indicators display correctly per session status diff --git a/docs/archives/new-ui-status-bar/spec.md b/docs/archives/new-ui-status-bar/spec.md deleted file mode 100644 index feb68e352..000000000 --- a/docs/archives/new-ui-status-bar/spec.md +++ /dev/null @@ -1,42 +0,0 @@ -# Working ChatStatusBar (Model + Effort Selectors) - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Problem - -ChatStatusBar shows hardcoded "Claude 4 Sonnet" / "High" / "Default permissions". It needs to read from and write to the active session's config via `chatStore.chatConfig` and `chatStore.updateChatConfig()`. - -## Model Selector - -- **Read**: `chatStore.chatConfig.providerId` + `chatStore.chatConfig.modelId` -- **Display**: Resolve model name via `modelStore.findModelByIdOrName(modelId)` -- **List**: Flatten `modelStore.enabledModels` (array of `{ providerId, models[] }`) -- **Write**: `chatStore.updateChatConfig({ providerId, modelId })` on selection - -## Effort Selector - -- **Read**: `chatStore.chatConfig.reasoningEffort` (Anthropic/others), `chatStore.chatConfig.thinkingBudget` (Google), `chatStore.chatConfig.verbosity` (OpenAI) -- **Display**: Unified effort label from the provider-appropriate field -- **Options**: Low, Medium, High (map to `reasoningEffort` values) -- **Write**: `chatStore.updateChatConfig({ reasoningEffort: value })` — the backend normalizes per provider - -### Effort Type Reference - -From `CONVERSATION_SETTINGS`: -- `reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high'` -- `verbosity?: 'low' | 'medium' | 'high'` -- `thinkingBudget?: number` - -## Permissions Indicator - -Stream-driven (permissions are requested per tool call). Kept as read-only indicator. Actual permission handling is done by `MessageBlockPermissionRequest` in the message list. - -## Initialization - -`modelStore.initialize()` must be called during `ChatTabView.onMounted` to ensure models are loaded before the status bar renders. - -## Files Modified - -- `src/renderer/src/components/chat/ChatStatusBar.vue` — major rewrite with real data -- `src/renderer/src/views/ChatTabView.vue` — add `modelStore.initialize()` to onMounted diff --git a/docs/archives/provider-layer-simplification/plan.md b/docs/archives/provider-layer-simplification/plan.md deleted file mode 100644 index a9f6ab837..000000000 --- a/docs/archives/provider-layer-simplification/plan.md +++ /dev/null @@ -1,18 +0,0 @@ -# Provider Layer Simplification Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -1. Add a provider definition registry that captures runtime kind, model source, health check, and - hook strategy per provider. -2. Introduce a generic `AiSdkProvider` that owns AI SDK-backed text, stream, summary, image, - embeddings, and provider check flows. -3. Switch `ProviderInstanceManager` from vendor constructor maps to: - - special providers for `acp`, `github-copilot`, `voiceai`, `ollama` - - generic `AiSdkProvider` for all other AI SDK-backed providers -4. Move ModelScope MCP sync HTTP logic into shared helpers so `ModelscopeProvider` is no longer - required. -5. Adapt provider tests to assert behavior through the generic provider instead of vendor classes. -6. Delete obsolete vendor provider classes after import scans confirm they have no remaining - callers. -7. Run format, i18n, lint, typecheck, and targeted provider tests. diff --git a/docs/archives/provider-layer-simplification/spec.md b/docs/archives/provider-layer-simplification/spec.md deleted file mode 100644 index b96b57f57..000000000 --- a/docs/archives/provider-layer-simplification/spec.md +++ /dev/null @@ -1,70 +0,0 @@ -# Provider Layer Simplification - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## Status - -Completed on `2026-04-11`. - -## Goal - -Collapse the AI SDK-backed provider layer into one internal implementation while keeping all -user-visible provider contracts unchanged. - -The simplified structure is: - -- registry-driven provider resolution -- one generic `AiSdkProvider` for AI SDK-backed providers -- special-case providers kept only when they own non-generic responsibilities - -## In Scope - -- replace vendor class selection in `ProviderInstanceManager` with a registry lookup -- move runtime choice, routing, model source, health-check strategy, and provider-specific hooks - into provider definitions -- keep `AcpProvider`, `GithubCopilotProvider`, `VoiceAIProvider`, and `OllamaProvider` as - independent classes -- keep `AiSdkProvider` as the single generic implementation for AI SDK-backed providers -- move ModelScope MCP sync helpers out of `ModelscopeProvider` -- delete obsolete vendor provider classes once they have no remaining callers - -## Out of Scope - -- removing providers from the settings UI or changing the default provider list -- changing persisted provider IDs, model configs, or conversation history -- refactoring `OllamaProvider` local model management into a different subsystem -- redesigning prompts or harmonizing provider-specific output behavior - -## Compatibility Constraints - -The following must remain stable: - -- `providerId` -- `apiType` -- provider configuration schema -- model configuration schema -- history and `function_call_record` compatibility -- `LLMProviderPresenter.getProviderInstance()` behavior -- `LLMCoreStreamEvent` names and payload structure - -## Result - -After this simplification: - -- `ProviderInstanceManager` acts as a registry-backed factory -- AI SDK-backed providers no longer rely on vendor-specific provider classes -- `providers/` retains only: - - `acpProvider.ts` - - `aiSdkProvider.ts` - - `githubCopilotProvider.ts` - - `ollamaProvider.ts` - - `voiceAIProvider.ts` - -## Acceptance Criteria - -- registry resolution honors `providerId` first and `apiType` second -- routing providers (`new-api`, `zenmux`, `grok`) continue to route by model capability -- ModelScope MCP sync works without `ModelscopeProvider` instance methods -- no remaining runtime imports of deleted vendor provider classes -- targeted provider tests pass on the generic path diff --git a/docs/archives/provider-layer-simplification/tasks.md b/docs/archives/provider-layer-simplification/tasks.md deleted file mode 100644 index c390afe04..000000000 --- a/docs/archives/provider-layer-simplification/tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# Provider Layer Simplification Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -- [x] Add `providerRegistry.ts` as the single source of AI SDK-backed provider definitions. -- [x] Add generic `AiSdkProvider` and move shared provider behavior into it. -- [x] Route `ProviderInstanceManager` through registry definitions plus special providers. -- [x] Decouple ModelScope MCP sync from `ModelscopeProvider` instance methods. -- [x] Update provider-layer tests to target the generic provider behavior. -- [x] Delete obsolete vendor provider classes from `src/main/presenter/llmProviderPresenter/providers/`. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. -- [x] Run `pnpm run typecheck`. -- [x] Run targeted provider-layer tests. diff --git a/docs/archives/remove-chat-mode/plan.md b/docs/archives/remove-chat-mode/plan.md deleted file mode 100644 index 208734d1c..000000000 --- a/docs/archives/remove-chat-mode/plan.md +++ /dev/null @@ -1,288 +0,0 @@ -# 移除 Chat 模式实施计划 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 当前实现基线 - -### 1.1 模式类型定义 - -```typescript -// src/renderer/src/components/chat-input/composables/useChatMode.ts:9 -export type ChatMode = 'chat' | 'agent' | 'acp agent' -``` - -共享类型分布在: -- `src/shared/types/presenters/thread.presenter.d.ts:24` -- `src/shared/types/presenters/session.presenter.d.ts:24,51` -- `src/main/presenter/sessionPresenter/types.ts:14,42` - -### 1.2 Chat 模式特点 - -1. **无 Agent 工具** - `toolPresenter/index.ts:100` 判断 `chatMode !== 'chat'` 才加载 -2. **支持 Web 搜索** - `ChatInput.vue:583` 的 `canUseWebSearch` 仅 chat 模式可用 -3. **简单 LLM 对话** - 无工具调用循环 - -### 1.3 Web 搜索架构 - -``` -Main Process: -├── searchPresenter/ -│ ├── index.ts - ISearchPresenter 实现 -│ ├── interface.ts - 搜索接口定义 -│ ├── managers/ -│ │ └── searchManager.ts - 搜索逻辑 -│ └── handlers/ -│ ├── baseHandler.ts - 基础处理器 -│ └── searchHandler.ts - 搜索处理器 - -Renderer Process: -├── components/ -│ ├── SearchStatusIndicator.vue -│ ├── SearchResultsDrawer.vue -│ └── message/MessageBlockSearch.vue -├── stores/ -│ ├── searchAssistantStore.ts -│ ├── searchEngineStore.ts -│ └── reference.ts - -Shared: -└── types/presenters/search.presenter.d.ts -``` - -### 1.4 涉及文件统计 - -| 类别 | 数量 | -|------|------| -| 待删除文件 | 11个 | -| 待修改文件 | ~25个 | -| 类型定义修改 | 7处 | -| i18n 文件 | 12个 | - -## 2. 设计决策 - -### 2.1 类型简化 - -**决策**:`ChatMode` 类型从三元改为二元 - -```typescript -// Before -export type ChatMode = 'chat' | 'agent' | 'acp agent' - -// After -export type ChatMode = 'agent' | 'acp agent' -``` - -**影响范围**: -- 7处类型定义 -- 所有 `chatMode === 'chat'` 判断改为始终加载 agent 能力 -- 默认值统一改为 `'agent'` - -### 2.2 旧数据迁移策略 - -**决策**:静默升级,无通知 - -```typescript -// sessionResolver.ts -if (settings.chatMode === 'chat') { - settings.chatMode = 'agent' - // 不持久化到数据库(运行时升级即可) - // 如果后续有 updateConversationSettings 调用会自动保存 -} -``` - -**原则**: -1. 不破坏数据库结构 -2. 不显示任何 toast/notification -3. 用户无感知 - -### 2.3 搜索功能移除策略 - -**决策**:完全删除,不保留任何代码 - -**删除清单**: -1. `searchPresenter/` 整个目录(5个文件) -2. 搜索相关组件(3个 Vue 文件) -3. 搜索相关 stores(3个文件) -4. 搜索类型定义(1个文件) - -**配置清理**: -1. `enableSearch`、`forcedSearch`、`searchStrategy` 从 CONVERSATION_SETTINGS 移除 -2. `searchPreviewEnabled`、`customSearchEngines` 从 configPresenter 移除 -3. 搜索相关方法从 configPresenter 移除 - -### 2.4 工具加载简化 - -**决策**:移除 chat 模式判断,始终加载 Agent 工具 - -```typescript -// toolPresenter/index.ts - Before -if (chatMode !== 'chat') { - // Load Agent tools -} - -// After -// Always load Agent tools (no condition) -``` - -### 2.5 UI 简化 - -**决策**: -1. 模式选择器只显示 Agent / ACP Agent 两个选项 -2. 移除 web 搜索按钮 -3. 移除 `variant === 'chat'` 的特殊样式 - -## 3. 实施阶段 - -### Phase 1:删除搜索 Presenter(影响最小) - -1. 删除 `src/main/presenter/searchPresenter/` 目录 -2. 删除 `src/shared/types/presenters/search.presenter.d.ts` -3. 从 `src/main/presenter/index.ts` 移除 searchPresenter 注册 -4. 从 `src/preload/index.ts` 移除 searchPresenter 暴露 -5. 运行 typecheck 确认编译通过 - -### Phase 2:删除搜索组件和 Stores - -1. 删除 `SearchStatusIndicator.vue` -2. 删除 `SearchResultsDrawer.vue` -3. 删除 `MessageBlockSearch.vue` -4. 删除 `searchAssistantStore.ts` -5. 删除 `searchEngineStore.ts` -6. 删除 `reference.ts` -7. 从 `MessageItemAssistant.vue` 移除 MessageBlockSearch 引用 - -### Phase 3:修改类型定义 - -1. 更新 7 处 `ChatMode` 类型定义 -2. 更新 `useChatMode.ts`: - - 移除 chat 图标 - - 移除 modes 数组中的 chat 选项 - - 默认值改为 `'agent'` - -### Phase 4:核心逻辑修改 - -1. `sessionResolver.ts` - 添加旧数据升级逻辑 -2. `sessionManager.ts` - fallback 默认值改为 `'agent'` -3. `toolPresenter/index.ts` - 移除 chat 模式判断 -4. `mcpPresenter/toolManager.ts` - fallback 默认值改为 `'agent'` - -### Phase 5:清理搜索配置 - -1. `CONVERSATION_SETTINGS` 类型移除 `enableSearch`、`forcedSearch`、`searchStrategy` -2. `chat.ts` store 移除相关配置 -3. `modelStore.ts` 移除 `enableSearch` -4. `configPresenter` 移除搜索配置和方法 -5. `ChatInput.vue` 移除 web 搜索按钮和 `canUseWebSearch` -6. `MessageBlockQuestionRequest.vue` 移除搜索相关代码 - -### Phase 6:国际化清理 - -1. 所有 `chat.json` 文件移除 `mode.chat` -2. 所有 `chat.json` 文件移除 `features.webSearch` -3. 所有 `chat.json` 文件移除 `search` 块 - -### Phase 7:测试与验证 - -1. 更新相关测试用例 -2. 删除搜索相关测试文件 -3. 运行完整测试套件 - -## 4. 数据与配置影响 - -### 4.1 数据库 - -| 影响项 | 处理方式 | -|--------|----------| -| `conversations.settings.chatMode` | 保留字段,运行时升级 `'chat'` → `'agent'` | -| `conversations.settings.enableSearch` | 保留字段,运行时忽略 | -| `conversations.settings.forcedSearch` | 保留字段,运行时忽略 | -| `conversations.settings.searchStrategy` | 保留字段,运行时忽略 | - -**结论**:无 schema 迁移,保持向后兼容 - -### 4.2 用户配置 - -| 配置项 | 处理方式 | -|--------|----------| -| `input_chatMode` | 运行时升级 `'chat'` → `'agent'` | -| `searchPreviewEnabled` | 删除(不再使用) | -| `customSearchEngines` | 删除(不再使用) | - -### 4.3 破坏性变化 - -| 变化 | 影响范围 | -|------|----------| -| Chat 模式不可用 | 所有用户 | -| Web 搜索功能移除 | Chat 模式用户 | -| 搜索引擎配置丢失 | 配置过搜索引擎的用户 | - -## 5. 测试策略 - -### 5.1 单元测试 - -1. **useChatMode** - - 默认模式为 `'agent'` - - modes 数组不包含 `'chat'` - - 设置 `'chat'` 模式被忽略或转为 `'agent'` - -2. **sessionResolver** - - `chatMode === 'chat'` 时自动升级为 `'agent'` - -3. **toolPresenter** - - Agent 工具始终加载 - -### 5.2 集成测试 - -1. **旧对话打开** - - chat 模式的对话可以正常打开 - - 自动升级为 agent 模式 - - 无错误或警告 - -2. **新对话创建** - - 默认为 agent 模式 - - 工具可正常调用 - -### 5.3 删除的测试文件 - -``` -test/main/presenter/searchPresenter/ (如果存在) -test/renderer/components/message/MessageBlockSearch.test.ts (如果存在) -``` - -## 6. 风险与缓解 - -| 风险 | 影响 | 缓解措施 | -|------|------|----------| -| 旧对话打开失败 | 高 | 升级逻辑充分测试,添加 fallback | -| 残留搜索引用 | 中 | 全局搜索 `search`、`Search`、`enableSearch` 等 | -| i18n key 丢失 | 低 | 检查运行时是否有 missing key 警告 | -| 配置迁移失败 | 低 | 运行时升级,不依赖持久化 | - -## 7. 质量门槛 - -```bash -# 代码格式 -pnpm run format - -# 代码检查 -pnpm run lint - -# 类型检查 -pnpm run typecheck - -# 测试 -pnpm test -``` - -## 8. 实施检查清单 - -- [ ] Phase 1: 删除搜索 Presenter -- [ ] Phase 2: 删除搜索组件和 Stores -- [ ] Phase 3: 修改类型定义 -- [ ] Phase 4: 核心逻辑修改 -- [ ] Phase 5: 清理搜索配置 -- [ ] Phase 6: 国际化清理 -- [ ] Phase 7: 测试与验证 -- [ ] 运行 `pnpm run format && pnpm run lint && pnpm run typecheck` -- [ ] 运行 `pnpm test` diff --git a/docs/archives/remove-chat-mode/spec.md b/docs/archives/remove-chat-mode/spec.md deleted file mode 100644 index 6546e846e..000000000 --- a/docs/archives/remove-chat-mode/spec.md +++ /dev/null @@ -1,179 +0,0 @@ -# 移除 Chat 模式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 概述 - -移除 DeepChat 中的 "chat" 模式,仅保留 "agent" 和 "acp agent" 两种模式。同时完全移除 Web 搜索功能(该功能仅在 chat 模式下可用)。旧的 chat 模式对话将静默升级为 agent 模式。 - -## 背景与动机 - -1. Agent 模式已成熟完善,内置工具调用能力已成为核心特性 -2. Chat 模式功能有限(无工具调用),与 Agent 模式形成冗余 -3. Web 搜索功能仅 Chat 模式可用,用户实际使用率低 -4. 简化模式选择,降低用户决策成本 - -## 用户故事 - -### US-1:模式选择简化 - -作为 DeepChat 用户,我希望不再需要在 chat 和 agent 模式之间选择,直接使用功能更强大的 agent 模式。 - -### US-2:旧对话无缝迁移 - -作为已有 chat 模式对话的用户,我希望打开旧对话时无需任何操作,系统能自动将其升级为 agent 模式并继续正常使用。 - -### US-3:一致的 Agent 体验 - -作为 DeepChat 用户,我希望所有对话都具备完整的工具调用能力,无需关心"这个对话是否支持某个功能"。 - -## 验收标准 - -### A. 类型定义 - -- [ ] `ChatMode` 类型改为 `'agent' | 'acp agent'` -- [ ] 所有涉及 `chatMode` 的类型定义更新 -- [ ] 默认值从 `'chat'` 改为 `'agent'` - -### B. 模式选择 UI - -- [ ] 模式选择器仅显示 "Agent" 和 "ACP Agent" 两个选项 -- [ ] 移除 `MODE_ICONS` 中的 chat 图标 -- [ ] `useChatMode.ts` 的 `modes` 数组不再包含 chat 选项 - -### C. Web 搜索功能移除 - -- [ ] 删除 `src/main/presenter/searchPresenter/` 整个目录 -- [ ] 删除 `src/renderer/src/components/SearchStatusIndicator.vue` -- [ ] 删除 `src/renderer/src/components/SearchResultsDrawer.vue` -- [ ] 删除 `src/renderer/src/components/message/MessageBlockSearch.vue` -- [ ] 删除 `src/renderer/src/stores/searchAssistantStore.ts` -- [ ] 删除 `src/renderer/src/stores/searchEngineStore.ts` -- [ ] 删除 `src/renderer/src/stores/reference.ts` -- [ ] 删除 `src/shared/types/presenters/search.presenter.d.ts` -- [ ] `ChatInput.vue` 移除 web 搜索按钮和相关逻辑 -- [ ] `MessageBlockQuestionRequest.vue` 移除搜索相关代码 - -### D. 配置清理 - -- [ ] `CONVERSATION_SETTINGS` 移除 `enableSearch`、`forcedSearch`、`searchStrategy` -- [ ] `chat.ts` store 移除搜索相关配置 -- [ ] `modelStore.ts` 移除 `enableSearch` 属性 -- [ ] `configPresenter` 移除 `searchPreviewEnabled`、`customSearchEngines` 等配置 - -### E. 工具加载逻辑 - -- [ ] `toolPresenter/index.ts` 移除 `chatMode !== 'chat'` 判断 -- [ ] Agent 工具始终加载(无需模式判断) - -### F. 旧数据迁移 - -- [ ] `sessionResolver.ts` 检测 `chatMode === 'chat'` 时静默升级为 `'agent'` -- [ ] 升级过程无任何用户通知或中断 - -### G. Presenter 注册清理 - -- [ ] `src/main/presenter/index.ts` 移除 searchPresenter 注册 -- [ ] `src/preload/index.ts` 移除 searchPresenter 暴露 -- [ ] `src/shared/types/presenters/index.d.ts` 移除 ISearchPresenter 引用 - -### H. 国际化 - -- [ ] 所有语言文件移除 `mode.chat` 翻译 -- [ ] 所有语言文件移除 `features.webSearch` 翻译 -- [ ] 所有语言文件移除 `search` 块 - -### I. 消息组件 - -- [ ] `MessageItemAssistant.vue` 移除 `MessageBlockSearch` 组件引用 - -## 非目标 - -1. **不修改数据库 schema** - `chatMode` 字段保留,仅运行时逻辑改变 -2. **不保留任何 chat 模式兼容代码** - 彻底移除,不做 fallback -3. **不保留搜索功能给 agent 模式使用** - 完全移除 -4. **不修改 MCP 相关工具** - 仅移除 DeepChat 内置搜索 - -## 约束 - -1. **数据迁移必须静默** - 不显示任何 toast 或通知 -2. **向后兼容** - 旧对话数据不丢失,仅升级模式标记 -3. **测试覆盖** - 关键路径必须有测试验证 -4. **代码格式** - 必须通过 `format`、`lint`、`typecheck` - -## 待删除文件清单 - -### Main 进程 - -``` -src/main/presenter/searchPresenter/ -├── index.ts -├── interface.ts -├── managers/searchManager.ts -└── handlers/ - ├── baseHandler.ts - └── searchHandler.ts -``` - -### Renderer 进程 - -``` -src/renderer/src/components/SearchStatusIndicator.vue -src/renderer/src/components/SearchResultsDrawer.vue -src/renderer/src/components/message/MessageBlockSearch.vue -src/renderer/src/stores/searchAssistantStore.ts -src/renderer/src/stores/searchEngineStore.ts -src/renderer/src/stores/reference.ts -``` - -### Shared - -``` -src/shared/types/presenters/search.presenter.d.ts -``` - -## 待修改文件清单 - -### 类型定义 (7处) - -| 文件 | 修改内容 | -|------|----------| -| `src/shared/types/presenters/thread.presenter.d.ts:24` | `chatMode?: 'agent' \| 'acp agent'` | -| `src/shared/types/presenters/session.presenter.d.ts:24,51` | 同上 | -| `src/shared/types/presenters/tool.presenter.d.ts:19` | 同上 | -| `src/shared/types/presenters/legacy.presenters.d.ts:1091` | 同上 | -| `src/main/presenter/sessionPresenter/types.ts:14,42` | 同上 | -| `src/renderer/src/components/chat-input/composables/useChatMode.ts:9` | 同上 | -| `src/renderer/src/components/chat-input/composables/useWorkspaceMention.ts:9` | 同上 | - -### 核心逻辑 - -| 文件 | 修改内容 | -|------|----------| -| `useChatMode.ts` | 移除 chat 模式选项,默认改为 agent | -| `sessionResolver.ts` | 添加旧数据升级逻辑 | -| `sessionManager.ts` | fallback 默认值改为 agent | -| `toolPresenter/index.ts` | 移除 chat 模式判断 | -| `chat.ts` store | 移除搜索配置 | -| `modelStore.ts` | 移除 enableSearch | -| `configPresenter/index.ts` | 移除搜索配置和方法 | - -### UI 组件 - -| 文件 | 修改内容 | -|------|----------| -| `ChatInput.vue` | 移除 web 搜索按钮、canUseWebSearch | -| `MessageBlockQuestionRequest.vue` | 移除搜索相关代码 | -| `MessageItemAssistant.vue` | 移除 MessageBlockSearch 引用 | - -### i18n (12个文件) - -- `src/renderer/src/i18n/*/chat.json` - 移除 mode.chat、features.webSearch、search 块 - -## 开放问题 - -无。所有问题已澄清: - -- **搜索功能处理**:完全移除 -- **旧数据升级通知**:静默升级 diff --git a/docs/archives/skills-system/design.md b/docs/archives/skills-system/design.md deleted file mode 100644 index 9369f415a..000000000 --- a/docs/archives/skills-system/design.md +++ /dev/null @@ -1,647 +0,0 @@ -# DeepChat Skills 系统设计文档 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 概述 - -### 1.1 核心理念 - -Skills 是一个**文件体系**,为 AI Agent 提供可激活的专业知识和行为指导。 - -**关键特征**: -- **文件驱动**:Skills 以文件形式存在于文件系统中,用户在设置中管理 -- **模型自主**:由 LLM 通过工具决定何时激活/停用 Skill -- **渐进加载**:Metadata 始终在 Context,完整内容仅激活后加载 -- **工具复用**:Skill 内的 scripts/references 通过现有工具(Read/Bash)访问 -- **热加载**:监控 Skill 文件变化,自动更新 Metadata - -### 1.2 工作流程 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 1. 启动时:扫描文件系统,提取所有 Skill 的 Metadata │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 2. 每轮对话:Metadata 列表始终存在于 Context │ -│ "Available skills: code-review, refactor, ..." │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 3. 模型决策:根据用户意图,调用 skill_control 工具 │ -│ skill_control({ action: "activate", skill_name: "..." })│ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 4. 下一轮 Loop:加载已激活 Skill 的完整 SKILL.md 内容 │ -│ 注入到系统提示中 │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 5. 模型执行:根据 SKILL.md 指令,用现有工具访问资源 │ -│ - Read 工具读取 references/ │ -│ - Bash 工具执行 scripts/ │ -└─────────────────────────────────────────────────────────────┘ -``` - -### 1.3 设计目标 - -| 目标 | 描述 | 优先级 | -|------|------|--------| -| **文件体系** | Skills 以文件夹形式存在,用户在设置中管理 | P0 | -| **模型自主激活** | LLM 通过工具控制 Skill 激活状态 | P0 | -| **渐进加载** | Metadata 常驻,完整内容按需加载 | P0 | -| **热加载** | 监控文件变化,自动更新 Metadata | P0 | -| **工具扩展** | Skill 可声明额外需要的工具 | P1 | - -### 1.4 非目标 - -- Skill 市场和在线分发 -- Skill 版本管理和依赖解析 -- 多 Skill 编排和工作流 -- 与 Custom Prompts 合并 -- 项目级/用户级分层(当前仅用户级) - ---- - -## 2. 文件体系 - -### 2.1 目录结构 - -``` -~/.deepchat/skills/ # Skills 目录 -├── code-review/ # Skill 文件夹 -│ ├── SKILL.md # 必需:元数据 + 完整指令 -│ ├── references/ # 可选:参考文档 -│ │ ├── style-guide.md -│ │ └── checklist.md -│ └── scripts/ # 可选:辅助脚本 -│ └── lint.sh -├── refactor/ -│ └── SKILL.md -└── my-skill/ - └── SKILL.md -``` - -**说明**: -- 内置 Skills 首次启动时自动安装到同一目录 -- 用户在设置界面中管理 Skills(启用/禁用/编辑) -- 用户可删除或修改内置 Skills - -### 2.2 Skill 安装 - -支持三种安装方式: - -| 方式 | 说明 | 使用场景 | -|------|------|----------| -| 本地文件夹 | 选择包含 SKILL.md 的文件夹 | 从其他客户端拷贝(如 ~/.claude/skills/) | -| 本地 zip | 选择 zip 文件 | 下载的 Skill 包 | -| URL | 输入 zip 下载地址 | 在线安装 | - -**文件夹/zip 结构要求**: - -``` -skill-name/ # Skill 文件夹 -├── SKILL.md # 必需 -├── references/ # 可选 -└── scripts/ # 可选 -``` - -**安装流程**: - -``` -用户选择文件夹 / zip 文件 / 输入 URL - │ - ▼ -读取文件夹 / 解压 zip / 下载并解压 - │ - ▼ -验证结构(SKILL.md 必须存在) - │ - ▼ -解析 SKILL.md frontmatter,获取 name - │ - ▼ -验证 name 与目录名一致(不一致则自动重命名目录为 name) - │ - ▼ -检查同名 Skill 是否存在 - │ - ├── 存在 → 提示用户确认覆盖 - │ - ▼ -拷贝到 ~/.deepchat/skills/{name}/ - │ - ▼ -触发热加载,更新 Metadata -``` - -**冲突处理**: -- 同名 Skill 已存在时,提示用户选择:覆盖 / 取消 -- 覆盖时先备份原 Skill 文件夹 - -### 2.3 SKILL.md 格式 - -```markdown ---- -name: code-review -description: 按照团队规范进行代码审查,检查代码质量、安全性和可维护性 -allowedTools: # 可选:额外需要的工具(与用户已启用工具取并集) - - Read - - Grep - - Glob - - Bash(git:*) ---- - -# Code Review Skill - -当前 Skill 根目录: ${SKILL_ROOT} - -## 你的角色 - -你是一个代码审查专家,负责按照团队规范审查代码变更。 - -## 审查流程 - -1. 首先使用 `git diff` 查看变更范围 -2. 阅读 ${SKILL_ROOT}/references/checklist.md 了解检查项 -3. 逐个检查代码变更 -4. 输出结构化的审查报告 - -## 资源位置 - -- 检查清单: ${SKILL_ROOT}/references/checklist.md -- 代码规范: ${SKILL_ROOT}/references/style-guide.md -- Lint 脚本: ${SKILL_ROOT}/scripts/lint.sh - -## 输出格式 - -对于每个发现,使用以下格式: -- **位置**: 文件:行号 -- **级别**: 🔴 严重 | 🟡 建议 | 🟢 优化 -- **描述**: 问题描述 -- **建议**: 修复建议 -``` - -### 2.4 Frontmatter 字段 - -| 字段 | 类型 | 必需 | 说明 | -|------|------|------|------| -| `name` | string | 是 | Skill 唯一标识符,**必须与目录名一致** | -| `description` | string | 是 | 简短描述,用于模型理解何时使用 | -| `allowedTools` | string[] | 否 | 额外需要的工具,与用户已启用工具取并集 | - -### 2.5 Name 与目录名一致性 - -`name` 是 Skill 的唯一标识符,**必须与目录名保持一致**。 - -**安装时验证**: -``` -解析 SKILL.md frontmatter 获取 name - │ - ▼ -比较 name 与源目录名 - │ - ├── 一致 → 直接拷贝到 ~/.deepchat/skills/{name}/ - │ - └── 不一致 → 自动重命名目录为 name,拷贝到 ~/.deepchat/skills/{name}/ -``` - -**持久化**: -- `activeSkills` 持久化 `name`(而非目录路径) -- 恢复会话时通过 `name` 查找 Skill - -### 2.6 路径变量 - -Skill 内容中支持以下变量替换: - -| 变量 | 说明 | -|------|------| -| `${SKILL_ROOT}` | 当前 Skill 的根目录路径 | -| `${SKILLS_DIR}` | Skills 总目录路径 (~/.deepchat/skills/) | - -**示例**: -```markdown -读取检查清单: ${SKILL_ROOT}/references/checklist.md -执行脚本: ${SKILL_ROOT}/scripts/lint.sh -``` - ---- - -## 3. 数据模型 - -### 3.1 SkillMetadata(元数据) - -启动时从 SKILL.md frontmatter 提取,始终保留在内存中。 - -```typescript -interface SkillMetadata { - name: string // 唯一标识符(来自 frontmatter,与目录名一致) - description: string // 描述文本 - path: string // SKILL.md 完整路径 - skillRoot: string // Skill 根目录路径 - allowedTools?: string[] // 额外需要的工具(可选) -} -``` - -### 3.2 SkillState(会话状态) - -与 Conversation 关联的激活状态,需要持久化。 - -```typescript -interface SkillState { - conversationId: string // 关联的会话 ID - activeSkills: Set // 已激活的 Skill 名称集合 -} -``` - -**持久化方案**: - -存储在 Conversation 记录中(SQLite chat.db): - -```typescript -// conversations 表扩展字段 -interface Conversation { - // ... 现有字段 - activeSkills?: string[] // JSON 序列化的激活 Skill 名称数组 -} -``` - -**生命周期**: -- 新建会话:`activeSkills = []` -- 激活/停用 Skill:更新内存状态 + 持久化到数据库 -- 恢复会话:从数据库加载 `activeSkills`,过滤掉已不存在的 Skill - -### 3.3 SkillContent(完整内容) - -激活后加载,注入到系统提示。 - -```typescript -interface SkillContent { - name: string - content: string // SKILL.md 完整内容(含 frontmatter 后的正文) -} -``` - ---- - -## 4. 工具定义 - -### 4.1 skill_list - -列出所有可用 Skills 及其激活状态。 - -```typescript -{ - name: "skill_list", - description: "列出所有可用的 skills 及其当前激活状态", - parameters: { - type: "object", - properties: {}, - required: [] - } -} - -// 返回示例 -{ - "skills": [ - { "name": "code-review", "description": "代码审查", "active": true }, - { "name": "refactor", "description": "代码重构", "active": false } - ] -} -``` - -### 4.2 skill_control - -激活或停用指定 Skill。 - -```typescript -{ - name: "skill_control", - description: "激活或停用一个 skill。激活后,该 skill 的完整指令将在下一轮对话中生效", - parameters: { - type: "object", - properties: { - action: { - type: "string", - enum: ["activate", "deactivate"], - description: "操作类型" - }, - skill_name: { - type: "string", - description: "skill 名称" - } - }, - required: ["action", "skill_name"] - } -} - -// 返回示例 -{ - "success": true, - "message": "Skill 'code-review' activated. Instructions will be loaded in next turn." -} -``` - ---- - -## 5. Agent Loop 集成 - -### 5.1 Context 构建流程 - -``` -每轮 Loop 开始前: - -1. 构建基础系统提示 - │ -2. 附加 Skills Metadata 列表(含 Skills 根目录路径) - │ "## Available Skills - │ Skills directory: ~/.deepchat/skills/ - │ You can activate these skills using skill_control tool: - │ - code-review: 代码审查 - │ - refactor: 代码重构" - │ -3. 检查 SkillState.activeSkills - │ -4. 对每个激活的 Skill,加载完整内容 - │ 读取 SKILL.md 正文部分,替换 ${SKILL_ROOT} 等变量 - │ -5. 附加到系统提示 - │ "## Active Skills - │ [所有激活 Skill 的内容依次列出]" - │ -6. 继续正常的 Agent Loop -``` - -### 5.2 状态生命周期 - -``` -新建会话 - │ - ▼ -SkillState = { conversationId, activeSkills: new Set() } - │ - ▼ -持久化空状态到 Conversation.activeSkills - │ - ▼ -用户消息 → Agent Loop - │ - ▼ -模型调用 skill_control({ action: "activate", skill_name: "code-review" }) - │ - ▼ -SkillState.activeSkills.add("code-review") - │ - ▼ -持久化更新到 Conversation.activeSkills - │ - ▼ -当前轮继续执行(Skill 内容尚未加载) - │ - ▼ -下一轮 Loop 开始 - │ - ▼ -检测到 "code-review" 激活 → 加载 SKILL.md 内容 → 注入系统提示 - │ - ▼ -模型看到完整 Skill 指令,按指令行动 - - -恢复会话(用户重新打开历史会话) - │ - ▼ -从 Conversation.activeSkills 加载持久化状态 - │ - ▼ -过滤掉已不存在的 Skill 名称 - │ - ▼ -SkillState = { conversationId, activeSkills: 过滤后的集合 } - │ - ▼ -继续正常的 Agent Loop(已激活 Skill 内容会被加载) -``` - -### 5.3 工具列表构建 - -当 Skill 定义了 `allowedTools` 时,与用户已启用的工具取**并集**: - -``` -LLM 请求时工具列表构建: - │ - ▼ -获取用户已启用的工具列表 (userEnabledTools) - │ - ▼ -遍历所有激活的 Skills - │ - ▼ -对每个 Skill,获取 allowedTools - │ - ▼ -最终工具列表 = userEnabledTools ∪ skill1.allowedTools ∪ skill2.allowedTools ∪ ... -``` - -**说明**: -- `allowedTools` 是**扩展**工具列表,而非限制 -- 提示词中提到的工具和 tools 属性中暴露的工具是两个概念 -- 提示词可以引导模型使用某些工具,但实际可用工具由 tools 列表决定 - ---- - -## 6. 组件设计 - -### 6.1 SkillPresenter - -核心协调器,负责 Skill 的发现、管理和内容加载。 - -| 方法 | 职责 | -|------|------| -| `installBuiltinSkills()` | 首次启动时安装内置 Skills 到用户目录 | -| `installFromFolder(folderPath)` | 从本地文件夹安装 Skill | -| `installFromZip(zipPath)` | 从本地 zip 文件安装 Skill | -| `installFromUrl(url)` | 从 URL 下载并安装 Skill | -| `uninstallSkill(name)` | 卸载指定 Skill(删除文件夹) | -| `discoverSkills()` | 扫描 skills 目录,提取 Metadata | -| `getMetadataList()` | 返回所有 Skill 的 Metadata 列表 | -| `getMetadataPrompt()` | 生成 Metadata 列表的文本(注入 Context) | -| `loadSkillContent(name)` | 读取指定 Skill 的完整 SKILL.md 内容,替换路径变量 | -| `getSkillsDir()` | 获取 Skills 根目录路径 | -| `watchSkillFiles()` | 监控 Skill 文件变化,触发热加载 | -| `getActiveSkillsAllowedTools(conversationId)` | 获取会话中激活 Skills 声明的额外工具列表 | -| `getActiveSkills(conversationId)` | 获取会话的激活 Skill 列表(从持久化加载) | -| `setActiveSkills(conversationId, skills)` | 更新会话的激活 Skill 列表(持久化) | -| `validateSkillNames(names)` | 过滤掉已不存在的 Skill 名称 | - -### 6.2 SkillTools - -暴露给模型的工具实现。 - -| 方法 | 职责 | -|------|------| -| `handleSkillList(state)` | 处理 skill_list 工具调用 | -| `handleSkillControl(state, action, name)` | 处理 skill_control 工具调用 | - -### 6.3 职责边界 - -``` -SkillPresenter SkillTools AgentLoop - │ │ │ - │ 提供数据 │ 提供工具 │ 管理状态 - │ - Metadata │ - skill_list │ - SkillState - │ - Content │ - skill_control │ - Context 构建 - │ │ │ -``` - ---- - -## 7. 与现有系统集成 - -### 7.1 与 ConfigPresenter - -``` -ConfigPresenter - │ - └── getSkillSettings() - ├── skillsPath: string // Skills 目录路径 (~/.deepchat/skills/) - └── enableSkills: boolean // 全局开关 -``` - -### 7.2 enableSkills 开关行为 - -`enableSkills` 是 Skills 系统的全局开关,控制整个功能的启用/禁用。 - -**enableSkills = true(启用)**: -- 注册 `skill_list` / `skill_control` 工具 -- 注入 Skills Metadata 列表到 Context -- 注入激活 Skill 的完整内容到 Context -- 合并激活 Skills 的 `allowedTools` 到工具列表 -- 正常处理 Skill 激活/停用 - -**enableSkills = false(禁用)**: -- **不注册** `skill_list` / `skill_control` 工具(模型无法看到和调用) -- **不注入** Metadata 列表到 Context -- **不注入** 激活 Skill 内容到 Context -- **不合并** `allowedTools` 到工具列表 -- **保留**持久化的 `activeSkills` 状态(视为挂起,重新启用后恢复) - -``` -用户关闭 enableSkills - │ - ▼ -Skills 功能挂起(非清除) - │ - ├── 工具不可见 - ├── 提示词无 Skills 相关内容 - ├── 工具列表不含 Skills 的 allowedTools - │ - └── Conversation.activeSkills 保持不变 - │ - ▼ - 用户重新开启 enableSkills - │ - ▼ - 恢复之前的激活状态 -``` - -### 7.3 热加载机制 - -``` -启动时: - │ - ├── discoverSkills() → 扫描并加载所有 Metadata - │ - └── watchSkillFiles() → 启动文件监控 - │ - ▼ - 监控 ~/.deepchat/skills/ 目录 - │ - ▼ - 文件变化事件 (add/change/unlink) - │ - ▼ - 重新解析受影响的 SKILL.md - │ - ▼ - 更新内存中的 Metadata - │ - ▼ - 发送 SKILL_EVENTS.METADATA_UPDATED 事件 -``` - -### 7.4 与 McpPresenter - -``` -构建 LLM 请求时的工具列表: - -SkillPresenter McpPresenter - │ │ - │ getActiveSkillsAllowedTools() │ - │ ─────────────────────────────► │ - │ │ - │ 返回 ["Read", "Bash(git:*)"] │ - │ ◄───────────────────────────── │ - │ │ - │ mergeTools(userEnabled, skillAllowed) - │ │ - │ 返回合并后的工具列表 │ - │ ◄───────────────────────────── │ -``` - -### 7.5 与 AgentLoopHandler - -``` -AgentLoopHandler - │ - ├── 检查 enableSkills 配置 - │ │ - │ └── 仅当 enableSkills = true 时: - │ │ - │ ├── 初始化 SkillState - │ │ - │ ├── 注册 skill_list / skill_control 工具 - │ │ - │ ├── preparePromptContent() 中: - │ │ ├── 附加 Metadata 列表 - │ │ └── 附加激活 Skill 的完整内容 - │ │ - │ └── 处理工具调用时: - │ └── 路由到 SkillTools - │ - └── enableSkills = false 时: - └── 跳过所有 Skills 相关逻辑 -``` - -### 7.6 与 EventBus - -```typescript -const SKILL_EVENTS = { - DISCOVERED: 'skill:discovered', // Skills 发现完成 - METADATA_UPDATED: 'skill:metadata-updated', // Metadata 热加载更新 - INSTALLED: 'skill:installed', // Skill 安装完成 - UNINSTALLED: 'skill:uninstalled', // Skill 卸载完成 - ACTIVATED: 'skill:activated', // Skill 被激活 - DEACTIVATED: 'skill:deactivated' // Skill 被停用 -} -``` - ---- - -## 8. 多 Skill 激活 - -多个 Skill 同时激活时,内容在同一个板块中依次列出: - -``` -## Active Skills - -[code-review 的 SKILL.md 内容] - -[refactor 的 SKILL.md 内容] -``` diff --git a/docs/archives/skills-system/research.md b/docs/archives/skills-system/research.md deleted file mode 100644 index 84cb3cff4..000000000 --- a/docs/archives/skills-system/research.md +++ /dev/null @@ -1,477 +0,0 @@ -# Skills 系统调研报告 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 概述 - -### 1.1 什么是 Skills 系统 - -Skills 系统是一种 AI 助手的可扩展性机制,允许 AI Agent 动态发现和应用专业知识、工作流程和能力。与传统的 Tools(执行动作)不同,Skills 提供**专业知识**和**程序性知识**,教会 AI Agent 如何处理特定领域或任务。 - -### 1.2 核心特征 - -| 特征 | 说明 | -|------|------| -| **Model-invoked** | AI Agent 基于语义匹配自动决定何时使用 Skill | -| **Progressive Disclosure** | 启动时仅加载元数据,激活时才加载完整内容 | -| **Domain Expertise** | 编码专业知识和行为模式,而非仅仅是可执行函数 | -| **Token Efficient** | 仅元数据(名称+描述)在启动时加载,完整内容按需加载 | -| **Composable** | 将通用 Agent 转换为特定领域的专家 | - -### 1.3 Skills vs Tools vs MCP 对比 - -| 维度 | Skills | MCP Tools | Custom Prompts | -|------|--------|-----------|----------------| -| **提供内容** | "怎么做" - 专业知识、方法论 | "做什么" - 能力、数据访问 | 模板化的提示词 | -| **焦点** | 程序性知识 | 外部集成 | 文本生成 | -| **加载方式** | 渐进式(元数据 → 完整内容) | 预先加载(完整 Schema) | 按需加载 | -| **Token 成本** | 低(~50 元数据 + 2-5K 激活时) | 高(90+ 工具可达 50K+) | 中等 | -| **调用方式** | 语义匹配描述 | 函数调用按名称 | @ 提及或 LLM 调用 | -| **内容类型** | 指令、工作流、模式 | 可执行函数 | 模板文本 | - ---- - -## 2. 业界实现模式分析 - -### 2.1 Claude Code Skills 系统 - -Claude Code 实现了最完整的 Skills 系统,是本次调研的主要参考。 - -#### 2.1.1 文件结构 - -``` -.claude/skills/ -├── skill-name/ -│ ├── SKILL.md (必需) # 元数据 + 指令 -│ ├── scripts/ # 可执行脚本 -│ ├── references/ # 按需加载的文档 -│ └── assets/ # 输出模板/资源 -``` - -#### 2.1.2 SKILL.md 格式 - -```yaml ---- -name: skill-name -description: 描述 Skill 的用途和触发条件(用于语义匹配) -allowed-tools: [可选] Read, Bash, Grep # 工具限制 -model: [可选] claude-sonnet-4 ---- - -# Skill 指令 -[Markdown 格式的 AI Agent 指令] -``` - -#### 2.1.3 发现流程 - -``` -1. 启动: 扫描 ~/.claude/skills/ 和 .claude/skills/ -2. 加载: 仅解析 YAML frontmatter(名称 + 描述) -3. 呈现: Skills 可用于语义匹配 -4. 匹配: 用户请求 → LLM 匹配描述 -5. 确认: 用户看到确认提示 -6. 激活: 加载完整 SKILL.md 正文 -7. 执行: 遵循指令,按需加载 references -``` - -#### 2.1.4 多级作用域 - -```typescript -const skillPaths = [ - '/enterprise/skills/', // 优先级 1: 企业级 - '~/.claude/skills/', // 优先级 2: 个人 - '.claude/skills/', // 优先级 3: 项目 - 'plugins/*/skills/' // 优先级 4: 插件提供 -]; -``` - -### 2.2 LangChain Skills 模式 - -LangChain 的 Skills 模式结合了渐进式加载和动态工具注册: - -```typescript -class SkillManager { - async loadSkill(skillName: string) { - // 先加载元数据 - const metadata = await loadMetadata(skillName); - - // Skill 激活时动态注册工具 - if (metadata.tools) { - this.registerTools(metadata.tools); - } - - // 加载完整指令 - const instructions = await loadInstructions(skillName); - return { metadata, instructions }; - } -} -``` - -**适用场景**: -- 单个 Agent 有多个专业化领域 -- 不同技能之间没有严格约束 -- 不同团队独立开发能力 - -### 2.3 VS Code Extension 模式 - -VS Code 的扩展架构提供了隔离和安全性的参考: - -``` -主进程 (应用管理) - ↓ -渲染进程 (UI) - ↓ -Extension Host 进程 (隔离) - ↓ (Extension API) -扩展 (用户代码) -``` - -**关键原则**: -- 扩展不直接访问 DOM -- 通过消息传递通信 (IPC) -- 定义良好、稳定的 Extension API -- 激活事件触发按需加载 - -### 2.4 工具注册表模式 (OpenAI/LangChain) - -传统的工具重型架构: - -```typescript -interface Tool { - name: string; - description: string; - parameters: JSONSchema; - execute: (args: any) => Promise; -} - -// 所有工具预先加载到上下文 -const toolRegistry = new ToolRegistry(); -toolRegistry.register(databaseTool); -// ...50+ 工具 = 50K+ tokens -``` - -**问题**:Token 成本高,所有工具定义预先加载。 - ---- - -## 3. Token 经济与性能 - -### 3.1 传统工具重型架构 - -``` -预先成本: 50,000 tokens (90+ 工具完整 Schema) -每次请求: 所有工具在上下文中 -总计: 50K + 消息 tokens -``` - -### 3.2 Skills 架构 - -``` -启动: 2,500 tokens (50 skills × 50 tokens 元数据) -空闲: 2,500 tokens -激活 (1 skill): 2,500 + 3,000 = 5,500 tokens -节省: 空闲时 90%+ 减少 -``` - -### 3.3 最佳实践 - -1. **SKILL.md 保持在 500 行以下** -2. **使用渐进式加载**(拆分到 references/) -3. **执行脚本不加载**(0 token 成本) -4. **按需加载 references** -5. **避免重复**(信息只在 SKILL.md 或 references 中出现一次) - ---- - -## 4. 调用模式 - -### 4.1 自动调用(基于上下文) - -- Agent 持续评估上下文与 Skill 描述的匹配度 -- 超过阈值时激活 Skill -- 用户在完整加载前看到确认提示 - -### 4.2 显式调用(斜杠命令) - -```bash -# 手动调用 -> /review-code --strict - -# 背后可能激活 "code-review" skill -``` - -### 4.3 混合调用(Tool-Skill 桥接) - -```typescript -// SlashCommand 工具允许程序化调用 Skill -{ - name: "SlashCommand", - description: "执行自定义斜杠命令", - parameters: { - command: "/skill-name", - args: "附加参数" - } -} -``` - ---- - -## 5. 安全考虑 - -### 5.1 沙箱隔离模式 - -| 模式 | 描述 | 适用场景 | -|------|------|----------| -| **容器 (Docker/LXC)** | 标准隔离,无性能惩罚 | 受信代码 | -| **MicroVM (Firecracker)** | 强隔离,独立内核 | 不受信代码 | -| **用户模式内核 (gVisor)** | 应用与 OS 之间的安全屏障 | Kubernetes Agent | -| **WebAssembly** | 默认拒绝权限模型 | 轻量级插件 | -| **V8 Isolates** | 隔离的 V8 引擎实例 | 高密度、低启动 | - -### 5.2 权限控制模式 - -#### Skill 级工具限制 -```yaml ---- -name: read-only-analysis -description: 分析代码但不修改 -allowed-tools: Read, Grep, Glob # 禁止 Write, Edit, Bash ---- -``` - -#### 白名单模式 -```yaml -allowed-tools: - - Bash(git status:*) - - Bash(git add:*) - - Bash(npm test:*) -``` - -#### 深度防御 -``` -层 1: 显式命令白名单 -层 2: 最小权限,时间限制 Token -层 3: 沙箱隔离 (gVisor/Firecracker) -层 4: 关键操作的人机协作 -层 5: 审计日志和监控 -``` - ---- - -## 6. DeepChat 现有架构分析 - -### 6.1 现有类似 Skill 的功能 - -#### Custom Prompts 系统 - -DeepChat 已有的 Custom Prompts 系统与 Skills 有 80% 的相似度: - -```typescript -interface Prompt { - id: string - name: string - description: string - content?: string // 模板文本 - parameters?: Array<{ // 动态参数 - name: string - description: string - required: boolean - }> - files?: FileItem[] - enabled?: boolean - source?: 'local' | 'imported' | 'builtin' - createdAt?: number - updatedAt?: number -} -``` - -**已有功能**: -- ✅ 命名、可重用操作 -- ✅ 参数支持 -- ✅ 启用/禁用开关 -- ✅ 用户管理 UI -- ✅ LLM 集成(AutoPromptingServer) -- ✅ 缓存优化 -- ✅ 事件驱动架构 - -#### AutoPromptingServer - -DeepChat 通过内置的 MCP 服务器暴露 Custom Prompts: - -```typescript -// 服务器名称: deepchat-inmemory/custom-prompts-server -// 暴露的工具: -- get_template_parameters // 列出 Prompt 参数 -- fill_template // 用参数填充 Prompt -``` - -### 6.2 MCP 架构 - -DeepChat 的 MCP 系统已经非常完善: - -``` -McpPresenter (核心协调器) - ├── ServerManager (服务器生命周期) - ├── ToolManager (工具执行和权限) - ├── McpClient (单个服务器连接) - └── InMemoryServers (内置服务器) - ├── ArtifactsServer - ├── SearchServers - ├── KnowledgeServers - ├── DeepResearchServer - ├── AutoPromptingServer ← 已有 Skill-like 功能 - ├── ConversationSearchServer - ├── MeetingServer - └── AppleServer -``` - -### 6.3 工具执行数据流 - -``` -用户消息 (Chat/Agent) - ↓ -Agent Loop (llmProviderPresenter) - ↓ -LLM 调用 agent.stream() 带工具定义 - ↓ -LLM 返回 tool_call 事件 - ↓ -ToolPresenter.callTool() 路由到: - - MCP via McpPresenter.callTool() - - Agent tools via AgentToolManager.callTool() - ↓ -ToolManager 解析工具名 → 客户端映射 - ↓ -检查权限 (read/write/all) - ↓ -在目标客户端执行 - ↓ -格式化响应 - ↓ -返回 Agent Loop → LLM 整合 -``` - -### 6.4 配置系统 - -DeepChat 使用 ElectronStore 持久化配置: - -``` -~/.../userData/ -├── app-settings.json # 主设置 -├── custom_prompts.json # Custom Prompts -├── system_prompts.json # 系统提示词 -├── mcp-settings.json # MCP 配置 -├── model-config.json # 模型配置 -└── ... -``` - -**配置模式**: -- Helper 模式:每个配置域有专门的 Helper 类 -- 代理模式:Renderer → Main 通过 IPC 代理通信 -- 缓存模式:高频读取的内存缓存 -- 事件驱动更新:通过 EventBus 广播到所有窗口 - ---- - -## 7. Skills 系统与现有架构的集成点 - -### 7.1 与 MCP 的关系 - -Skills 可以**指导**工具使用方法论,而 MCP Tools 提供实际能力: - -``` -用户请求 - ↓ -AI Agent (带 Skills) - ↓ -Skill 激活 → 提供领域专业知识 - ↓ ↓ -Skill 指令指导 → MCP Tool 执行 - ↓ - 外部系统 -``` - -**示例**: -- **Skill**: "按公司标准进行代码审查"(教方法论) -- **MCP Tools**: Git 操作、文件读取、Lint(提供能力) -- **结果**: Skill 指导 Agent 如何审查;Tools 提供代码访问 - -### 7.2 自然扩展点 - -1. **存储层** - - 扩展 Prompt 接口或创建 SkillDefinition - - 添加元数据(标签、分类、版本、依赖) - - 存储在独立的 skills store 或扩展 prompts store - -2. **注册** - - 使用类似 in-memory servers 的模式 - - 创建 skills builder/registry - - 可能作为 MCP 服务器暴露 - -3. **执行** - - 如果基于 MCP,通过 ToolPresenter 路由 - - 如需更深集成,可直接调用 - - 通过现有系统进行权限检查 - -4. **UI 集成** - - 设置中的 Skills 面板 - - 专门的 Skills 浏览器 - - Skill 市场/发现 - - Skill 链式/组合 UI - -5. **事件广播** - - 新增 SKILL_EVENTS 常量 - - 广播 Skill 注册/执行事件 - - 与现有 EventBus 集成 - ---- - -## 8. 关键发现与建议 - -### 8.1 DeepChat 的优势 - -1. **模块化 Presenter 模式** - 清晰的关注点分离 -2. **事件驱动** - 通过 EventBus 松耦合 -3. **动态工具加载** - MCP 服务器运行时加载 -4. **多格式支持** - 工具可与任何 LLM 提供商配合 -5. **权限框架** - 内置访问控制 -6. **缓存** - 优化性能 -7. **统一工具接口** - 所有工具来源的单一入口 - -### 8.2 Skills 系统的自然演进路径 - -1. **起点**:Custom Prompts(已有 80%) -2. **添加**:Skill 元数据和分类 -3. **暴露**:通过现有 MCP 基础设施 -4. **增强**:UI 专门的 Skills 管理 -5. **可选**:LLM 上下文外的直接 Skill 执行 - -### 8.3 推荐的实现策略 - -基于调研结果,建议采用**渐进式增强**策略: - -1. **Phase 1**: 扩展现有 Custom Prompts 为 Skills -2. **Phase 2**: 添加文件系统发现机制 -3. **Phase 3**: 实现渐进式加载和语义匹配 -4. **Phase 4**: 添加权限控制和工具限制 -5. **Phase 5**: UI 增强和市场功能 - ---- - -## 9. 参考资源 - -### Skills 系统 -- [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills) -- [Claude Skills vs. MCP](https://intuitionlabs.ai/articles/claude-skills-vs-mcp) -- [LangChain Skills](https://docs.langchain.com/oss/python/langchain/multi-agent/skills) - -### 架构模式 -- [Google Cloud: Agentic AI Design Patterns](https://cloud.google.com/architecture/choose-design-pattern-agentic-ai-system) -- [VS Code Architecture](https://franz-ajit.medium.com/understanding-visual-studio-code-architecture-5fc411fca07) -- [Plugin Architecture Pattern](https://www.devleader.ca/2023/09/07/plugin-architecture-design-pattern-a-beginners-guide-to-modularity/) - -### 安全与沙箱 -- [Sandboxes for AI](https://www.luiscardoso.dev/blog/sandboxes-for-ai) -- [Google Agent Sandbox](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/agent-sandbox) -- [E2B Code Interpreter](https://e2b.dev/) diff --git a/docs/archives/skills-system/skills-syncing-antigravity.md b/docs/archives/skills-system/skills-syncing-antigravity.md deleted file mode 100644 index 09dea0804..000000000 --- a/docs/archives/skills-system/skills-syncing-antigravity.md +++ /dev/null @@ -1,237 +0,0 @@ -# Google Antigravity Workflows 格式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 本文档是 [skills-syncing.md](./skills-syncing.md) 的子文档,描述 Google Antigravity Workflows 的格式规格和转换规则。 - -## 1. 基本信息 - -| 属性 | 值 | -|------|-----| -| 工具名称 | Antigravity (Google Project IDX) | -| Workflows 目录 | `.agent/workflows/` | -| 文件模式 | `*.md` | -| 格式类型 | YAML frontmatter + Markdown | -| Frontmatter | **有** (仅 description) | - -## 2. 目录结构 - -``` -.agent/workflows/ -├── code-review.md -├── generate-tests.md -├── deploy.md -└── refactor.md -``` - -**特点**: -- 每个 Workflow 是**单个 Markdown 文件** -- 文件名即 Workflow 名称 -- 不支持子文件夹结构 -- 使用 `## Steps` + 编号步骤结构 - -## 3. Workflow 格式 - -### 3.1 完整示例 - -```markdown ---- -description: Generate unit tests for existing code ---- - -## Steps - -### 1. Analyze Target File - -- Read the specified code file -- Identify all exported functions and classes -- Note any existing test patterns in the codebase - -### 2. Generate Test Cases - -For each exported function: -- Create at least 3 test cases -- Cover edge cases and error conditions -- Use clear, descriptive test names - -### 3. Write Test File - -```javascript -import { describe, it, expect } from 'vitest' -import { functionName } from './target-file' - -describe('functionName', () => { - it('should handle normal case', () => { - // test implementation - }) -}) -``` - -### 4. Verify Tests - -Run the generated tests: -```bash -npm test -``` -``` - -### 3.2 结构说明 - -| 部分 | 必需 | 说明 | -|------|------|------| -| YAML frontmatter | ❌ 否 | 可选,仅支持 `description` | -| `description` | ❌ 否 | workflow 描述 | -| `## Steps` | ✅ 是 | 步骤容器 | -| `### N. Step Name` | ✅ 是 | 编号步骤 | - -### 3.3 Frontmatter 字段 - -| 字段 | 类型 | 必需 | 说明 | -|------|------|------|------| -| `description` | string | ❌ 否 | Workflow 描述 | - -**注意**:Antigravity 的 frontmatter 仅支持 `description` 字段,不支持 `name`、`tools`、`model` 等。 - -### 3.4 调用方式 - -在 Project IDX / Antigravity 中: -1. 打开命令面板 -2. 选择 "Run Workflow" -3. 选择要运行的 workflow - -或在 AI 对话中提及 workflow 名称。 - -## 4. 发现机制 - -Antigravity 从以下位置发现 Workflows: - -1. **项目目录**:`.agent/workflows/` -2. **全局配置**:用户级 workflows(如有) - -## 5. 与 DeepChat 的转换 - -### 5.1 兼容性 - -| 能力 | Antigravity | DeepChat | 转换 | -|------|:-----------:|:--------:|------| -| name | ✅ (从文件名) | ✅ | 提取文件名 | -| description | ✅ | ✅ | 直接映射 | -| instructions | ✅ (Steps) | ✅ | Steps 作为 instructions | -| allowedTools | ❌ | ✅ | 导出时丢失 | -| model | ❌ | ✅ | 导出时丢失 | -| references/ | ❌ | ✅ | 导出时丢失 | -| scripts/ | ❌ | ✅ | 导出时丢失 | - -### 5.2 导入转换 (Antigravity → DeepChat) - -```typescript -function convertFromAntigravity(content: string, filename: string): DeepChatSkill { - const { data, body } = grayMatter(content) - - // 从文件名提取 name - const name = filename.replace('.md', '') - - return { - name, - description: data.description || '', - instructions: body, - allowedTools: undefined, - model: undefined, - references: undefined, - scripts: undefined - } -} -``` - -### 5.3 导出转换 (DeepChat → Antigravity) - -```typescript -function convertToAntigravity(skill: DeepChatSkill): string { - let output = '' - - // 添加 frontmatter(如果有 description) - if (skill.description) { - output += `---\ndescription: ${skill.description}\n---\n\n` - } - - // 检查 instructions 是否已有步骤结构 - if (skill.instructions.includes('## Steps') || - skill.instructions.includes('### 1.') || - skill.instructions.includes('### Step 1')) { - output += skill.instructions - } else { - // 将整个 instructions 包装为步骤结构 - output += `## Steps\n\n### 1. Execute\n\n${skill.instructions}` - } - - return output -} -``` - -### 5.4 转换警告 - -导出到 Antigravity 时: - -| 丢失内容 | 处理方式 | -|----------|----------| -| `allowedTools` | 静默丢失 | -| `model` | 静默丢失 | -| `references/` | 静默丢失 | -| `scripts/` | 静默丢失 | - -导入到 DeepChat 时: - -| 转换内容 | 处理方式 | -|----------|----------| -| 步骤结构 | 保持原样作为 instructions | -| 无额外信息丢失 | - | - -### 5.5 步骤结构智能检测 - -在导出时,检测现有步骤结构: - -```typescript -function hasStepsStructure(content: string): boolean { - const patterns = [ - /^## Steps/m, - /^### \d+\./m, - /^### Step \d+/m - ] - return patterns.some(p => p.test(content)) -} -``` - -如果原 instructions 已有步骤结构,保持原样;否则包装为单一步骤。 - -## 6. 与 Windsurf 的比较 - -Antigravity 和 Windsurf 的格式非常相似: - -| 特性 | Antigravity | Windsurf | -|------|-------------|----------| -| 目录 | `.agent/workflows/` | `.windsurf/workflows/` | -| Frontmatter | 有 (仅 description) | 无 | -| 步骤结构 | `## Steps` + `### N.` | `## Steps` + `### N.` | -| 描述位置 | frontmatter | 标题后首段 | - -### 6.1 通用转换 - -由于格式相似,可以创建通用的步骤式 Markdown 适配器: - -```typescript -class StepsMarkdownAdapter { - parse(content: string, hasDescription: boolean): ParsedWorkflow { - // 通用解析逻辑 - } - - serialize(workflow: ParsedWorkflow, includeDescription: boolean): string { - // 通用序列化逻辑 - } -} -``` - -## 7. 参考资源 - -- [Customize Antigravity Rules and Workflows](https://atamel.dev/posts/2025/11-25_customize_antigravity_rules_workflows/) -- [Project IDX Documentation](https://idx.dev/docs) diff --git a/docs/archives/skills-system/skills-syncing-claude-code.md b/docs/archives/skills-system/skills-syncing-claude-code.md deleted file mode 100644 index f1507839c..000000000 --- a/docs/archives/skills-system/skills-syncing-claude-code.md +++ /dev/null @@ -1,203 +0,0 @@ -# Claude Code Skills 格式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 本文档是 [skills-syncing.md](./skills-syncing.md) 的子文档,描述 Claude Code Skills 的格式规格和转换规则。 - -## 1. 基本信息 - -| 属性 | 值 | -|------|-----| -| 工具名称 | Claude Code | -| Skills 目录 | `~/.claude/skills/` (用户级) 或 `.claude/skills/` (项目级) | -| 文件模式 | `*/SKILL.md` | -| 格式类型 | YAML frontmatter + Markdown | -| Frontmatter | **必需** | - -## 2. 目录结构 - -``` -~/.claude/skills/ -├── code-review/ -│ ├── SKILL.md # 必需:元数据 + 指令 -│ ├── references/ # 可选:参考文档(按需加载) -│ │ ├── style-guide.md -│ │ └── checklist.md -│ ├── scripts/ # 可选:可执行脚本 -│ │ └── lint.sh -│ └── assets/ # 可选:输出模板/资源 -│ └── report-template.md -├── refactor/ -│ └── SKILL.md -└── my-skill/ - └── SKILL.md -``` - -**特点**: -- 每个 Skill 是一个**文件夹**,而非单个文件 -- 文件夹名应与 `name` 字段一致 -- 支持 `references/`、`scripts/`、`assets/` 子文件夹 - -## 3. SKILL.md 格式 - -### 3.1 完整示例 - -```markdown ---- -name: code-review -description: Reviews code changes according to team standards. Use when the user asks for a code review, PR review, or wants feedback on their changes. -allowed-tools: Read, Grep, Glob, Bash(git:*) -license: MIT ---- - -# Code Review - -## Your Role - -You are a code review expert responsible for reviewing code changes according to team standards. - -## Review Process - -1. First use `git diff` to see the scope of changes -2. Read ${SKILL_ROOT}/references/checklist.md to understand check items -3. Review each code change -4. Output a structured review report - -## Resources - -- Checklist: ${SKILL_ROOT}/references/checklist.md -- Style Guide: ${SKILL_ROOT}/references/style-guide.md -- Lint Script: ${SKILL_ROOT}/scripts/lint.sh - -## Output Format - -For each finding, use the following format: -- **Location**: file:line -- **Level**: 🔴 Critical | 🟡 Suggestion | 🟢 Optimization -- **Description**: Issue description -- **Suggestion**: Fix suggestion -``` - -### 3.2 Frontmatter 字段 - -| 字段 | 类型 | 必需 | 约束 | 说明 | -|------|------|------|------|------| -| `name` | string | ✅ 是 | 最长 64 字符,仅小写字母/数字/连字符 | Skill 唯一标识符 | -| `description` | string | ✅ 是 | 最长 1024 字符,非空 | 描述用途和触发条件,用于语义匹配 | -| `allowed-tools` | string/array | ❌ 否 | - | 限制可用工具,省略则不限制 | -| `license` | string | ❌ 否 | - | 许可证类型 | - -### 3.3 allowed-tools 格式 - -支持两种写法: - -**字符串格式**: -```yaml -allowed-tools: Read, Grep, Glob, Bash(git:*) -``` - -**数组格式**: -```yaml -allowed-tools: - - Read - - Grep - - Glob - - Bash(git:*) -``` - -**工具限制语法**: -- `Bash` - 允许所有 Bash 命令 -- `Bash(git:*)` - 仅允许 git 开头的命令 -- `Bash(npm test:*)` - 仅允许 npm test 开头的命令 - -### 3.4 路径变量 - -Skill 内容中支持以下变量替换: - -| 变量 | 说明 | -|------|------| -| `${SKILL_ROOT}` | 当前 Skill 的根目录路径 | - -## 4. 发现机制 - -Claude Code 按以下顺序扫描 Skills: - -1. `~/.config/claude/skills/` - 用户配置目录 -2. `~/.claude/skills/` - 用户 HOME 目录 -3. `.claude/skills/` - 项目目录 -4. 插件提供的 Skills -5. 内置 Skills - -**优先级**:后发现的同名 Skill 覆盖先发现的。 - -## 5. 已知问题 - -根据 [Issue #9817](https://github.com/anthropics/claude-code/issues/9817): - -- Frontmatter 格式敏感,多行 description 可能导致发现失败 -- 发现失败时无错误提示(静默失败) -- YAML 对缩进敏感,建议使用 2 空格缩进 - -**建议**: -```yaml -# ✅ 推荐:单行 description -description: Reviews code changes according to team standards. - -# ❌ 避免:多行 description -description: | - Reviews code changes according to team standards. - Use when the user asks for a code review. -``` - -## 6. 与 DeepChat 的转换 - -### 6.1 兼容性 - -| 能力 | Claude Code | DeepChat | 转换 | -|------|:-----------:|:--------:|------| -| name | ✅ | ✅ | 直接映射 | -| description | ✅ | ✅ | 直接映射 | -| allowed-tools | ✅ | ✅ | 字段名转换 `allowed-tools` ↔ `allowedTools` | -| references/ | ✅ | ✅ | 直接复制 | -| scripts/ | ✅ | ✅ | 直接复制 | -| assets/ | ✅ | ⚠️ | 复制到 references/ 或忽略 | -| license | ✅ | ❌ | 忽略 | - -### 6.2 导入转换 (Claude Code → DeepChat) - -```typescript -function convertFromClaudeCode(skill: ClaudeCodeSkill): DeepChatSkill { - return { - name: skill.name, - description: skill.description, - // 字段名转换 - allowedTools: skill['allowed-tools'], - instructions: skill.body, - // 子文件夹直接复制 - references: skill.references, - scripts: skill.scripts - } -} -``` - -### 6.3 导出转换 (DeepChat → Claude Code) - -```typescript -function convertToClaudeCode(skill: DeepChatSkill): string { - const frontmatter = { - name: skill.name, - description: skill.description, - // 字段名转换 - ...(skill.allowedTools && { 'allowed-tools': skill.allowedTools }) - } - - return `---\n${yaml.dump(frontmatter)}---\n\n${skill.instructions}` -} -``` - -## 7. 参考资源 - -- [Agent Skills - Claude Code Docs](https://code.claude.com/docs/en/skills) -- [Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) -- [Anthropic Skills Repository](https://github.com/anthropics/skills) diff --git a/docs/archives/skills-system/skills-syncing-copilot.md b/docs/archives/skills-system/skills-syncing-copilot.md deleted file mode 100644 index d400bc49e..000000000 --- a/docs/archives/skills-system/skills-syncing-copilot.md +++ /dev/null @@ -1,247 +0,0 @@ -# GitHub Copilot Prompt Files 格式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 本文档是 [skills-syncing.md](./skills-syncing.md) 的子文档,描述 GitHub Copilot Prompt Files 的格式规格和转换规则。 - -## 1. 基本信息 - -| 属性 | 值 | -|------|-----| -| 工具名称 | GitHub Copilot | -| Prompts 目录 | `.github/prompts/` | -| 文件模式 | `*.prompt.md` | -| 格式类型 | YAML frontmatter + Markdown | -| Frontmatter | **可选** | - -## 2. 目录结构 - -``` -.github/prompts/ -├── code-review.prompt.md -├── refactor.prompt.md -├── generate-tests.prompt.md -└── explain-code.prompt.md -``` - -**特点**: -- 每个 Prompt 是**单个 Markdown 文件** -- 文件名必须以 `.prompt.md` 结尾 -- 文件名(不含扩展名)即 Prompt 名称 -- 不支持子文件夹结构 -- 支持 `#file` 引用语法 - -## 3. Prompt File 格式 - -### 3.1 完整示例 - -```markdown ---- -description: Generate a comprehensive code review for the selected code -agent: agent -model: GPT-4o -tools: ['githubRepo', 'search/codebase', 'read', 'edit'] ---- - -# Code Review - -Review the code changes and provide feedback on: -- Code quality and readability -- Potential bugs and edge cases -- Performance implications -- Security vulnerabilities - -## Context - -Use #file:'${file}' to reference the current file context. - -## Output Format - -Provide a structured review with: -1. Summary of changes -2. Issues found (by severity) -3. Suggested improvements -4. Overall assessment -``` - -### 3.2 Frontmatter 字段 - -| 字段 | 类型 | 必需 | 说明 | -|------|------|------|------| -| `description` | string | ❌ 否 | 描述用途,用于提示列表显示 | -| `agent` | string | ❌ 否 | 通常设为 `agent` | -| `model` | string | ❌ 否 | 指定使用的模型(如 `GPT-4o`) | -| `tools` | string[] | ❌ 否 | 可用工具列表 | - -### 3.3 tools 数组 - -支持的工具包括: - -| 工具名 | 说明 | -|--------|------| -| `githubRepo` | GitHub 仓库访问 | -| `search/codebase` | 代码库搜索 | -| `read` | 读取文件 | -| `edit` | 编辑文件 | -| `terminalLastCommand` | 终端最后命令 | -| `runCommands` | 运行命令 | - -### 3.4 文件引用语法 - -Copilot 支持特殊的 `#file` 引用语法: - -```markdown -# Using file references -Use #file:'src/utils.ts' to include file context. - -# Using variable -Check #file:'${file}' for the current file. -``` - -### 3.5 调用方式 - -在 VS Code 中: -1. 打开命令面板 (`Ctrl/Cmd + Shift + P`) -2. 输入 `@workspace /prompt-name` -3. 或在 Chat 中直接输入 `/prompt-name` - -## 4. 发现机制 - -GitHub Copilot 从以下位置发现 Prompt Files: - -1. **工作区目录**:`.github/prompts/` -2. **用户全局目录**:`~/.github/prompts/` - -**注意**:Copilot 按字母顺序列出 prompts。 - -## 5. 与 DeepChat 的转换 - -### 5.1 兼容性 - -| 能力 | GitHub Copilot | DeepChat | 转换 | -|------|:--------------:|:--------:|------| -| name | ✅ (从文件名) | ✅ | 提取文件名 | -| description | ✅ | ✅ | 直接映射 | -| instructions | ✅ | ✅ | Markdown body | -| allowedTools | ✅ (tools) | ✅ | 工具名称映射 | -| model | ✅ | ✅ | 直接映射 | -| references/ | ⚠️ (#file 引用) | ✅ | 转换 #file 或内联 | -| scripts/ | ❌ | ✅ | 导出时丢失 | - -### 5.2 工具名称映射 - -DeepChat 与 Copilot 的工具名称需要映射: - -| DeepChat | GitHub Copilot | -|----------|----------------| -| Read | read | -| Edit | edit | -| Bash | runCommands | -| Grep | search/codebase | -| Glob | search/codebase | - -### 5.3 导入转换 (Copilot → DeepChat) - -```typescript -function convertFromCopilot(content: string, filename: string): DeepChatSkill { - const { data, body } = grayMatter(content) - - // 从文件名提取 name - const name = filename.replace('.prompt.md', '') - - // 映射工具名称 - const allowedTools = data.tools?.map((tool: string) => { - const mapping: Record = { - 'read': 'Read', - 'edit': 'Edit', - 'runCommands': 'Bash', - 'search/codebase': 'Grep' - } - return mapping[tool] || tool - }) - - // 处理 #file 引用 - 转换为 ${SKILL_ROOT}/references/ - const processedBody = body.replace( - /#file:'([^']+)'/g, - '${SKILL_ROOT}/references/$1' - ) - - return { - name, - description: data.description || '', - instructions: processedBody, - allowedTools, - model: data.model, - references: undefined, - scripts: undefined - } -} -``` - -### 5.4 导出转换 (DeepChat → Copilot) - -```typescript -function convertToCopilot(skill: DeepChatSkill): string { - const frontmatter: Record = {} - - if (skill.description) { - frontmatter.description = skill.description - } - - frontmatter.agent = 'agent' - - if (skill.model) { - frontmatter.model = skill.model - } - - // 映射工具名称 - if (skill.allowedTools?.length) { - const mapping: Record = { - 'Read': 'read', - 'Edit': 'edit', - 'Bash': 'runCommands', - 'Grep': 'search/codebase', - 'Glob': 'search/codebase' - } - frontmatter.tools = skill.allowedTools.map(t => mapping[t] || t) - } - - // 处理 references - 转换为 #file 引用 - let instructions = skill.instructions - if (skill.references?.length) { - instructions += '\n\n## References\n\n' - for (const ref of skill.references) { - instructions += `See #file:'${ref.relativePath}' for ${ref.name}\n` - } - } - - const yaml = Object.keys(frontmatter).length > 0 - ? `---\n${yamlDump(frontmatter)}---\n\n` - : '' - - return yaml + instructions -} -``` - -### 5.5 转换警告 - -导出到 GitHub Copilot 时: - -| 丢失内容 | 处理方式 | -|----------|----------| -| `scripts/` | 静默丢失,无法映射 | -| `references/` | 转换为 `#file` 引用(内容不会复制) | -| 部分工具 | 映射到最接近的 Copilot 工具 | - -导入到 DeepChat 时: - -| 转换内容 | 处理方式 | -|----------|----------| -| `#file` 引用 | 转换为 `${SKILL_ROOT}/references/` 路径 | -| 工具名称 | 映射到 DeepChat 工具名 | - -## 6. 参考资源 - -- [Prompt Files - VS Code Docs](https://code.visualstudio.com/docs/copilot/customization/prompt-files) -- [GitHub Copilot Chat Documentation](https://docs.github.com/en/copilot/github-copilot-chat) diff --git a/docs/archives/skills-system/skills-syncing-cursor.md b/docs/archives/skills-system/skills-syncing-cursor.md deleted file mode 100644 index 1bfccfc34..000000000 --- a/docs/archives/skills-system/skills-syncing-cursor.md +++ /dev/null @@ -1,184 +0,0 @@ -# Cursor Commands 格式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 本文档是 [skills-syncing.md](./skills-syncing.md) 的子文档,描述 Cursor Commands 的格式规格和转换规则。 - -## 1. 基本信息 - -| 属性 | 值 | -|------|-----| -| 工具名称 | Cursor | -| Commands 目录 | `.cursor/commands/` (项目级) | -| 文件模式 | `*.md` | -| 格式类型 | 纯结构化 Markdown | -| Frontmatter | **无** | - -## 2. 目录结构 - -``` -.cursor/commands/ -├── code-review.md -├── refactor.md -├── lint-suite.md -├── create-pr.md -└── optimize-performance.md -``` - -**特点**: -- 每个 Command 是**单个 Markdown 文件** -- 文件名即 Command 名称 -- 不支持子文件夹或附属资源 - -## 3. Command 格式 - -### 3.1 完整示例 - -```markdown -# Code Review - -Brief description of what this command does - reviews code changes according to team standards. - -## Objective - -Perform a thorough code review of the current changes, checking for: -- Code quality and readability -- Potential bugs and edge cases -- Performance implications -- Security vulnerabilities - -## Requirements - -- Follow the project's coding standards -- Check for proper error handling -- Verify test coverage for new code -- Ensure documentation is updated - -## Output - -Provide a structured review report with: -1. Summary of changes reviewed -2. List of issues found (categorized by severity) -3. Suggested improvements -4. Overall assessment -``` - -### 3.2 结构说明 - -| 部分 | 必需 | 说明 | -|------|------|------| -| `# Title` | ✅ 是 | 一级标题,Command 显示名称 | -| 首段描述 | ✅ 是 | 标题后的首段,简短描述用途 | -| `## Objective` | ❌ 否 | 详细说明任务目标和预期结果 | -| `## Requirements` | ❌ 否 | 具体要求、约束、编码标准 | -| `## Output` | ❌ 否 | 描述 AI 应该产出什么 | - -### 3.3 调用方式 - -在 Cursor 的 Agent 输入框中输入 `/` 触发命令选择: - -``` -> /code-review -> /refactor -> /lint-suite -``` - -## 4. 发现机制 - -Cursor 从以下位置发现 Commands: - -1. `.cursor/commands/` - 项目目录 -2. 全局 Commands 库(用户级) -3. Team Commands(通过 Cursor Dashboard 配置) - -## 5. 与 DeepChat 的转换 - -### 5.1 兼容性 - -| 能力 | Cursor | DeepChat | 转换 | -|------|:------:|:--------:|------| -| name | ✅ (从标题/文件名) | ✅ | 提取标题或文件名 | -| description | ✅ (从首段) | ✅ | 提取首段 | -| instructions | ✅ | ✅ | 合并所有 sections | -| allowedTools | ❌ | ✅ | 导出时丢失 | -| references/ | ❌ | ✅ | 可选择内联 | -| scripts/ | ❌ | ✅ | 导出时丢失 | - -### 5.2 导入转换 (Cursor → DeepChat) - -```typescript -function convertFromCursor(content: string, filename: string): DeepChatSkill { - const lines = content.split('\n') - - // 提取标题 - const titleMatch = lines.find(l => l.startsWith('# ')) - const name = titleMatch - ? titleMatch.replace('# ', '').toLowerCase().replace(/\s+/g, '-') - : filename.replace('.md', '') - - // 提取首段作为 description - const titleIndex = lines.findIndex(l => l.startsWith('# ')) - let description = '' - for (let i = titleIndex + 1; i < lines.length; i++) { - const line = lines[i].trim() - if (line === '') continue - if (line.startsWith('#')) break - description = line - break - } - - // 剩余内容作为 instructions - const instructions = content - - return { - name, - description, - instructions, - allowedTools: undefined, // Cursor 不支持 - references: undefined, - scripts: undefined - } -} -``` - -### 5.3 导出转换 (DeepChat → Cursor) - -```typescript -function convertToCursor(skill: DeepChatSkill): string { - const title = skill.name - .split('-') - .map(w => w.charAt(0).toUpperCase() + w.slice(1)) - .join(' ') - - let output = `# ${title}\n\n` - output += `${skill.description}\n\n` - output += `## Objective\n\n` - output += skill.instructions - - // 如果有 references,可选择内联 - if (skill.references?.length) { - output += `\n\n## References\n\n` - for (const ref of skill.references) { - output += `### ${ref.name}\n\n${ref.content}\n\n` - } - } - - return output -} -``` - -### 5.4 转换警告 - -导出到 Cursor 时,以下内容会丢失: - -| 丢失内容 | 处理方式 | -|----------|----------| -| `allowedTools` | 静默丢失,无法映射 | -| `scripts/` | 静默丢失,无法映射 | -| `references/` | 可选择内联到 `## References` 或丢失 | - -## 6. 参考资源 - -- [Commands - Cursor Docs](https://cursor.com/docs/agent/chat/commands) -- [Cursor Custom Slash Commands](https://github.com/hamzafer/cursor-commands) diff --git a/docs/archives/skills-system/skills-syncing-kiro.md b/docs/archives/skills-system/skills-syncing-kiro.md deleted file mode 100644 index 4e8c1e1c2..000000000 --- a/docs/archives/skills-system/skills-syncing-kiro.md +++ /dev/null @@ -1,286 +0,0 @@ -# Kiro Steering Files 格式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 本文档是 [skills-syncing.md](./skills-syncing.md) 的子文档,描述 Kiro Steering Files 的格式规格和转换规则。 - -## 1. 基本信息 - -| 属性 | 值 | -|------|-----| -| 工具名称 | Kiro | -| Steering 目录 | `.kiro/steering/` | -| 文件模式 | `*.md` | -| 格式类型 | YAML frontmatter + Markdown | -| Frontmatter | **可选** | - -## 2. 目录结构 - -``` -.kiro/steering/ -├── product.md # 产品概述(always included) -├── tech.md # 技术栈(always included) -├── structure.md # 项目结构(always included) -├── react-components.md # 条件包含 -├── api-patterns.md # 按需引用 -└── testing-guidelines.md # 按需引用 -``` - -**特点**: -- 每个 Steering File 是**单个 Markdown 文件** -- 文件名即 Steering File 名称 -- 不支持子文件夹结构 -- 支持三种**包含模式**(inclusion modes) -- 支持 `#filename` 引用语法 - -## 3. Steering File 格式 - -### 3.1 完整示例 - -**Always Inclusion(始终包含)**: -```markdown ---- -title: Product Overview -inclusion: always ---- - -# Product Overview - -This is a React-based e-commerce platform with the following features: -- User authentication -- Product catalog -- Shopping cart -- Order management -``` - -**Conditional Inclusion(条件包含)**: -```markdown ---- -title: React Component Guidelines -inclusion: conditional -file_patterns: ["*.jsx", "*.tsx", "src/components/**/*"] ---- - -# React Component Guidelines - -## Naming Conventions -- Use PascalCase for component names -- Use camelCase for props - -## Structure -- One component per file -- Co-locate styles and tests -``` - -**On-Demand(按需引用)**: -```markdown ---- -title: API Patterns ---- - -# API Patterns - -## REST Endpoints -- Use plural nouns for resources -- Use HTTP verbs correctly - -## Error Handling -- Return consistent error format -- Include error codes -``` - -### 3.2 Frontmatter 字段 - -| 字段 | 类型 | 必需 | 说明 | -|------|------|------|------| -| `title` | string | ❌ 否 | 显示名称,用于 `#filename` 引用 | -| `inclusion` | string | ❌ 否 | 包含模式:`always` 或 `conditional` | -| `file_patterns` | string[] | ❌ 否 | 仅在 `conditional` 模式下使用 | - -### 3.3 包含模式(Inclusion Modes) - -| 模式 | 触发条件 | 说明 | -|------|----------|------| -| `always` | 每次对话 | 始终包含在 AI 上下文中 | -| `conditional` | 文件匹配 | 当操作的文件匹配 `file_patterns` 时包含 | -| 无 `inclusion` | 手动引用 | 通过 `#filename` 显式引用 | - -### 3.4 file_patterns 语法 - -支持 glob 模式: - -```yaml -file_patterns: - - "*.jsx" # 所有 JSX 文件 - - "*.tsx" # 所有 TSX 文件 - - "src/components/**/*" # components 目录下所有文件 - - "**/*.test.ts" # 所有测试文件 -``` - -### 3.5 调用方式 - -**自动包含**: -- `always` 模式的文件自动包含 -- `conditional` 模式根据当前文件自动触发 - -**手动引用**: -在对话中使用 `#` 加文件名(不含 .md): -``` -Check #api-patterns for the REST conventions -Follow #react-components guidelines -``` - -## 4. 发现机制 - -Kiro 从以下位置发现 Steering Files: - -1. **项目目录**:`.kiro/steering/` -2. 递归扫描工作区内的 `.kiro/steering/` 目录 - -**注意**: -- 同名文件可能产生冲突 -- `always` 模式文件会增加每次对话的 token 消耗 - -## 5. 与 DeepChat 的转换 - -### 5.1 兼容性 - -| 能力 | Kiro | DeepChat | 转换 | -|------|:----:|:--------:|------| -| name | ✅ (title/filename) | ✅ | 提取 title 或文件名 | -| description | ❌ | ✅ | 导出时丢失 | -| instructions | ✅ | ✅ | Markdown body | -| allowedTools | ❌ | ✅ | 导出时丢失 | -| model | ❌ | ✅ | 导出时丢失 | -| references/ | ❌ | ✅ | 导出时丢失 | -| scripts/ | ❌ | ✅ | 导出时丢失 | -| inclusion | ✅ | ❌ | 导入时记录,导出时设置 | -| file_patterns | ✅ | ❌ | 导入时记录,导出时设置 | - -### 5.2 导入转换 (Kiro → DeepChat) - -```typescript -function convertFromKiro(content: string, filename: string): DeepChatSkill { - const { data, body } = grayMatter(content) - - // 从 title 或文件名提取 name - const name = data.title - ? data.title.toLowerCase().replace(/\s+/g, '-') - : filename.replace('.md', '') - - // 将 inclusion 信息嵌入到 instructions 开头 - let instructions = body - if (data.inclusion) { - const inclusionNote = `\n` - if (data.file_patterns?.length) { - instructions = `${inclusionNote}\n\n${body}` - } else { - instructions = `${inclusionNote}\n${body}` - } - } - - return { - name, - description: '', // Kiro 没有专门的 description 字段 - instructions, - allowedTools: undefined, - model: undefined, - references: undefined, - scripts: undefined, - // 保存 Kiro 特有信息用于后续导出 - _kiro: { - inclusion: data.inclusion, - filePatterns: data.file_patterns - } - } -} -``` - -### 5.3 导出转换 (DeepChat → Kiro) - -```typescript -interface KiroExportOptions { - inclusion?: 'always' | 'conditional' - filePatterns?: string[] -} - -function convertToKiro(skill: DeepChatSkill, options?: KiroExportOptions): string { - const frontmatter: Record = {} - - // 转换 name 为 title - const title = skill.name - .split('-') - .map(w => w.charAt(0).toUpperCase() + w.slice(1)) - .join(' ') - - frontmatter.title = title - - // 设置 inclusion 模式 - if (options?.inclusion) { - frontmatter.inclusion = options.inclusion - if (options.inclusion === 'conditional' && options.filePatterns?.length) { - frontmatter.file_patterns = options.filePatterns - } - } - - // 如果有 description,嵌入到 instructions 开头 - let instructions = skill.instructions - if (skill.description) { - instructions = `> ${skill.description}\n\n${instructions}` - } - - const yaml = Object.keys(frontmatter).length > 0 - ? `---\n${yamlDump(frontmatter)}---\n\n` - : '' - - return yaml + instructions -} -``` - -### 5.4 转换警告 - -导出到 Kiro 时: - -| 丢失内容 | 处理方式 | -|----------|----------| -| `description` | 嵌入到 instructions 开头(作为引用块) | -| `allowedTools` | 静默丢失 | -| `model` | 静默丢失 | -| `references/` | 静默丢失 | -| `scripts/` | 静默丢失 | - -导入到 DeepChat 时: - -| 转换内容 | 处理方式 | -|----------|----------| -| `inclusion` 模式 | 作为注释嵌入 instructions | -| `file_patterns` | 作为注释嵌入 instructions | -| 无 `description` | 设为空字符串 | - -### 5.5 导出选项 UI - -由于 Kiro 支持 inclusion 模式,导出时应提供选项: - -``` -┌────────────────────────────────────────────────────────────┐ -│ Kiro 导出选项 │ -├────────────────────────────────────────────────────────────┤ -│ │ -│ 包含模式: │ -│ ○ 按需引用 (默认,通过 #name 手动引用) │ -│ ○ 始终包含 (每次对话自动包含) │ -│ ○ 条件包含 (根据文件模式自动包含) │ -│ │ -│ 文件模式 (仅条件包含): │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ *.tsx, *.jsx, src/components/**/* │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ │ -└────────────────────────────────────────────────────────────┘ -``` - -## 6. 参考资源 - -- [Kiro Steering Documentation](https://kiro.dev/docs/steering/) -- [Kiro Getting Started](https://kiro.dev/docs/getting-started/) diff --git a/docs/archives/skills-system/skills-syncing-tasks.md b/docs/archives/skills-system/skills-syncing-tasks.md deleted file mode 100644 index 7486949c4..000000000 --- a/docs/archives/skills-system/skills-syncing-tasks.md +++ /dev/null @@ -1,430 +0,0 @@ -# Skills 跨工具同步开发任务清单 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 概述 - -本文档基于 [skills-syncing.md](./skills-syncing.md) 及其子文档制定,用于开发跟踪。 - -**预计工作量**:中等复杂度功能,涉及 Main/Renderer 双端开发 - -**支持的工具**: -- Claude Code - YAML frontmatter + Markdown(子文件夹结构) -- Cursor - 纯 Markdown(单文件) -- Windsurf - 步骤式 Markdown(单文件) -- GitHub Copilot - YAML frontmatter + Markdown(单文件) -- Kiro - YAML frontmatter + Markdown(单文件,支持 inclusion 模式) -- Antigravity - YAML frontmatter + Markdown(单文件,步骤式) - ---- - -## Phase 1: 类型定义与核心数据模型 - -### 1.1 共享类型定义 - -- [x] **1.1.1** 创建 `src/shared/types/skillSync.ts` 文件骨架 - ```typescript - // 包含:CanonicalSkill, SkillReference, SkillScript - // ExternalToolConfig, FormatCapabilities - // ScanResult, ExternalSkillInfo - // ImportPreview, ExportPreview - // ConflictStrategy, SyncResult - ``` - -- [x] **1.1.2** 定义 `CanonicalSkill` 中间格式接口 - - name, description, instructions - - allowedTools, model, tags - - references, scripts - - source 来源信息 - -- [x] **1.1.3** 定义 `ExternalToolConfig` 工具配置接口 - - id, name, skillsDir, filePattern, format - - capabilities (FormatCapabilities) - -- [x] **1.1.4** 定义 `FormatCapabilities` 格式能力接口 - - hasFrontmatter, supportsName, supportsDescription - - supportsTools, supportsModel - - supportsSubfolders, supportsReferences, supportsScripts - -- [x] **1.1.5** 定义同步操作相关类型 - - ScanResult, ExternalSkillInfo - - ImportPreview, ExportPreview - - ConflictStrategy (enum) - - SyncResult - -- [x] **1.1.6** 定义 `ISkillSyncPresenter` 接口 - - scanExternalTools(), scanTool() - - previewImport(), executeImport() - - previewExport(), executeExport() - - getRegisteredTools(), isToolAvailable() - -- [x] **1.1.7** 在 `src/shared/types/index.d.ts` 中导出 skillSync 类型 - ---- - -## Phase 2: Format Adapters 实现(插件化) - -### 2.1 适配器基础架构 - -- [x] **2.1.1** 创建 `src/main/presenter/skillSyncPresenter/adapters/` 目录结构 -- [x] **2.1.2** 定义 `IFormatAdapter` 接口 - ```typescript - interface IFormatAdapter { - id: string - name: string - parse(content: string, context: ParseContext): CanonicalSkill - serialize(skill: CanonicalSkill): string - detect(content: string): boolean - getCapabilities(): FormatCapabilities - } - ``` -- [x] **2.1.3** 创建 `adapters/index.ts` 适配器注册表 - -### 2.2 Claude Code 适配器 - -- [x] **2.2.1** 创建 `adapters/claudeCodeAdapter.ts` -- [x] **2.2.2** 实现 parse() - YAML frontmatter 解析 - - 处理 `allowed-tools` ↔ `allowedTools` 字段名转换 - - 支持字符串和数组两种 allowed-tools 格式 -- [x] **2.2.3** 实现 serialize() - 生成 SKILL.md 格式 -- [x] **2.2.4** 实现 references/ 和 scripts/ 子文件夹处理 -- [x] **2.2.5** 单元测试 - -### 2.3 Cursor 适配器 - -- [x] **2.3.1** 创建 `adapters/cursorAdapter.ts` -- [x] **2.3.2** 实现 parse() - 纯 Markdown 解析 - - 从 `# Title` 提取 name - - 从首段提取 description -- [x] **2.3.3** 实现 serialize() - 生成 Cursor 格式 - - 可选将 references 内联到 `## References` -- [x] **2.3.4** 单元测试 - -### 2.4 Windsurf 适配器 - -- [x] **2.4.1** 创建 `adapters/windsurfAdapter.ts` -- [x] **2.4.2** 实现 parse() - 步骤式 Markdown 解析 - - 从标题提取 name(去除 " Workflow" 后缀) - - 提取 `## Steps` 之前的描述 -- [x] **2.4.3** 实现 serialize() - 生成步骤式格式 - - 智能检测是否已有步骤结构 -- [x] **2.4.4** 单元测试 - -### 2.5 GitHub Copilot 适配器 - -- [x] **2.5.1** 创建 `adapters/copilotAdapter.ts` -- [x] **2.5.2** 实现 parse() - YAML frontmatter 解析 - - 工具名称映射(read → Read, runCommands → Bash 等) - - 处理 `#file:` 引用语法 -- [x] **2.5.3** 实现 serialize() - 生成 `.prompt.md` 格式 - - 反向工具名称映射 - - 将 references 转换为 `#file:` 引用 -- [x] **2.5.4** 单元测试 - -### 2.6 Kiro 适配器 - -- [x] **2.6.1** 创建 `adapters/kiroAdapter.ts` -- [x] **2.6.2** 实现 parse() - 处理 inclusion 模式 - - 保存 inclusion 和 file_patterns 信息 -- [x] **2.6.3** 实现 serialize() - 生成 Kiro 格式 - - 支持设置 inclusion 模式 - - 将 description 嵌入为引用块 -- [x] **2.6.4** 定义 Kiro 导出选项接口 -- [x] **2.6.5** 单元测试 - -### 2.7 Antigravity 适配器 - -- [x] **2.7.1** 创建 `adapters/antigravityAdapter.ts` -- [x] **2.7.2** 实现 parse() - 与 Windsurf 类似,但有 frontmatter -- [x] **2.7.3** 实现 serialize() - 生成步骤式格式 -- [x] **2.7.4** 单元测试 - ---- - -## Phase 3: 核心服务实现 - -### 3.1 ToolScanner 工具扫描器 - -- [x] **3.1.1** 创建 `src/main/presenter/skillSyncPresenter/toolScanner.ts` -- [x] **3.1.2** 实现工具配置注册表 - ```typescript - const EXTERNAL_TOOLS: ExternalToolConfig[] = [ - { id: 'claude-code', name: 'Claude Code', skillsDir: '~/.claude/skills/', ... }, - { id: 'cursor', name: 'Cursor', skillsDir: '.cursor/commands/', ... }, - // ... - ] - ``` -- [x] **3.1.3** 实现 `scanTool(toolId)` - 扫描单个工具目录 - - 检查目录是否存在 - - 根据 filePattern 匹配文件 - - 提取基本元信息 -- [x] **3.1.4** 实现 `scanExternalTools()` - 扫描所有已注册工具 -- [x] **3.1.5** 实现路径安全验证(防止路径遍历) -- [x] **3.1.6** 单元测试 - -### 3.2 FormatConverter 格式转换引擎 - -- [x] **3.2.1** 创建 `src/main/presenter/skillSyncPresenter/formatConverter.ts` -- [x] **3.2.2** 实现 `parseExternal()` - 根据格式选择适配器解析 -- [x] **3.2.3** 实现 `serializeToExternal()` - 序列化为外部格式 -- [x] **3.2.4** 实现 `serializeToSkillMd()` - 序列化为 DeepChat SKILL.md -- [x] **3.2.5** 实现 `getConversionWarnings()` - 获取转换警告 - - 检测功能丢失(如 allowedTools 导出到 Cursor) -- [x] **3.2.6** 单元测试 - -### 3.3 SkillSyncPresenter 主类 - -- [x] **3.3.1** 创建 `src/main/presenter/skillSyncPresenter/index.ts` 骨架 -- [x] **3.3.2** 创建 `src/main/presenter/skillSyncPresenter/types.ts` 内部类型 - -- [x] **3.3.3** 实现扫描功能 - - `scanExternalTools()` - 扫描所有外部工具 - - `scanTool(toolId)` - 扫描指定工具 - -- [x] **3.3.4** 实现导入功能 - - `previewImport()` - 预览导入,检测冲突 - - `executeImport()` - 执行导入,处理冲突策略 - - 调用 SkillPresenter.installFromFolder() - -- [x] **3.3.5** 实现导出功能 - - `previewExport()` - 预览导出,检测冲突 - - `executeExport()` - 执行导出,写入目标目录 - - 调用 SkillPresenter.loadSkillContent() - -- [x] **3.3.6** 实现工具查询 - - `getRegisteredTools()` - 获取所有已注册工具配置 - - `isToolAvailable()` - 检查工具目录是否存在 - -- [x] **3.3.7** 实现冲突处理逻辑 - - SKIP - 跳过 - - OVERWRITE - 覆盖 - - RENAME - 重命名(添加后缀) - -- [x] **3.3.8** 集成测试 - -### 3.4 Presenter 注册与 IPC - -- [x] **3.4.1** 在 `src/main/presenter/index.ts` 中注册 SkillSyncPresenter -- [x] **3.4.2** ~~在 `src/preload/presenter.ts` 中暴露 API~~ (不需要,使用动态路由) -- [x] **3.4.3** 更新 `src/shared/presenter.d.ts` 类型定义 - ---- - -## Phase 4: 事件系统 - -### 4.1 事件定义 - -- [x] **4.1.1** 在 `src/main/events.ts` 中添加 SKILL_SYNC_EVENTS - ```typescript - const SKILL_SYNC_EVENTS = { - SCAN_STARTED: 'skill-sync:scan-started', - SCAN_COMPLETED: 'skill-sync:scan-completed', - IMPORT_STARTED: 'skill-sync:import-started', - IMPORT_PROGRESS: 'skill-sync:import-progress', - IMPORT_COMPLETED: 'skill-sync:import-completed', - EXPORT_STARTED: 'skill-sync:export-started', - EXPORT_PROGRESS: 'skill-sync:export-progress', - EXPORT_COMPLETED: 'skill-sync:export-completed' - } - ``` - -- [x] **4.1.2** 在相应操作时发送事件 - ---- - -## Phase 5: UI 实现 - -### 5.1 组件目录结构 - -- [x] **5.1.1** 创建 `src/renderer/settings/components/skills/SkillSyncDialog/` 目录 -- [x] **5.1.2** 规划组件文件 - ``` - SkillSyncDialog/ - ├── SkillSyncDialog.vue # 同步向导主组件 - ├── ImportWizard.vue # 导入向导 - ├── ExportWizard.vue # 导出向导 - ├── ToolSelector.vue # 工具选择器 - ├── SkillSelector.vue # Skill 选择器 - ├── ConflictResolver.vue # 冲突处理 - └── SyncResult.vue # 结果展示 - ``` - -### 5.2 入口集成 - -- [x] **5.2.1** 修改 `SkillsHeader.vue`,添加"同步"下拉菜单 - - "从其他工具导入..." - - "导出到其他工具..." - -### 5.3 主对话框组件 - -- [x] **5.3.1** 创建 `SkillSyncDialog.vue` - - 管理导入/导出模式切换 - - 控制向导步骤流程 - -### 5.4 导入向导 - -- [x] **5.4.1** 创建 `ImportWizard.vue` - - Step 1: 选择来源工具 - - Step 2: 选择 Skills - - Step 3: 预览与冲突处理 - -- [x] **5.4.2** 创建 `ToolSelector.vue` - - 显示已检测到的工具列表 - - 显示每个工具的 Skills 数量 - - 支持自定义路径 - -- [x] **5.4.3** 创建 `SkillSelector.vue` - - 复选框列表选择 Skills - - 显示冲突警告标识 - - 支持全选/取消全选 - -- [x] **5.4.4** 创建 `ConflictResolver.vue` - - 单个冲突的处理选项(覆盖/跳过/重命名) - - 批量冲突处理 - -- [x] **5.4.5** 导入流程进度展示 - -### 5.5 导出向导 - -- [x] **5.5.1** 创建 `ExportWizard.vue` - - Step 1: 选择要导出的 Skills - - Step 2: 选择目标工具 - - Step 3: 预览与确认 - -- [x] **5.5.2** 实现转换警告展示 - - 显示功能丢失警告 - - 显示转换后内容预览 - -- [x] **5.5.3** 实现 Kiro 特殊导出选项 - - inclusion 模式选择 - - file_patterns 输入 - -### 5.6 结果展示 - -- [x] **5.6.1** 创建 `SyncResult.vue` - - 成功/跳过/失败统计 - - 详细列表展示 - ---- - -## Phase 6: 国际化 - -### 6.1 i18n 键值 - -- [x] **6.1.1** 添加中文 i18n keys (`zh-CN`) - ```json - { - "settings.skills.sync": "同步", - "settings.skills.sync.import": "从其他工具导入...", - "settings.skills.sync.export": "导出到其他工具...", - // ... 完整键值见 skills-syncing.md 第 6 节 - } - ``` - -- [x] **6.1.2** 添加英文 i18n keys (`en-US`) - -- [x] **6.1.3** 运行 `pnpm run i18n` 检查完整性 - ---- - -## Phase 7: 安全与测试 - -### 7.1 安全验证 - -- [x] **7.1.1** 实现路径安全验证 - - 防止路径遍历攻击(../) - - 验证目录在预期范围内 - -- [x] **7.1.2** 实现内容安全 - - 限制文件大小 - - YAML 解析使用安全选项 - -- [x] **7.1.3** 实现权限检查 - - 导出时检查目标目录写权限 - - 导入时检查源目录读权限 - -### 7.2 单元测试 - -- [x] **7.2.1** Format Adapters 测试(各适配器) -- [x] **7.2.2** ToolScanner 测试 -- [x] **7.2.3** FormatConverter 测试 -- [x] **7.2.4** SkillSyncPresenter 测试 - -### 7.3 集成测试 - -- [x] **7.3.1** 完整导入流程测试 -- [x] **7.3.2** 完整导出流程测试 -- [x] **7.3.3** 冲突处理测试 - -### 7.4 代码质量 - -- [x] **7.4.1** 运行 `pnpm run format && pnpm run lint && pnpm run typecheck` -- [ ] **7.4.2** 代码审查 - ---- - -## 依赖关系 - -``` -Phase 1 (类型定义) - │ - └── 1.1 共享类型 ───────────────────────────────────────────────┐ - │ -Phase 2 (Format Adapters) │ - │ │ - ├── 2.1 适配器基础架构 ◄─────────────────────────────────────────┤ - │ │ - ├── 2.2 Claude Code 适配器 ◄────────────────────────────────────┤ - ├── 2.3 Cursor 适配器 ◄─────────────────────────────────────────┤ - ├── 2.4 Windsurf 适配器 ◄───────────────────────────────────────┤ - ├── 2.5 Copilot 适配器 ◄────────────────────────────────────────┤ - ├── 2.6 Kiro 适配器 ◄───────────────────────────────────────────┤ - └── 2.7 Antigravity 适配器 ◄────────────────────────────────────┘ - │ - ▼ -Phase 3 (核心服务) - │ - ├── 3.1 ToolScanner ◄── Phase 2 - ├── 3.2 FormatConverter ◄── Phase 2 - ├── 3.3 SkillSyncPresenter ◄── 3.1, 3.2 - └── 3.4 IPC 注册 ◄── 3.3 - │ - ▼ -Phase 4 (事件系统) ◄── Phase 3 - │ - ▼ -Phase 5 (UI 实现) - │ - ├── 5.1-5.2 目录结构与入口 - ├── 5.3 主对话框 - ├── 5.4 导入向导 - ├── 5.5 导出向导 - └── 5.6 结果展示 - │ - ▼ -Phase 6 (国际化) ◄── Phase 5 - │ - ▼ -Phase 7 (安全与测试) ◄── All Phases -``` - ---- - -## 里程碑 - -| 里程碑 | 完成标准 | -|--------|----------| -| **M1: 类型与适配器** | 所有 Format Adapters 实现并通过测试 | -| **M2: 核心服务** | SkillSyncPresenter 完成,能扫描、导入、导出 | -| **M3: UI 完成** | 导入/导出向导可用,冲突处理完善 | -| **M4: 发布就绪** | i18n 完成,安全验证通过,测试覆盖 | - ---- - -## 备注 - -- 开发过程中如有设计变更,及时更新 skills-syncing.md 和相关子文档 -- 每个任务完成后在本文档标记 `[x]` -- 建议按 Phase 顺序推进,Phase 内可并行 -- 适配器开发可并行进行,由不同开发者负责 diff --git a/docs/archives/skills-system/skills-syncing-windsurf.md b/docs/archives/skills-system/skills-syncing-windsurf.md deleted file mode 100644 index 85a24ef6f..000000000 --- a/docs/archives/skills-system/skills-syncing-windsurf.md +++ /dev/null @@ -1,231 +0,0 @@ -# Windsurf Workflows 格式规格 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 本文档是 [skills-syncing.md](./skills-syncing.md) 的子文档,描述 Windsurf Workflows 的格式规格和转换规则。 - -## 1. 基本信息 - -| 属性 | 值 | -|------|-----| -| 工具名称 | Windsurf | -| Workflows 目录 | `.windsurf/workflows/` | -| 文件模式 | `*.md` | -| 格式类型 | 步骤式 Markdown | -| Frontmatter | **无** | - -## 2. 目录结构 - -``` -.windsurf/workflows/ -├── code-review.md -├── deploy.md -├── pr-review.md -└── run-tests.md -``` - -**特点**: -- 每个 Workflow 是**单个 Markdown 文件** -- 文件名即 Workflow 名称(用于 `/` 调用) -- 不支持子文件夹或附属资源 -- 支持多级目录发现 - -## 3. Workflow 格式 - -### 3.1 完整示例 - -```markdown -# Code Review Workflow - -A workflow to systematically review code changes and provide structured feedback. - -## Steps - -### 1. Get the Changes - -Run the following command to see all changes: -```bash -git diff HEAD~1 -``` - -### 2. Analyze Each File - -For each changed file: -- Check code style and formatting -- Look for potential bugs -- Verify error handling -- Check for security issues - -### 3. Check Test Coverage - -Ensure new code has appropriate tests: -```bash -npm run test:coverage -``` - -### 4. Generate Review Report - -Output a structured report with: -- Summary of changes -- Issues found (by severity) -- Recommendations -- Overall assessment -``` - -### 3.2 结构说明 - -| 部分 | 必需 | 说明 | -|------|------|------| -| `# Title` | ✅ 是 | 一级标题,Workflow 显示名称 | -| 描述段落 | ✅ 是 | 标题后的描述文本 | -| `## Steps` | ✅ 是 | 步骤容器 | -| `### N. Step Name` | ✅ 是 | 编号步骤,按顺序执行 | - -### 3.3 调用方式 - -在 Cascade 中输入 `/` 加 workflow 名称: - -``` -> /code-review -> /deploy -> /pr-review -``` - -## 4. 发现机制 - -Windsurf 自动从多个位置发现 Workflows: - -1. **当前工作区**:`.windsurf/workflows/` -2. **子目录**:递归搜索所有 `.windsurf/workflows/` -3. **Git 仓库根目录**:向上搜索到 git root -4. **多工作区**:去重并显示最短相对路径 - -**企业功能**: -- 系统级 Workflows(全局可用,不可修改) -- 管理员权限控制 - -## 5. 高级特性 - -### 5.1 Workflow 链式调用 - -Workflows 可以调用其他 Workflows: - -```markdown -# Full CI Pipeline - -## Steps - -### 1. Run Linting -Call /lint-check - -### 2. Run Tests -Call /run-tests - -### 3. Deploy -Call /deploy-staging -``` - -### 5.2 常见用例 - -- 代码格式化(Prettier, Black) -- Linting(ESLint, Flake8) -- 单元测试和 E2E 测试 -- 部署流程 -- 安全漏洞扫描 - -## 6. 与 DeepChat 的转换 - -### 6.1 兼容性 - -| 能力 | Windsurf | DeepChat | 转换 | -|------|:--------:|:--------:|------| -| name | ✅ (从标题/文件名) | ✅ | 提取标题或文件名 | -| description | ✅ (从首段) | ✅ | 提取首段 | -| instructions | ✅ (Steps) | ✅ | Steps 作为 instructions | -| allowedTools | ❌ | ✅ | 导出时丢失 | -| model | ❌ | ✅ | 导出时丢失 | -| references/ | ❌ | ✅ | 可选择内联 | -| scripts/ | ❌ | ✅ | 导出时丢失 | - -### 6.2 导入转换 (Windsurf → DeepChat) - -```typescript -function convertFromWindsurf(content: string, filename: string): DeepChatSkill { - const lines = content.split('\n') - - // 提取标题 - const titleMatch = lines.find(l => l.startsWith('# ')) - const name = titleMatch - ? titleMatch.replace('# ', '').replace(' Workflow', '').toLowerCase().replace(/\s+/g, '-') - : filename.replace('.md', '') - - // 提取描述(标题和 ## Steps 之间的内容) - const titleIndex = lines.findIndex(l => l.startsWith('# ')) - const stepsIndex = lines.findIndex(l => l.startsWith('## Steps')) - - let description = '' - for (let i = titleIndex + 1; i < stepsIndex; i++) { - const line = lines[i].trim() - if (line && !line.startsWith('#')) { - description = line - break - } - } - - // Steps 内容作为 instructions - const instructions = stepsIndex >= 0 - ? lines.slice(stepsIndex).join('\n') - : content - - return { - name, - description, - instructions, - allowedTools: undefined, - references: undefined, - scripts: undefined - } -} -``` - -### 6.3 导出转换 (DeepChat → Windsurf) - -```typescript -function convertToWindsurf(skill: DeepChatSkill): string { - const title = skill.name - .split('-') - .map(w => w.charAt(0).toUpperCase() + w.slice(1)) - .join(' ') - - let output = `# ${title} Workflow\n\n` - output += `${skill.description}\n\n` - output += `## Steps\n\n` - - // 检查 instructions 是否已有步骤结构 - if (skill.instructions.includes('### 1.') || skill.instructions.includes('### Step 1')) { - output += skill.instructions - } else { - // 整体作为单个步骤 - output += `### 1. Execute\n\n${skill.instructions}` - } - - return output -} -``` - -### 6.4 转换警告 - -导出到 Windsurf 时,以下内容会丢失: - -| 丢失内容 | 处理方式 | -|----------|----------| -| `allowedTools` | 静默丢失 | -| `model` | 静默丢失 | -| `scripts/` | 静默丢失 | -| `references/` | 可内联到步骤中或丢失 | - -## 7. 参考资源 - -- [Workflows - Windsurf Docs](https://docs.windsurf.com/windsurf/cascade/workflows) -- [Windsurf Workflows Guide](https://www.kzsoftworks.com/blog/windsurf-workflows-from-prompt-chaos-to-productive-focus) diff --git a/docs/archives/skills-system/skills-syncing.md b/docs/archives/skills-system/skills-syncing.md deleted file mode 100644 index b90647c70..000000000 --- a/docs/archives/skills-system/skills-syncing.md +++ /dev/null @@ -1,721 +0,0 @@ -# Skills 跨工具同步设计文档 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 概述 - -### 1.1 背景 - -不同的 AI Agent 工具都有自己的 Skills/Commands/Workflows 系统,但格式和存储位置各不相同。用户在多个工具之间切换时,希望能够复用已有的 Skills。 - -### 1.2 设计目标 - -| 目标 | 描述 | 优先级 | -|------|------|--------| -| **双向同步** | 支持从其他工具导入和导出到其他工具 | P0 | -| **格式转换** | 自动处理不同工具间的格式差异 | P0 | -| **冲突处理** | 提供清晰的冲突解决机制 | P0 | -| **批量操作** | 支持一次性同步多个 Skills | P1 | -| **增量同步** | 仅同步有变化的 Skills | P2 | - -### 1.3 非目标 - -- 实时双向自动同步(需用户主动触发) -- 跨工具的 Skill 依赖解析 -- 云端同步服务 -- 工具间的功能完全对等(部分工具功能可能无法完全转换) - -### 1.4 支持的工具 - -| 工具 | 规格文档 | -|------|----------| -| Claude Code | [skills-syncing-claude-code.md](./skills-syncing-claude-code.md) | -| Cursor | [skills-syncing-cursor.md](./skills-syncing-cursor.md) | -| Windsurf | [skills-syncing-windsurf.md](./skills-syncing-windsurf.md) | -| Antigravity | [skills-syncing-antigravity.md](./skills-syncing-antigravity.md) | -| GitHub Copilot | [skills-syncing-copilot.md](./skills-syncing-copilot.md) | -| Kiro | [skills-syncing-kiro.md](./skills-syncing-kiro.md) | - ---- - -## 2. 数据模型 - -### 2.1 统一中间格式 (Canonical Skill Format) - -所有工具的 Skill 在转换时,先转为统一的中间格式: - -```typescript -/** - * 统一的 Skill 中间格式 - * 用于在不同工具格式之间转换 - */ -interface CanonicalSkill { - // 基础元数据 - name: string // 唯一标识符 - description: string // 描述文本 - - // 内容 - instructions: string // 主要指令内容(Markdown) - - // 可选元数据 - allowedTools?: string[] // 工具限制 - model?: string // 指定模型 - tags?: string[] // 标签分类 - - // 附属资源 - references?: SkillReference[] // 参考文档 - scripts?: SkillScript[] // 脚本文件 - - // 来源信息 - source?: { - tool: string // 来源工具标识 - originalPath: string // 原始路径 - originalFormat: string // 原始格式 - } -} - -interface SkillReference { - name: string // 文件名 - content: string // 文件内容 - relativePath: string // 相对路径 -} - -interface SkillScript { - name: string // 脚本名 - content: string // 脚本内容 - relativePath: string // 相对路径 -} -``` - -### 2.2 外部工具配置 - -```typescript -/** - * 外部工具配置接口 - * 每个工具的具体配置在对应的子文档中定义 - */ -interface ExternalToolConfig { - id: string // 工具唯一标识 - name: string // 显示名称 - skillsDir: string // 相对于 HOME 的路径 - filePattern: string // 文件匹配模式 (glob) - format: string // 文件格式类型 - capabilities: FormatCapabilities // 格式能力 -} - -/** - * 格式能力定义 - */ -interface FormatCapabilities { - hasFrontmatter: boolean // 是否有 YAML frontmatter - supportsName: boolean // 支持 name 字段 - supportsDescription: boolean // 支持 description 字段 - supportsTools: boolean // 支持工具限制 - supportsModel: boolean // 支持模型指定 - supportsSubfolders: boolean // 支持子文件夹结构 - supportsReferences: boolean // 支持 references/ - supportsScripts: boolean // 支持 scripts/ -} -``` - -### 2.3 同步操作类型 - -```typescript -/** - * 扫描结果 - */ -interface ScanResult { - toolId: string - toolName: string - available: boolean // 目录是否存在 - skillsDir: string // 完整路径 - skills: ExternalSkillInfo[] // 发现的 Skills - error?: string // 扫描错误 -} - -interface ExternalSkillInfo { - name: string - description?: string - path: string // 文件/文件夹路径 - format: string // 检测到的格式 - lastModified: Date // 最后修改时间 -} - -/** - * 导入预览 - */ -interface ImportPreview { - skill: CanonicalSkill // 转换后的 Skill - source: ExternalSkillInfo // 来源信息 - conflict?: { - existingSkill: SkillMetadata // 已存在的同名 Skill - strategy: ConflictStrategy // 建议的处理策略 - } - warnings: string[] // 转换警告(如丢失功能) -} - -/** - * 导出预览 - */ -interface ExportPreview { - skill: SkillMetadata // 要导出的 Skill - targetTool: string // 目标工具 ID - targetPath: string // 目标路径 - convertedContent: string // 转换后的内容预览 - warnings: string[] // 转换警告 - conflict?: { - existingPath: string // 已存在的文件 - strategy: ConflictStrategy - } -} - -/** - * 冲突处理策略 - */ -enum ConflictStrategy { - SKIP = 'skip', // 跳过 - OVERWRITE = 'overwrite', // 覆盖 - RENAME = 'rename', // 重命名(添加后缀) - MERGE = 'merge' // 合并(仅适用于部分场景) -} - -/** - * 同步操作结果 - */ -interface SyncResult { - success: boolean - imported: number // 成功导入数量 - exported: number // 成功导出数量 - skipped: number // 跳过数量 - failed: Array<{ - skill: string - reason: string - }> -} -``` - ---- - -## 3. 架构设计 - -### 3.1 模块架构 - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ SkillSyncPresenter │ -│ 统一的同步协调器,管理导入/导出流程 │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌───────────────────┐ ┌───────────────────┐ │ -│ │ ToolScanner │ │ FormatConverter │ │ -│ │ 扫描外部工具目录 │ │ 格式转换引擎 │ │ -│ └─────────┬─────────┘ └─────────┬─────────┘ │ -│ │ │ │ -│ ▼ ▼ │ -│ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ Format Adapters (插件化) │ │ -│ │ 每个外部工具一个 Adapter,负责解析和序列化该工具的格式 │ │ -│ └─────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ - │ - │ 调用 - ▼ -┌─────────────────────────────────────────────────────────────────────┐ -│ SkillPresenter │ -│ 现有的 Skill 管理器(安装/卸载/读写) │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -### 3.2 核心组件接口 - -#### SkillSyncPresenter - -同步功能的主入口,协调扫描、转换和写入: - -```typescript -interface ISkillSyncPresenter { - // 扫描 - scanExternalTools(): Promise - scanTool(toolId: string): Promise - - // 导入(从外部工具 → DeepChat) - previewImport(toolId: string, skillNames: string[]): Promise - executeImport(previews: ImportPreview[], strategies: Record): Promise - - // 导出(从 DeepChat → 外部工具) - previewExport(skillNames: string[], targetToolId: string): Promise - executeExport(previews: ExportPreview[], strategies: Record): Promise - - // 工具配置 - getRegisteredTools(): ExternalToolConfig[] - isToolAvailable(toolId: string): Promise -} -``` - -#### FormatConverter - -格式转换引擎,处理不同格式间的转换: - -```typescript -interface IFormatConverter { - // 解析外部格式 → CanonicalSkill - parseExternal(content: string, format: string, context: ParseContext): CanonicalSkill - - // CanonicalSkill → 外部格式 - serializeToExternal(skill: CanonicalSkill, targetToolId: string): string - - // CanonicalSkill → DeepChat SKILL.md - serializeToSkillMd(skill: CanonicalSkill): string - - // 获取转换警告 - getConversionWarnings(skill: CanonicalSkill, targetToolId: string): string[] -} - -interface ParseContext { - toolId: string - filePath: string - folderPath?: string // 对于支持子文件夹的工具 -} -``` - -#### Format Adapter - -每种格式的具体转换实现(插件化): - -```typescript -interface IFormatAdapter { - // 适配器标识 - readonly id: string - readonly name: string - - // 解析 - parse(content: string, context: ParseContext): CanonicalSkill - - // 序列化 - serialize(skill: CanonicalSkill): string - - // 检测格式 - detect(content: string): boolean - - // 获取功能限制 - getCapabilities(): FormatCapabilities -} -``` - -### 3.3 数据流 - -#### 导入流程 - -``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ 用户选择 │ │ 扫描外部 │ │ 格式转换 │ │ 冲突检测 │ -│ 外部工具 │────▶│ 工具目录 │────▶│ → Canonical │────▶│ & 预览 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ - ▼ -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ 刷新 UI │◀────│ 写入到 │◀────│ Canonical │◀────│ 用户确认 │ -│ 显示结果 │ │ DeepChat │ │ → SKILL.md │ │ 冲突策略 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ -``` - -#### 导出流程 - -``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ 用户选择 │ │ 读取 │ │ 格式转换 │ │ 冲突检测 │ -│ Skills & │────▶│ DeepChat │────▶│ → 目标格式 │────▶│ & 预览 │ -│ 目标工具 │ │ Skills │ │ │ │ │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ - ▼ -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ 显示结果 │◀────│ 写入到 │◀────│ 用户确认 │◀────│ 显示转换 │ -│ │ │ 目标目录 │ │ 冲突策略 │ │ 警告 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ -``` - ---- - -## 4. 交互设计 - -### 4.1 UI 入口 - -在 Skills 设置页面添加同步功能: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Skills Settings │ -├─────────────────────────────────────────────────────────────────┤ -│ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ Header │ │ -│ │ ┌─────────────────┐ ┌──────────┐ ┌──────────┐ ┌───────┐ │ │ -│ │ │ 🔍 Search... │ │ 同步 ▾ │ │ 导入 ▾ │ │+ 安装 │ │ │ -│ │ └─────────────────┘ └──────────┘ └──────────┘ └───────┘ │ │ -│ │ ┌───────────────────────┐ │ │ -│ │ │ 从其他工具导入... │ │ │ -│ │ │ 导出到其他工具... │ │ │ -│ │ └───────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────┘ │ -│ │ -│ [Skills 卡片网格...] │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 4.2 导入向导流程 - -**Step 1: 选择来源工具** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ 从其他工具导入 Skills ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ 选择要导入的工具: │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ○ [工具名称] [目录路径] │ │ -│ │ ✓ 已检测到 N 个 Skills │ │ -│ ├────────────────────────────────────────────────────────────┤ │ -│ │ ○ [工具名称] [目录路径] │ │ -│ │ ⚠ 目录不存在 │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ○ 自定义路径... │ │ -│ │ 选择包含 Skills 的文件夹 │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [取消] [下一步] │ -└──────────────────────────────────────────────────────────────────┘ -``` - -**Step 2: 选择 Skills** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ 从 [工具名称] 导入 ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌────────────────────────┐ │ -│ │ ☐ 全选 (N) │ │ -│ └────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ☑ [skill-name] │ │ -│ │ [skill description] │ │ -│ │ ⚠ 已存在同名 Skill (如有冲突) │ │ -│ ├────────────────────────────────────────────────────────────┤ │ -│ │ ☑ [skill-name] │ │ -│ │ [skill description] │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ 已选择 N 个,其中 M 个存在冲突 │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [上一步] [取消] [下一步] │ -└──────────────────────────────────────────────────────────────────┘ -``` - -**Step 3: 预览与冲突处理** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ 确认导入 ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ 即将导入 N 个 Skills: │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ✓ [skill-name] │ │ -│ │ ┌──────────────────────────────────────────────────────┐ │ │ -│ │ │ ⚠ 冲突: 已存在同名 Skill │ │ │ -│ │ │ │ │ │ -│ │ │ 处理方式: ○ 覆盖 ○ 跳过 ● 重命名为 xxx-1 │ │ │ -│ │ └──────────────────────────────────────────────────────┘ │ │ -│ ├────────────────────────────────────────────────────────────┤ │ -│ │ ✓ [skill-name] │ │ -│ │ 无冲突,将直接导入 │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ℹ️ 转换说明 │ │ -│ │ • [转换说明列表,根据源工具动态生成] │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [上一步] [取消] [导入] │ -└──────────────────────────────────────────────────────────────────┘ -``` - -### 4.3 导出向导流程 - -**Step 1: 选择要导出的 Skills** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ 导出 Skills 到其他工具 ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ 选择要导出的 Skills: │ -│ │ -│ ┌────────────────────────┐ │ -│ │ ☐ 全选 (N) │ │ -│ └────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ☑ [skill-name] │ │ -│ │ [skill description] │ │ -│ │ 📁 包含 references/, scripts/ (如有) │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ 已选择 N 个 Skills │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [取消] [下一步] │ -└──────────────────────────────────────────────────────────────────┘ -``` - -**Step 2: 选择目标工具** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ 选择目标工具 ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ 导出到: │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ○ [工具名称] [目录路径] │ │ -│ │ ✓ 完全兼容 / ⚠ [丢失功能列表] │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ ○ 自定义路径... │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [上一步] [取消] [下一步] │ -└──────────────────────────────────────────────────────────────────┘ -``` - -**Step 3: 预览与确认** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ 确认导出到 [工具名称] ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ 即将导出 N 个 Skills 到 [目标路径] │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ [skill-name] → [目标文件名] │ │ -│ │ ┌──────────────────────────────────────────────────────┐ │ │ -│ │ │ ℹ️ 转换说明: │ │ │ -│ │ │ • [转换说明列表] │ │ │ -│ │ └──────────────────────────────────────────────────────┘ │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 预览: [目标文件名] │ │ -│ │ ┌──────────────────────────────────────────────────────┐ │ │ -│ │ │ [转换后的内容预览] │ │ │ -│ │ └──────────────────────────────────────────────────────┘ │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [上一步] [取消] [导出] │ -└──────────────────────────────────────────────────────────────────┘ -``` - -### 4.4 结果反馈 - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ [导入/导出]完成 ✕ │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ ✓ [操作]成功 │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 成功: N │ │ -│ │ • [skill-name] │ │ -│ │ • [skill-name] │ │ -│ │ │ │ -│ │ 跳过: M │ │ -│ │ • [skill-name] (原因) │ │ -│ │ │ │ -│ │ 失败: K │ │ -│ │ • [skill-name] (错误原因) │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────────────────────────────────┤ -│ [完成] │ -└──────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 5. 与现有系统集成 - -### 5.1 与 SkillPresenter 集成 - -``` -SkillSyncPresenter SkillPresenter - │ │ - │ scanExternalTools() │ - │ ──────────────────────────────────▶│ (不调用) - │ │ - │ executeImport() │ - │ ──────────────────────────────────▶│ installFromFolder() - │ │ - │ executeExport() │ - │ ──────────────────────────────────▶│ loadSkillContent() - │ │ getSkillFolderTree() - │ │ -``` - -### 5.2 事件定义 - -```typescript -const SKILL_SYNC_EVENTS = { - SCAN_STARTED: 'skill-sync:scan-started', - SCAN_COMPLETED: 'skill-sync:scan-completed', - IMPORT_STARTED: 'skill-sync:import-started', - IMPORT_PROGRESS: 'skill-sync:import-progress', - IMPORT_COMPLETED: 'skill-sync:import-completed', - EXPORT_STARTED: 'skill-sync:export-started', - EXPORT_PROGRESS: 'skill-sync:export-progress', - EXPORT_COMPLETED: 'skill-sync:export-completed' -} -``` - -### 5.3 文件结构 - -``` -src/main/presenter/ -├── skillPresenter/ -│ └── index.ts # 现有 Skill 管理 -└── skillSyncPresenter/ - ├── index.ts # SkillSyncPresenter 主类 - ├── types.ts # 类型定义 - ├── toolScanner.ts # 工具扫描器 - ├── formatConverter.ts # 格式转换引擎 - └── adapters/ # 格式适配器(插件化) - └── index.ts # 适配器注册表 - -src/renderer/settings/components/skills/ -├── SkillsSettings.vue # 添加同步按钮 -├── SkillSyncDialog/ -│ ├── SkillSyncDialog.vue # 同步向导主组件 -│ ├── ImportWizard.vue # 导入向导 -│ ├── ExportWizard.vue # 导出向导 -│ ├── ToolSelector.vue # 工具选择器 -│ ├── SkillSelector.vue # Skill 选择器 -│ ├── ConflictResolver.vue # 冲突处理 -│ └── SyncResult.vue # 结果展示 - -src/shared/types/ -└── skillSync.ts # 共享类型定义 -``` - ---- - -## 6. 国际化 - -```json -{ - "settings.skills.sync": "同步", - "settings.skills.sync.import": "从其他工具导入...", - "settings.skills.sync.export": "导出到其他工具...", - - "settings.skills.sync.import.title": "从其他工具导入 Skills", - "settings.skills.sync.import.selectTool": "选择要导入的工具:", - "settings.skills.sync.import.detected": "已检测到 {count} 个 Skills", - "settings.skills.sync.import.notFound": "目录不存在", - "settings.skills.sync.import.customPath": "自定义路径...", - "settings.skills.sync.import.selectSkills": "选择要导入的 Skills", - "settings.skills.sync.import.selectAll": "全选 ({count})", - "settings.skills.sync.import.conflict": "已存在同名 Skill", - "settings.skills.sync.import.selected": "已选择 {count} 个,其中 {conflicts} 个存在冲突", - - "settings.skills.sync.export.title": "导出 Skills 到其他工具", - "settings.skills.sync.export.selectSkills": "选择要导出的 Skills", - "settings.skills.sync.export.selectTarget": "选择目标工具", - "settings.skills.sync.export.compatible": "完全兼容", - "settings.skills.sync.export.warning": "{feature} 将丢失", - - "settings.skills.sync.conflict.title": "冲突处理", - "settings.skills.sync.conflict.overwrite": "覆盖", - "settings.skills.sync.conflict.skip": "跳过", - "settings.skills.sync.conflict.rename": "重命名为 {name}", - - "settings.skills.sync.preview.title": "确认{action}", - "settings.skills.sync.preview.converting": "转换说明", - "settings.skills.sync.preview.warnings": "警告", - - "settings.skills.sync.result.title": "{action}完成", - "settings.skills.sync.result.success": "成功: {count}", - "settings.skills.sync.result.skipped": "跳过: {count}", - "settings.skills.sync.result.failed": "失败: {count}", - - "settings.skills.sync.action.import": "导入", - "settings.skills.sync.action.export": "导出", - "settings.skills.sync.button.next": "下一步", - "settings.skills.sync.button.prev": "上一步", - "settings.skills.sync.button.done": "完成" -} -``` - ---- - -## 7. 安全考虑 - -### 7.1 路径安全 - -- 所有路径操作需验证在预期目录范围内 -- 防止路径遍历攻击(../) -- 验证外部工具目录确实存在且可读 - -### 7.2 内容安全 - -- 解析外部文件时限制文件大小 -- YAML 解析使用安全选项(禁用不安全的类型) -- 不执行任何脚本内容,仅复制 - -### 7.3 权限 - -- 导出时检查目标目录写权限 -- 导入时检查源目录读权限 -- 失败时提供明确错误信息 - ---- - -## 8. 未来扩展 - -### 8.1 增量同步 - -- 基于文件修改时间检测变化 -- 仅同步有更新的 Skills -- 提供"上次同步时间"记录 - -### 8.2 自动同步 - -- 可选的文件监控模式 -- 检测到外部工具 Skills 变化时通知用户 -- 提供一键更新 - -### 8.3 更多工具支持 - -- 插件化的适配器架构 -- 用户可添加自定义工具配置 -- 社区贡献的适配器 - -### 8.4 云同步 - -- 可选的云端备份 -- 跨设备同步 -- 团队共享 diff --git a/docs/archives/skills-system/ui-design.md b/docs/archives/skills-system/ui-design.md deleted file mode 100644 index f682ba5bc..000000000 --- a/docs/archives/skills-system/ui-design.md +++ /dev/null @@ -1,549 +0,0 @@ -# DeepChat Skills 系统 UI 设计文档 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 1. 概述 - -本文档描述 Skills 系统的用户界面设计,基于 DeepChat 现有 UI 模式和组件库。 - -### 1.1 设计原则 - -- **一致性**:遵循现有 MCP、Custom Prompts 等功能的 UI 模式 -- **简洁性**:卡片网格展示,侧边栏编辑 -- **复用性**:使用 shadcn/ui 组件库 - -### 1.2 UI 入口 - -Skills 管理页面作为设置窗口的新页面,位于 MCP 和 Prompt 之间: - -| 路由 | 组件 | 图标 | 位置 | -|------|------|------|------| -| `/skills` | SkillsSettings | `lucide:sparkles` | 5 (MCP Market 之后) | - ---- - -## 2. 页面结构 - -### 2.1 整体布局 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Skills Settings │ -├─────────────────────────────────────────────────────────────────┤ -│ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ Header │ │ -│ │ ┌─────────────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ 🔍 Search... │ │ 导入 ▾ │ │ + 安装 │ │ │ -│ │ └─────────────────┘ └──────────┘ └──────────┘ │ │ -│ └─────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ ScrollArea │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ Skill │ │ Skill │ │ Skill │ │ Skill │ │ │ -│ │ │ Card │ │ Card │ │ Card │ │ Card │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ Skill │ │ Skill │ │ │ -│ │ │ Card │ │ Card │ │ │ -│ │ └──────────┘ └──────────┘ │ │ -│ │ │ │ -│ └─────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ Footer: 共 6 个 Skills | 打开 Skills 文件夹 │ │ -│ └─────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 2.2 组件层次 - -``` -SkillsSettings.vue -├── SkillsHeader.vue -│ ├── Input (搜索) -│ ├── DropdownMenu (导入) -│ │ ├── 从文件夹安装 -│ │ ├── 从 ZIP 安装 -│ │ └── 从 URL 安装 -│ └── Button (安装) -├── ScrollArea -│ ├── Empty State (无 Skills 时) -│ └── Grid -│ └── SkillCard.vue (循环) -├── Footer -│ ├── 统计信息 -│ └── 打开文件夹按钮 -└── Dialogs - ├── SkillInstallDialog.vue (安装对话框) - ├── SkillEditorSheet.vue (编辑侧边栏) - └── AlertDialog (删除确认) -``` - ---- - -## 3. 组件设计 - -### 3.1 SkillCard - -Skill 卡片组件,展示单个 Skill 信息。 - -``` -┌─────────────────────────────────────┐ -│ ┌───┐ code-review ⋮ │ -│ │ ✨ │ 按照团队规范进行代码审查 │ -│ └───┘ │ -│ │ -│ ┌────────────────────────────────┐ │ -│ │ allowedTools: Read, Grep, ... │ │ -│ └────────────────────────────────┘ │ -│ │ -│ ┌────────┐ ┌────────┐ │ -│ │ 编辑 │ │ 删除 │ │ -│ └────────┘ └────────┘ │ -└─────────────────────────────────────┘ -``` - -**状态变体**: -- 正常状态 -- Hover 状态(显示操作按钮) -- 加载状态(安装/删除中) - -**Props**: -```typescript -interface SkillCardProps { - skill: SkillMetadata - onEdit: () => void - onDelete: () => void -} -``` - -### 3.2 SkillEditorSheet - -右侧滑出的编辑面板,用于查看和编辑 Skill 详情。 - -``` -┌──────────────────────────────────────┐ -│ ← 编辑 Skill │ -├──────────────────────────────────────┤ -│ │ -│ 名称 │ -│ ┌──────────────────────────────────┐ │ -│ │ code-review │ │ -│ └──────────────────────────────────┘ │ -│ │ -│ 描述 │ -│ ┌──────────────────────────────────┐ │ -│ │ 按照团队规范进行代码审查... │ │ -│ └──────────────────────────────────┘ │ -│ │ -│ 额外工具 (allowedTools) │ -│ ┌──────────────────────────────────┐ │ -│ │ Read, Grep, Glob, Bash(git:*) │ │ -│ └──────────────────────────────────┘ │ -│ │ -│ ─────────────────────────────────── │ -│ │ -│ Skill 内容 │ -│ ┌──────────────────────────────────┐ │ -│ │ # Code Review Skill │ │ -│ │ │ │ -│ │ ## 你的角色 │ │ -│ │ 你是一个代码审查专家... │ │ -│ │ │ │ -│ │ │ │ -│ └──────────────────────────────────┘ │ -│ │ -│ ─────────────────────────────────── │ -│ │ -│ 文件夹内容 │ -│ ┌──────────────────────────────────┐ │ -│ │ 📁 references/ │ │ -│ │ 📄 style-guide.md │ │ -│ │ 📄 checklist.md │ │ -│ │ 📁 scripts/ │ │ -│ │ 📄 lint.sh │ │ -│ └──────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────┤ -│ [取消] [保存] │ -└──────────────────────────────────────┘ -``` - -**功能**: -- 编辑 frontmatter 字段(name, description, allowedTools) -- 编辑 SKILL.md 正文内容(Markdown 编辑器) -- 查看 Skill 文件夹结构(只读树形展示) -- 保存修改(写回 SKILL.md 文件) - -### 3.3 SkillInstallDialog - -安装 Skill 的对话框,支持三种安装方式。 - -``` -┌──────────────────────────────────────┐ -│ 安装 Skill ✕ │ -├──────────────────────────────────────┤ -│ │ -│ ┌────────┐ ┌────────┐ ┌────────┐ │ -│ │ 文件夹 │ │ ZIP │ │ URL │ │ -│ └────────┘ └────────┘ └────────┘ │ -│ │ -│ ┌──────────────────────────────────┐ │ -│ │ │ │ -│ │ 拖拽文件夹到此处 │ │ -│ │ 或点击选择 │ │ -│ │ │ │ -│ └──────────────────────────────────┘ │ -│ │ -│ 提示:支持从 ~/.claude/skills/ │ -│ 等其他客户端直接导入 Skill 文件夹 │ -│ │ -├──────────────────────────────────────┤ -│ [取消] [安装] │ -└──────────────────────────────────────┘ -``` - -**Tab 切换内容**: - -1. **文件夹 Tab**:文件夹选择区域 -2. **ZIP Tab**:ZIP 文件选择区域 -3. **URL Tab**:URL 输入框 - -**冲突处理对话框**: -``` -┌──────────────────────────────────────┐ -│ Skill 已存在 ✕ │ -├──────────────────────────────────────┤ -│ │ -│ 名为 "code-review" 的 Skill 已存在。│ -│ 是否要覆盖现有 Skill? │ -│ │ -├──────────────────────────────────────┤ -│ [取消] [覆盖] │ -└──────────────────────────────────────┘ -``` - ---- - -## 4. 交互流程 - -### 4.1 查看 Skills 列表 - -``` -用户打开 Settings → Skills - │ - ▼ -加载 Skills Metadata 列表 - │ - ▼ -渲染 SkillCard 网格 - │ - ├── 有 Skills → 显示卡片网格 - │ - └── 无 Skills → 显示空状态 - "还没有安装任何 Skill" - [安装第一个 Skill] -``` - -### 4.2 安装 Skill - -``` -用户点击 "安装" 按钮 - │ - ▼ -打开 SkillInstallDialog - │ - ▼ -用户选择安装方式(文件夹/ZIP/URL) - │ - ▼ -选择/输入安装源 - │ - ▼ -点击 "安装" - │ - ▼ -验证 Skill 结构 - │ - ├── 无效 → 显示错误提示 - │ - ▼ -检查是否存在同名 Skill - │ - ├── 存在 → 显示覆盖确认对话框 - │ │ - │ ├── 取消 → 返回 - │ │ - │ └── 覆盖 → 继续安装 - │ - ▼ -执行安装 - │ - ▼ -显示成功提示(Toast) - │ - ▼ -刷新 Skills 列表 -``` - -### 4.3 编辑 Skill - -``` -用户点击 SkillCard 的 "编辑" 按钮 - │ - ▼ -打开 SkillEditorSheet - │ - ▼ -加载 Skill 完整内容 - │ - ▼ -用户编辑字段 - │ - ▼ -点击 "保存" - │ - ▼ -验证表单 - │ - ├── 无效 → 显示错误提示 - │ - ▼ -写回 SKILL.md 文件 - │ - ▼ -显示成功提示(Toast) - │ - ▼ -刷新 Skills 列表 -``` - -### 4.4 删除 Skill - -``` -用户点击 SkillCard 的 "删除" 按钮 - │ - ▼ -显示 AlertDialog 确认 - │ - ├── 取消 → 关闭对话框 - │ - └── 确认 → 执行删除 - │ - ▼ - 删除 Skill 文件夹 - │ - ▼ - 显示成功提示(Toast) - │ - ▼ - 刷新 Skills 列表 -``` - ---- - -## 5. 文件结构 - -``` -src/renderer/settings/ -├── main.ts # 添加 /skills 路由 -└── components/ - └── skills/ - ├── SkillsSettings.vue # 主页面 - ├── SkillsHeader.vue # 头部(搜索、导入、安装) - ├── SkillCard.vue # Skill 卡片 - ├── SkillEditorSheet.vue # 编辑侧边栏 - ├── SkillInstallDialog.vue # 安装对话框 - └── SkillFolderTree.vue # 文件夹树形展示 - -src/renderer/src/stores/ -└── skills.ts # Skills Pinia Store -``` - ---- - -## 6. Store 设计 - -```typescript -// src/renderer/src/stores/skills.ts -import { defineStore } from 'pinia' - -interface SkillMetadata { - name: string - description: string - path: string - skillRoot: string - allowedTools?: string[] -} - -export const useSkillsStore = defineStore('skills', { - state: () => ({ - skills: [] as SkillMetadata[], - loading: false, - error: null as string | null - }), - - actions: { - // 加载 Skills 列表 - async loadSkills() { - this.loading = true - try { - const presenter = useLegacyPresenter('skillPresenter') - this.skills = await presenter.getMetadataList() - } finally { - this.loading = false - } - }, - - // 安装 Skill - async installFromFolder(folderPath: string) { /* ... */ }, - async installFromZip(zipPath: string) { /* ... */ }, - async installFromUrl(url: string) { /* ... */ }, - - // 卸载 Skill - async uninstall(name: string) { /* ... */ }, - - // 更新 Skill - async updateSkill(name: string, content: string) { /* ... */ } - } -}) -``` - ---- - -## 7. 国际化 - -添加以下 i18n key: - -```json -{ - "settings.skills": "Skills", - "settings.skills.title": "Skills 管理", - "settings.skills.search": "搜索 Skills...", - "settings.skills.install": "安装", - "settings.skills.import": "导入", - "settings.skills.import.folder": "从文件夹安装", - "settings.skills.import.zip": "从 ZIP 安装", - "settings.skills.import.url": "从 URL 安装", - "settings.skills.empty": "还没有安装任何 Skill", - "settings.skills.empty.action": "安装第一个 Skill", - "settings.skills.count": "共 {count} 个 Skills", - "settings.skills.openFolder": "打开 Skills 文件夹", - "settings.skills.card.edit": "编辑", - "settings.skills.card.delete": "删除", - "settings.skills.card.allowedTools": "额外工具", - "settings.skills.editor.title": "编辑 Skill", - "settings.skills.editor.name": "名称", - "settings.skills.editor.description": "描述", - "settings.skills.editor.allowedTools": "额外工具 (allowedTools)", - "settings.skills.editor.content": "Skill 内容", - "settings.skills.editor.files": "文件夹内容", - "settings.skills.editor.save": "保存", - "settings.skills.editor.cancel": "取消", - "settings.skills.install.title": "安装 Skill", - "settings.skills.install.tab.folder": "文件夹", - "settings.skills.install.tab.zip": "ZIP", - "settings.skills.install.tab.url": "URL", - "settings.skills.install.folder.hint": "拖拽文件夹到此处或点击选择", - "settings.skills.install.folder.tip": "支持从 ~/.claude/skills/ 等其他客户端直接导入", - "settings.skills.install.zip.hint": "拖拽 ZIP 文件到此处或点击选择", - "settings.skills.install.url.placeholder": "输入 ZIP 下载地址", - "settings.skills.install.confirm": "安装", - "settings.skills.install.cancel": "取消", - "settings.skills.conflict.title": "Skill 已存在", - "settings.skills.conflict.message": "名为 \"{name}\" 的 Skill 已存在。是否要覆盖现有 Skill?", - "settings.skills.conflict.overwrite": "覆盖", - "settings.skills.conflict.cancel": "取消", - "settings.skills.delete.title": "删除 Skill", - "settings.skills.delete.message": "确定要删除 Skill \"{name}\" 吗?此操作不可撤销。", - "settings.skills.delete.confirm": "删除", - "settings.skills.delete.cancel": "取消", - "settings.skills.toast.installed": "Skill \"{name}\" 安装成功", - "settings.skills.toast.updated": "Skill \"{name}\" 更新成功", - "settings.skills.toast.deleted": "Skill \"{name}\" 已删除", - "settings.skills.toast.error": "操作失败: {error}" -} -``` - ---- - -## 8. 路由配置 - -```typescript -// src/renderer/settings/main.ts -{ - path: '/skills', - name: 'skills', - component: () => import('./components/skills/SkillsSettings.vue'), - meta: { - titleKey: 'settings.skills', - icon: 'lucide:sparkles', - position: 5 - } -} -``` - ---- - -## 9. Presenter 接口 - -UI 层通过 `useLegacyPresenter('skillPresenter')` 调用以下方法: - -```typescript -interface SkillPresenter { - // 列表 - getMetadataList(): Promise - - // 安装 - installFromFolder(folderPath: string): Promise<{ success: boolean; error?: string }> - installFromZip(zipPath: string): Promise<{ success: boolean; error?: string }> - installFromUrl(url: string): Promise<{ success: boolean; error?: string }> - - // 卸载 - uninstallSkill(name: string): Promise<{ success: boolean; error?: string }> - - // 读取完整内容 - loadSkillContent(name: string): Promise - - // 更新 SKILL.md - updateSkillFile(name: string, content: string): Promise<{ success: boolean; error?: string }> - - // 获取 Skill 文件夹结构 - getSkillFolderTree(name: string): Promise - - // 打开 Skills 目录 - openSkillsFolder(): Promise - - // 获取 Skills 目录路径 - getSkillsDir(): Promise -} -``` - ---- - -## 10. 事件监听 - -UI 需要监听以下事件以实时更新: - -```typescript -// 在 SkillsSettings.vue 中 -onMounted(() => { - // 监听 Skill 变化事件(热加载触发) - eventBus.on(SKILL_EVENTS.METADATA_UPDATED, () => { - skillsStore.loadSkills() - }) - - eventBus.on(SKILL_EVENTS.INSTALLED, () => { - skillsStore.loadSkills() - }) - - eventBus.on(SKILL_EVENTS.UNINSTALLED, () => { - skillsStore.loadSkills() - }) -}) -``` - diff --git a/docs/archives/skills-ux-redesign/analysis.md b/docs/archives/skills-ux-redesign/analysis.md deleted file mode 100644 index 02fcd0ec9..000000000 --- a/docs/archives/skills-ux-redesign/analysis.md +++ /dev/null @@ -1,223 +0,0 @@ -# Skills 用户体验重设计 - 产品分析文档 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> 文档版本: v1.0 -> 创建日期: 2025-01-11 -> 状态: 分析阶段 - ---- - -## 一、核心问题 - -**Skills 功能强大,但用户感知不到、不会用。** - -| 问题 | 表现 | 影响程度 | -|------|------|---------| -| 对话界面零曝光 | 主界面无任何 Skills 入口,用户无法感知其存在 | ⚠️ 严重 | -| 激活状态不可见 | 当前对话启用了哪些 Skills,用户无从得知 | ⚠️ 严重 | -| 设置入口太深 | 设置窗口第 7 项,同步藏在下拉菜单里 | 中等 | -| 与 MCP 关系模糊 | MCP 默认关闭,但 Skills 同步依赖 MCP 生态 | 中等 | - ---- - -## 二、设计决策 - -### 决策 1: 触发符号分离 - -**`@` 用于引用,`/` 用于调用能力** - -| 触发符 | 语义 | 包含内容 | -|--------|------|---------| -| `@` | 引用上下文 | context、files、resources、workspace files | -| `/` | 调用能力 | **skills**、prompts、tools | - -**理由**: `@` 在社交产品中用于"提及",`/` 在 CLI/Slack 中用于"命令",语义更清晰。 - -### 决策 2: 取消二级菜单 - -**无论 `@` 还是 `/`,直接匹配全量内容,不再需要先选类别** - -- 减少点击次数,提高效率 -- 用户通常已知要找什么,直接输入更快 -- 通过图标区分类别,不影响识别 - -### 决策 3: 输入框增加 Skills 状态入口 - -**在工具栏增加 Skills 指示器,展示当前激活状态** - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ [Mode ▾] [📁] [📎] [🌐] [MCP ▾] [✨ 2] [Model ▾] [⚙️] [↑]│ -│ ↑ │ -│ Skills 指示器,点击展开面板 │ -└──────────────────────────────────────────────────────────────────┘ -``` - -### 决策 4: 优化同步流程与状态展示 - -- 检测到外部 AI 工具时,主动提示是否导入 skills -- 设置页增加"同步状态"区域,展示各工具同步时间和 skill 数量 -- 同步入口从下拉菜单提升为独立区域 - ---- - -## 三、具体需求 - -### 3.1 输入框内 - -| 需求 | 描述 | 优先级 | -|------|------|--------| -| `/` 触发 Skills 选择 | 输入 `/` 显示 skills + prompts + tools 列表 | P0 | -| 扁平化列表 | `@` 和 `/` 都直接显示匹配结果,无二级菜单 | P0 | -| 模糊搜索 | 根据输入实时过滤匹配项 | P0 | -| 类别图标 | 用图标区分(✨ skill / 💬 prompt / 🔧 tool) | P1 | - -### 3.2 输入框外 - -| 需求 | 描述 | 优先级 | -|------|------|--------| -| Skills 指示器 | 工具栏显示当前激活 skills 数量 | P0 | -| Skills 面板 | 点击指示器展开,显示激活/可用 skills | P0 | -| 快速切换 | 面板内可直接激活/停用 skill | P1 | -| 跳转管理 | 面板提供入口跳转到设置页 | P2 | - -### 3.3 同步与引导 - -| 需求 | 描述 | 优先级 | -|------|------|--------| -| 首次导入引导 | 检测到外部工具时提示导入 | P1 | -| 同步状态展示 | 设置页显示各工具同步时间和数量 | P1 | -| MCP 依赖提示 | 激活依赖 MCP 的 skill 时提示启用 | P2 | - ---- - -## 四、交互示意 - -### 4.1 `/` 触发交互 - -``` -用户输入: / -┌─────────────────────────────────────────────┐ -│ ✨ commit 生成规范的 Git 提交信息 │ -│ ✨ review 代码审查助手 │ -│ 💬 summarize 总结内容 (prompt) │ -│ 🔧 web_search 搜索网页 (tool) │ -└─────────────────────────────────────────────┘ - -用户输入: /rev -┌─────────────────────────────────────────────┐ -│ ✨ review 代码审查助手 │ -└─────────────────────────────────────────────┘ -``` - -### 4.2 Skills 面板 - -``` -点击 [✨ 2] 后: -┌─────────────────────────────────────┐ -│ ✨ Active Skills [管理] │ -├─────────────────────────────────────┤ -│ ● commit 生成提交信息 [✕] │ -│ ● review 代码审查 [✕] │ -├─────────────────────────────────────┤ -│ ○ explain 解释代码 [+] │ -│ ○ refactor 重构建议 [+] │ -└─────────────────────────────────────┘ -``` - -### 4.3 设置页同步状态 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 同步状态 │ -├─────────────────────────────────────────────────────────────┤ -│ 🔵 Claude Code 12 skills 上次: 2小时前 [同步] │ -│ 🟢 Cursor 5 skills 上次: 1天前 [同步] │ -│ ⚪ Copilot 未连接 [设置连接] │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## 五、待确认事项 - -| 问题 | 选项 | 建议 | -|------|------|------| -| `/` 列表排序 | A) Skills 优先 B) 按使用频率 C) 按字母 | A | -| 自动同步 | A) 支持后台自动 B) 仅手动 | 待讨论 | -| 同名冲突 | A) 覆盖 B) 重命名 C) 让用户选择 | C | - ---- - -## 附录 A: 现状调研 - -### A.1 界面结构 - -主界面仅有侧边栏(Chat/设置)和对话区域。输入框支持 @ 提及 (context/files/resources/tools/prompts),但没有 Skills 相关入口。 - -### A.2 设置窗口导航 - -Skills 位于第 7 位(共 12 项):通用 → 显示 → 模型 → MCP → MCP市场 → ACP → **Skills** → 提示词 → 知识库 → 数据 → 快捷键 → 关于 - -### A.3 当前 @ 提及系统 - -位置: `src/renderer/src/components/editor/mention/suggestion.ts` - -采用二级菜单:先选类别 → 再选具体项。类别包括 context/files/resources/tools/prompts,**不包含 skills**。 - -### A.4 Skills 同步入口 - -位置: `src/renderer/settings/components/skills/SkillsHeader.vue` - -同步隐藏在右上角下拉菜单: `[同步 ▾] → 导入 / 导出` - -### A.5 AI 工具调用 - -AI 可通过 `skill_list` 和 `skill_control` 工具管理 Skills,但用户不可见。 - -### A.6 支持同步的 12 个外部工具 - -**用户级**: Claude Code、Cursor、OpenCode、Goose、Kilo Code、GitHub Copilot - -**项目级**: Cursor、Windsurf、GitHub Copilot、Kiro、Antigravity、Codex - ---- - -## 附录 B: 代码改动范围 - -### B.1 输入框 - -| 文件 | 改动 | -|------|------| -| `src/renderer/src/components/editor/mention/suggestion.ts` | 拆分 @ 和 / 逻辑 | -| `src/renderer/src/components/editor/mention/MentionList.vue` | 扁平化 UI | -| `src/renderer/src/components/chat-input/ChatInput.vue` | 增加 Skills 指示器 | -| 新增 `SkillsIndicator.vue` / `SkillsPanel.vue` | Skills 组件 | - -### B.2 状态管理 - -| 文件 | 改动 | -|------|------| -| 新增 `src/renderer/src/stores/skillsActiveStore.ts` | 对话级 skills 状态 | - -### B.3 设置页 - -| 文件 | 改动 | -|------|------| -| `src/renderer/settings/components/skills/SkillsSettings.vue` | 同步状态区 | -| 新增 `SyncStatusCard.vue` | 同步状态卡片 | - ---- - -## 附录 C: 关键文件索引 - -| 模块 | 路径 | -|------|------| -| Skills 核心 | `src/main/presenter/skillPresenter/index.ts` | -| Skills 同步 | `src/main/presenter/skillSyncPresenter/index.ts` | -| Skills 设置页 | `src/renderer/settings/components/skills/SkillsSettings.vue` | -| 输入框 | `src/renderer/src/components/chat-input/ChatInput.vue` | -| @ 提及 | `src/renderer/src/components/editor/mention/suggestion.ts` | -| 类型定义 | `src/shared/types/skill.ts` | diff --git a/docs/archives/skills-ux-redesign/spec.md b/docs/archives/skills-ux-redesign/spec.md deleted file mode 100644 index 6a3064907..000000000 --- a/docs/archives/skills-ux-redesign/spec.md +++ /dev/null @@ -1,777 +0,0 @@ -# Skills UX Redesign - Technical Specification - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> Version: 1.0 -> Date: 2025-01-11 -> Status: Draft - ---- - -## Overview - -This document specifies the technical implementation for the Skills UX redesign based on the 4 design decisions outlined in [analysis.md](./analysis.md). - ---- - -## Decision 1: Trigger Symbol Separation - -### Goal - -Separate `@` (reference context) from `/` (invoke capabilities) to provide clearer mental models. - -### Current State - -- Single `@` trigger in [suggestion.ts](../../../src/renderer/src/components/editor/mention/suggestion.ts) -- Categories: `context`, `files`, `resources`, `tools`, `prompts` -- No skills integration -- TipTap Mention extension with `char: '@'` - -### Target State - -| Trigger | Semantic | Content | -|---------|----------|---------| -| `@` | Reference context | context, files, resources, workspace files | -| `/` | Invoke capability | **skills**, prompts, tools | - -### Technical Approach - -#### 1.1 Create Slash Suggestion Module - -Create new file: `src/renderer/src/components/editor/mention/slashSuggestion.ts` - -```typescript -// Similar structure to suggestion.ts but with char: '/' -export default { - char: '/', - allowedPrefixes: null, - items: ({ query }) => { - // Return skills + prompts + tools - }, - render: () => { /* Same popup renderer */ } -} -``` - -#### 1.2 Create Slash Extension - -Create new file: `src/renderer/src/components/editor/mention/slashMention.ts` - -```typescript -import TipTMention from '@tiptap/extension-mention' - -export const SlashMention = TipTMention.extend({ - name: 'slashMention', // Different name to coexist with @mention - // ... same attributes as mention.ts -}) -``` - -#### 1.3 Update ChatInput.vue - -Add SlashMention extension to editor: - -```typescript -// In editor extensions array -SlashMention.configure({ - HTMLAttributes: { - class: 'slash-mention px-1.5 py-0.5 text-xs rounded-md bg-primary/10 ...' - }, - suggestion: slashSuggestion, - deleteTriggerWithBackspace: true -}) -``` - -#### 1.4 Update suggestion.ts - -Remove `tools` and `prompts` from `@` categories: - -```typescript -const categorizedData: CategorizedData[] = [ - { label: 'context', icon: 'lucide:quote', type: 'category' }, - { label: 'files', icon: 'lucide:files', type: 'category' }, - { label: 'resources', icon: 'lucide:swatch-book', type: 'category' } - // tools and prompts moved to / -] -``` - -#### 1.5 Add Skills Data Source - -Create composable: `src/renderer/src/components/chat-input/composables/useSkillsData.ts` - -```typescript -export function useSkillsData(conversationId: Ref) { - const skillPresenter = useLegacyPresenter('skillPresenter') - const skills = ref([]) - const activeSkills = ref([]) - - // Fetch skills metadata - const loadSkills = async () => { - skills.value = await skillPresenter.getMetadataList() - } - - // Fetch active skills for current conversation - const loadActiveSkills = async () => { - if (!conversationId.value) return - activeSkills.value = await skillPresenter.getActiveSkills(conversationId.value) - } - - return { skills, activeSkills, loadSkills, loadActiveSkills } -} -``` - -### Files Changed - -| File | Change | -|------|--------| -| `src/renderer/src/components/editor/mention/slashSuggestion.ts` | **New** - `/` trigger logic | -| `src/renderer/src/components/editor/mention/slashMention.ts` | **New** - TipTap extension | -| `src/renderer/src/components/editor/mention/suggestion.ts` | Remove tools/prompts categories | -| `src/renderer/src/components/chat-input/ChatInput.vue` | Add SlashMention extension | -| `src/renderer/src/components/chat-input/composables/useSkillsData.ts` | **New** - Skills data source | -| `src/renderer/src/components/chat-input/composables/useMentionData.ts` | Split @ and / data | - ---- - -## Decision 2: Flatten Menus - -### Goal - -Remove secondary category selection, show flat filtered list directly. - -### Current State - -- [MentionList.vue](../../../src/renderer/src/components/editor/mention/MentionList.vue) uses `isCategoryView` state -- First shows categories, then items within selected category -- `displayItems` computed filters by `currentCategory` - -### Target State - -- Single flat list showing all matching items -- Icons distinguish item types -- No category navigation needed - -### Technical Approach - -#### 2.1 Simplify MentionList.vue - -Remove category navigation logic: - -```typescript -// Remove these: -const currentCategory = ref(null) -const isCategoryView = computed(...) -const backHandler = () => {...} - -// Simplify displayItems: -const displayItems = computed(() => { - if (props.query) { - return props.items.filter(item => - item.label.toLowerCase().includes(props.query.toLowerCase()) - ).slice(0, 10) - } - return props.items.slice(0, 10) -}) -``` - -#### 2.2 Add Type Icons - -Update template to show type-specific icons: - -```vue - -``` - -#### 2.3 Update Data Structure - -Ensure all items have proper `category` and `description`: - -```typescript -interface CategorizedData { - label: string - icon?: string - id?: string - type: 'item' // No more 'category' type - category: 'context' | 'files' | 'resources' | 'skills' | 'prompts' | 'tools' - description?: string - // ... other fields -} -``` - -### Files Changed - -| File | Change | -|------|--------| -| `src/renderer/src/components/editor/mention/MentionList.vue` | Remove category navigation, flatten UI | -| `src/renderer/src/components/editor/mention/suggestion.ts` | Update CategorizedData type | - ---- - -## Decision 3: Skills Indicator - -### Goal - -Add visible Skills indicator in chat input toolbar showing active skills count and quick management panel. - -### Current State - -- No Skills UI in chat input -- Toolbar has: Mode, Folder, Attach, Web, MCP -- Skills only manageable via settings page - -### Target State - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ [Mode ▾] [📁] [📎] [🌐] [MCP ▾] [✨ 2] [Model ▾] [⚙️] [↑]│ -│ ↑ │ -│ Skills indicator │ -└──────────────────────────────────────────────────────────────────┘ -``` - -### Technical Approach - -#### 3.1 Create SkillsIndicator Component - -New file: `src/renderer/src/components/chat-input/SkillsIndicator.vue` - -```vue - - - -``` - -#### 3.2 Create SkillsPanel Component - -New file: `src/renderer/src/components/chat-input/SkillsPanel.vue` - -```vue - -``` - -#### 3.3 Update ChatInput.vue - -Add SkillsIndicator to toolbar: - -```vue - - - -``` - -#### 3.4 Create Skills Store - -New file: `src/renderer/src/stores/skillsActiveStore.ts` - -```typescript -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { SkillMetadata } from '@shared/types/skill' - -export const useSkillsActiveStore = defineStore('skillsActive', () => { - const skills = ref([]) - const activeSkillsByConversation = ref>(new Map()) - - const getActiveSkills = (conversationId: string) => { - return activeSkillsByConversation.value.get(conversationId) || [] - } - - const setActiveSkills = (conversationId: string, skillNames: string[]) => { - activeSkillsByConversation.value.set(conversationId, skillNames) - } - - const toggleSkill = async (conversationId: string, skillName: string) => { - const current = getActiveSkills(conversationId) - const isActive = current.includes(skillName) - const updated = isActive - ? current.filter(s => s !== skillName) - : [...current, skillName] - setActiveSkills(conversationId, updated) - - // Sync to backend - await window.api.skillPresenter.setActiveSkills(conversationId, updated) - } - - return { - skills, - getActiveSkills, - setActiveSkills, - toggleSkill - } -}) -``` - -### Files Changed - -| File | Change | -|------|--------| -| `src/renderer/src/components/chat-input/SkillsIndicator.vue` | **New** | -| `src/renderer/src/components/chat-input/SkillsPanel.vue` | **New** | -| `src/renderer/src/components/chat-input/ChatInput.vue` | Add SkillsIndicator | -| `src/renderer/src/stores/skillsActiveStore.ts` | **New** | -| `src/renderer/src/components/chat-input/composables/useSkillsData.ts` | **New** | - -### i18n Keys - -```yaml -skills: - indicator: - active: "{count} skills active" - none: "No skills active" - panel: - title: "Skills" - manage: "Manage" - active: "Active" - available: "Available" - empty: "No skills installed" -``` - ---- - -## Decision 4: Sync Flow Optimization - -### Goal - -Improve skills sync discoverability with proactive detection and clear status display. - -### Current State - -- Sync hidden in dropdown: `[同步 ▾] → 导入 / 导出` -- No detection of external AI tools -- No sync status visibility -- Located in [SkillsHeader.vue](../../../src/renderer/settings/components/skills/SkillsHeader.vue) - -### Target State - -1. Proactive detection of external AI tools on first launch -2. Dedicated sync status section in settings -3. Clear last-sync timestamps and skill counts per tool - -### Technical Approach - -#### 4.1 Create SyncStatusSection Component - -New file: `src/renderer/settings/components/skills/SyncStatusSection.vue` - -```vue - -``` - -#### 4.2 Create SyncStatusCard Component - -New file: `src/renderer/settings/components/skills/SyncStatusCard.vue` - -```vue - - - -``` - -#### 4.3 Add External Tool Detection - -Create: `src/main/presenter/skillSyncPresenter/toolDetection.ts` - -```typescript -import fs from 'fs' -import path from 'path' -import os from 'os' - -interface DetectedTool { - id: string - name: string - configPath: string - exists: boolean -} - -const TOOL_PATHS = { - 'claude-code': { - win32: path.join(os.homedir(), '.claude', 'settings.json'), - darwin: path.join(os.homedir(), '.claude', 'settings.json'), - linux: path.join(os.homedir(), '.claude', 'settings.json') - }, - 'cursor': { - win32: path.join(os.homedir(), 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage'), - darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage'), - linux: path.join(os.homedir(), '.config', 'Cursor', 'User', 'globalStorage') - }, - // ... other tools -} - -export function detectExternalTools(): DetectedTool[] { - const platform = process.platform as 'win32' | 'darwin' | 'linux' - const detected: DetectedTool[] = [] - - for (const [id, paths] of Object.entries(TOOL_PATHS)) { - const configPath = paths[platform] - if (configPath) { - detected.push({ - id, - name: formatToolName(id), - configPath, - exists: fs.existsSync(configPath) - }) - } - } - - return detected.filter(t => t.exists) -} - -function formatToolName(id: string): string { - const names: Record = { - 'claude-code': 'Claude Code', - 'cursor': 'Cursor', - 'windsurf': 'Windsurf', - 'github-copilot': 'GitHub Copilot', - // ... - } - return names[id] || id -} -``` - -#### 4.4 Add First-Launch Detection Prompt - -In settings initialization or first app launch, check for external tools: - -```typescript -// In renderer settings initialization -const checkFirstLaunch = async () => { - const hasShownSyncPrompt = await configPresenter.getSetting('skills.syncPromptShown') - if (hasShownSyncPrompt) return - - const detectedTools = await skillSyncPresenter.detectExternalTools() - if (detectedTools.length > 0) { - // Show prompt dialog - showSyncPromptDialog(detectedTools) - } - - await configPresenter.setSetting('skills.syncPromptShown', true) -} -``` - -#### 4.5 Update SkillsSettings.vue - -Add sync status section: - -```vue - -``` - -### Files Changed - -| File | Change | -|------|--------| -| `src/renderer/settings/components/skills/SyncStatusSection.vue` | **New** | -| `src/renderer/settings/components/skills/SyncStatusCard.vue` | **New** | -| `src/renderer/settings/components/skills/SkillsSettings.vue` | Add SyncStatusSection | -| `src/main/presenter/skillSyncPresenter/toolDetection.ts` | **New** | -| `src/main/presenter/skillSyncPresenter/index.ts` | Add detection methods | - -### i18n Keys - -```yaml -skills: - sync: - title: "Sync Status" - refreshAll: "Refresh All" - sync: "Sync" - setup: "Setup" - notConnected: "Not connected" - noToolsDetected: "No external AI tools detected" - lastSync: "Last synced {time}" - skillCount: "{count} skills" -``` - ---- - -## Implementation Order - -| Phase | Components | Priority | -|-------|------------|----------| -| 1 | Flatten menus (Decision 2) | P0 | -| 2 | Skills Indicator (Decision 3) | P0 | -| 3 | Slash trigger (Decision 1) | P0 | -| 4 | Sync optimization (Decision 4) | P1 | - -### Rationale - -1. **Flatten menus first** - Simplest change, improves existing UX immediately -2. **Skills indicator second** - Core visibility improvement, relatively self-contained -3. **Slash trigger third** - Depends on having skills data accessible, more complex -4. **Sync optimization last** - Enhancement layer, not blocking core functionality - ---- - -## Testing Considerations - -### Unit Tests - -- `useSkillsData` composable: skill loading, activation toggling -- `slashSuggestion`: filtering, ordering -- Flatten logic in `MentionList.vue` - -### Integration Tests - -- `/` trigger shows skills + prompts + tools -- `@` trigger shows context + files + resources only -- Skills indicator updates on activation/deactivation -- Sync status reflects actual tool states - -### E2E Tests - -- User types `/` → sees skills list → selects skill → skill activated -- User clicks skills indicator → panel opens → can toggle skills -- User opens settings → sees sync status → can sync tools - ---- - -## Migration Notes - -### Breaking Changes - -None. All changes are additive or modify internal behavior. - -### Backward Compatibility - -- Existing `@` mentions continue to work (subset of categories) -- Existing skill activation via AI tools unchanged -- Settings page structure preserved - -### Data Migration - -None required. Skills state already stored per-conversation in database. - diff --git a/docs/archives/skills-ux-redesign/tasks.md b/docs/archives/skills-ux-redesign/tasks.md deleted file mode 100644 index 3af3cb8c5..000000000 --- a/docs/archives/skills-ux-redesign/tasks.md +++ /dev/null @@ -1,450 +0,0 @@ -# Skills UX Redesign - Development Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -> Version: 1.1 -> Date: 2025-01-11 -> Related: [analysis.md](./analysis.md) | [spec.md](./spec.md) - ---- - -## Task Overview - -| Phase | Description | Tasks | Priority | Status | -|-------|-------------|-------|----------|--------| -| 1 | Flatten Menus | 4 | P0 | ✅ Done | -| 2 | Skills Indicator | 7 | P0 | ✅ Done | -| 3 | Slash Trigger | 6 | P0 | ✅ Done | -| 4 | Sync Optimization | 5 | P1 | ✅ Done | - -**Total: 22 tasks** - ---- - -## Phase 1: Flatten Menus (Decision 2) ✅ - -> Goal: Remove secondary category selection, show flat filtered list directly. -> Status: **Completed** - -### 1.1 Remove Category Navigation Logic ✅ - -**File:** `src/renderer/src/components/editor/mention/MentionList.vue` - -- [x] Remove `currentCategory` ref and `isCategoryView` computed -- [x] Remove `backHandler()` function -- [x] Remove `saveCurrentIndexForCategory()` and `getLastIndexForCategory()` helpers -- [x] Remove `lastIndexMap` ref -- [x] Simplify `onKeyDown` to remove Backspace category navigation - -**Acceptance Criteria:** -- ✅ No category state management in component -- ✅ Backspace key no longer triggers category back navigation - ---- - -### 1.2 Simplify displayItems Logic ✅ - -**File:** `src/renderer/src/components/editor/mention/MentionList.vue` - -- [x] Update `displayItems` computed to return flat filtered list - -**Acceptance Criteria:** -- ✅ List shows all matching items without category grouping -- ✅ Maximum 10 items displayed -- ✅ Fuzzy search works on item labels - ---- - -### 1.3 Update Template for Flat Display ✅ - -**File:** `src/renderer/src/components/editor/mention/MentionList.vue` - -- [x] Remove category view header (`v-if="isCategoryView"`) -- [x] Remove chevron-right icon for category items -- [x] Add type indicator icons based on `item.category`: - - `skills` → ✨ (amber) - - `prompts` → 💬 (blue) - - `tools` → 🔧 (green) - - Others → use `item.icon` -- [x] Add description display (truncated, right-aligned) - -**Acceptance Criteria:** -- ✅ Each item shows type icon + label + description -- ✅ Visual distinction between different item types -- ✅ No category rows in the list - ---- - -### 1.4 Update CategorizedData Type ✅ - -**File:** `src/renderer/src/components/editor/mention/suggestion.ts` - -- [x] Keep `'category'` in type union for backward compatibility but not displayed -- [x] Clear category entries from initial `categorizedData` array -- [x] Items now populated by `useMentionData` composable - -**Acceptance Criteria:** -- ✅ All mention items are direct items with category metadata -- ✅ Category entries no longer rendered - ---- - -## Phase 2: Skills Indicator (Decision 3) ✅ - -> Goal: Add visible Skills indicator in chat input toolbar. -> Status: **Completed** - -### 2.1 Create useSkillsData Composable ✅ - -**File:** `src/renderer/src/components/chat-input/composables/useSkillsData.ts` (NEW) - -- [x] Create composable with: - - `skills: Ref` - all available skills - - `activeSkills: Ref` - currently active skill names - - `loadActiveSkills()` - fetch active for conversation - - `toggleSkill(skillName)` - activate/deactivate skill - - `activeCount: ComputedRef` - count of active skills - - `activeSkillItems` / `availableSkills` - computed filtered lists -- [x] Add event listener for `skill:activated` / `skill:deactivated` -- [x] Add watcher for conversationId changes - -**Acceptance Criteria:** -- ✅ Skills data reactive and auto-updates -- ✅ Toggle correctly calls `skillPresenter.setActiveSkills` -- ✅ Responds to external skill state changes - ---- - -### 2.2 Create SkillsPanel Component ✅ - -**File:** `src/renderer/src/components/chat-input/SkillsPanel.vue` (NEW) - -- [x] Create component with props: - - `skills: SkillMetadata[]` - - `activeSkills: string[]` -- [x] Create emits: - - `toggle(skillName: string)` - - `manage()` -- [x] Implement template: - - Header with title and "Manage" button - - Active skills section with deactivate buttons - - Available skills section with activate buttons - - Empty state when no skills - -**Acceptance Criteria:** -- ✅ Shows active skills at top -- ✅ Shows available (inactive) skills below -- ✅ Click toggle emits event -- ✅ Manage button emits event - ---- - -### 2.3 Create SkillsIndicator Component ✅ - -**File:** `src/renderer/src/components/chat-input/SkillsIndicator.vue` (NEW) - -- [x] Create component with props: - - `conversationId: string | null` -- [x] Integrate `useSkillsData` composable -- [x] Implement Popover with: - - Trigger: Button showing ✨ icon + active count - - Content: SkillsPanel component -- [x] Add Tooltip for button hover -- [x] Style: highlight when skills active (primary color border) - -**Acceptance Criteria:** -- ✅ Shows sparkles icon -- ✅ Shows count badge when skills active -- ✅ Opens panel on click -- ✅ Tooltip shows summary - ---- - -### 2.4 Add SkillsIndicator to ChatInput ✅ - -**File:** `src/renderer/src/components/chat-input/ChatInput.vue` - -- [x] Import SkillsIndicator component -- [x] Add to toolbar after ``: - ```vue - - - ``` -- [x] Ensure conversationId is available (already exists as computed) - -**Acceptance Criteria:** -- ✅ SkillsIndicator visible in chat input toolbar -- ✅ Positioned between MCP tools and right-side actions -- ✅ Works in both chat and newThread variants - ---- - -### 2.5 Create skillsActiveStore ⏭️ (Skipped) - -**Note:** Decided to use `useSkillsData` composable instead of a separate Pinia store. The composable leverages the existing `skillsStore` for metadata and manages per-conversation state directly via `skillPresenter`. - ---- - -### 2.6 Add Settings Navigation ✅ - -**File:** `src/renderer/src/components/chat-input/SkillsIndicator.vue` - -- [x] Implement `openSettings()` method using `windowPresenter.openOrFocusSettingsTab()` -- [x] Wire to SkillsPanel's `@manage` event - -**Acceptance Criteria:** -- ✅ Clicking "Manage" opens settings window - ---- - -### 2.7 Add i18n Keys ✅ - -**Files:** `src/renderer/src/i18n/{en-US,zh-CN}/chat.json` - -- [x] Add `chat.skills.indicator.active` / `chat.skills.indicator.none` -- [x] Add `chat.skills.panel.title` / `manage` / `active` / `available` / `empty` -- [x] Added to en-US and zh-CN - -**Acceptance Criteria:** -- ✅ All UI text uses i18n keys -- ✅ Keys exist in en-US and zh-CN locales - ---- - -## Phase 3: Slash Trigger (Decision 1) ✅ - -> Goal: Separate `/` trigger for skills, prompts, tools from `@` trigger. -> Status: **Completed** - -### 3.1 Create SlashMention Extension ✅ - -**File:** `src/renderer/src/components/editor/mention/slashMention.ts` (NEW) - -- [x] Extend TipTap Mention with `name: 'slashMention'` -- [x] Copy attribute definitions from mention.ts -- [x] Add `trigger` attribute to distinguish from @ mentions - -**Acceptance Criteria:** -- ✅ Extension can coexist with existing Mention -- ✅ Unique node name prevents conflicts - ---- - -### 3.2 Create slashSuggestion Module ✅ - -**File:** `src/renderer/src/components/editor/mention/slashSuggestion.ts` (NEW) - -- [x] Create with `char: '/'` -- [x] Create `slashMentionData` ref for skills + prompts + tools -- [x] Implement `items({ query })`: - - Filter skills, prompts, tools by query - - Sort: skills first, then prompts, then tools - - Limit to 10 items -- [x] Reuse render logic from suggestion.ts - -**Acceptance Criteria:** -- ✅ `/` triggers popup -- ✅ Shows skills, prompts, tools only -- ✅ Query filters results - ---- - -### 3.3 Create useSlashMentionData Composable ✅ - -**File:** `src/renderer/src/components/chat-input/composables/useSlashMentionData.ts` (NEW) - -- [x] Watch skills from skillsStore -- [x] Watch prompts from useAgentMcpData -- [x] Watch tools from useAgentMcpData -- [x] Aggregate into `slashMentionData` with proper category tags -- [x] Sort: skills → prompts → tools - -**Acceptance Criteria:** -- ✅ Data updates reactively from all sources -- ✅ Each item has correct category for icon display - ---- - -### 3.4 Update suggestion.ts ✅ - -**File:** `src/renderer/src/components/editor/mention/suggestion.ts` - -- [x] Remove `tools` and `prompts` from categorizedData -- [x] Keep only: context, files, resources (+ workspace files) -- [x] Update useMentionData to remove tools/prompts watchers - -**Acceptance Criteria:** -- ✅ `@` trigger only shows context, files, resources -- ✅ No tools or prompts in `@` menu - ---- - -### 3.5 Integrate SlashMention in ChatInput ✅ - -**File:** `src/renderer/src/components/chat-input/ChatInput.vue` - -- [x] Import SlashMention and slashSuggestion -- [x] Add to editor extensions with appropriate styling -- [x] Initialize useSlashMentionData composable -- [x] Wire skill activation handler via `setSkillActivationHandler` - -**Acceptance Criteria:** -- ✅ Both `@` and `/` triggers work in editor -- ✅ Different visual styles for @ vs / mentions - ---- - -### 3.6 Handle Skill Selection ✅ - -**File:** `src/renderer/src/components/editor/mention/slashSuggestion.ts` - -- [x] On skill selection: - - Insert mention node - - Activate skill for current conversation via handler -- [x] On prompt selection: - - Handle same as current @ prompts (with params dialog if needed) -- [x] On tool selection: - - Insert mention node (current behavior) - -**Acceptance Criteria:** -- ✅ Selecting skill activates it -- ✅ Prompt param dialog works -- ✅ Tool insertion works - ---- - -## Phase 4: Sync Optimization (Decision 4) ✅ - -> Goal: Improve skills sync discoverability and status visibility. -> Status: **Completed** - -### 4.1 Create Tool Detection Logic ✅ - -**Note:** Tool detection logic already exists in `src/main/presenter/skillSyncPresenter/toolScanner.ts` - -- [x] `EXTERNAL_TOOLS` registry defines all supported tools -- [x] `isToolAvailable()` checks if tool's directory exists -- [x] `scanExternalTools()` scans all registered tools -- [x] Already integrated in skillSyncPresenter interface - -**Acceptance Criteria:** -- ✅ Detects installed AI tools on all platforms -- ✅ Returns tool id, name, config path - ---- - -### 4.2 Create SyncStatusCard Component ✅ - -**File:** `src/renderer/settings/components/skills/SyncStatusCard.vue` (NEW) - -- [x] Create component with props: - - `tool: ScanResult` - - `syncing: boolean` -- [x] Create emits: - - `sync(toolId)` -- [x] Implement template: - - Status indicator (colored dot) - - Tool name and skill count - - Import button for available tools - -**Acceptance Criteria:** -- ✅ Shows connection status visually -- ✅ Shows skill count -- ✅ Appropriate action button based on state - ---- - -### 4.3 Create SyncStatusSection Component ✅ - -**File:** `src/renderer/settings/components/skills/SyncStatusSection.vue` (NEW) - -- [x] Create component with: - - Detected tools list from presenter - - Sync states tracking - - Refresh functionality -- [x] Implement sync handler: - - Emits import event for parent to open sync dialog -- [x] Add empty state for no detected tools -- [x] Add show more/less toggle for long lists - -**Acceptance Criteria:** -- ✅ Lists all detected external tools -- ✅ Each tool shows SyncStatusCard -- ✅ Refresh updates detection - ---- - -### 4.4 Update SkillsSettings Layout ✅ - -**File:** `src/renderer/settings/components/skills/SkillsSettings.vue` - -- [x] Import SyncStatusSection -- [x] Add before skills list with separator -- [x] Adjust spacing/layout - -**Acceptance Criteria:** -- ✅ Sync section visible at top of Skills settings -- ✅ Clean visual hierarchy with skills list below - ---- - -### 4.5 Add First-Launch Detection Dialog ✅ - -**File:** `src/renderer/settings/components/skills/SyncPromptDialog.vue` (NEW) - -- [x] Create dialog component: - - Shows list of detected tools with skill counts - - Checkbox to select tools for import - - "Don't show again" checkbox -- [x] Wire to first launch check via `configPresenter.getSetting('skills.syncPromptShown')` -- [x] Pre-select all available tools by default - -**Acceptance Criteria:** -- Dialog appears on first launch if tools detected -- User can import or dismiss -- Preference persisted - ---- - -## Testing Checklist - -### Phase 1 Tests ✅ -- [x] Typing `@` shows flat list of context/files/resources -- [x] Arrow keys navigate flat list -- [x] Enter selects item -- [x] Backspace deletes characters (no category navigation) -- [x] Query filters items correctly - -### Phase 2 Tests ✅ -- [x] Skills indicator visible in toolbar -- [x] Count updates when skills activated/deactivated -- [x] Panel opens on click -- [x] Can toggle skills from panel -- [x] Manage button opens settings - -### Phase 3 Tests ✅ -- [x] Typing `/` shows skills/prompts/tools -- [x] `@` no longer shows tools/prompts -- [x] Skill selection activates the skill -- [x] Prompt params dialog works -- [x] Both @ and / mentions render correctly - -### Phase 4 Tests ✅ -- [x] Sync section shows in settings -- [x] Detected tools display correctly -- [x] Sync button triggers import dialog -- [x] Status updates after refresh -- [x] First launch dialog appears appropriately - ---- - -## Definition of Done - -- [x] All tasks completed and tested -- [x] `pnpm run lint` passes -- [x] `pnpm run typecheck` passes -- [ ] `pnpm test` passes -- [x] i18n keys added to all locales (en-US, zh-CN) -- [ ] No regressions in existing functionality -- [ ] Code reviewed and approved diff --git a/docs/archives/specs-migration-2026-05-02.md b/docs/archives/specs-migration-2026-05-02.md deleted file mode 100644 index 09863df5f..000000000 --- a/docs/archives/specs-migration-2026-05-02.md +++ /dev/null @@ -1,39 +0,0 @@ -# Specs Migration 2026-05-02 - -> Archive note: This document records the SDD directory migration performed on 2026-05-02. Historical paths can reference code that has since moved or been removed. - -## Summary - -The legacy SDD tree was split into goal-based directories: - -- `docs/features`: 35 active feature and capability targets -- `docs/issues`: 5 active issue, regression, and reliability targets -- `docs/architecture`: 8 active architecture and migration targets -- `docs/archives`: 27 stale, completed, retired, or superseded target folders - -Active goal folders now contain `spec.md`, `plan.md`, and `tasks.md`. Generated placeholder plan/task files preserve SDD shape for older spec-only folders and should be replaced with detailed content when those targets are resumed. - -## Active Features - -`acp-agent-uninstall`, `acp-session-config-options`, `active-input-routing`, `agent-db-legacy-import`, `agent-input-advanced-config`, `app-spotlight-search`, `chat-settings-control`, `chat-sidebar-input-polish`, `edit-file-tool`, `electron-vite-5-upgrade`, `file-attachment-support`, `floating-agent-widget`, `hooks-notifications`, `message-toolbar-actions`, `message-trace-storage`, `ollama-model-selection`, `privacy-mode`, `process-tool`, `provider-deeplink-import`, `remote-acp-control`, `remote-block-streaming`, `remote-discord-lark`, `remote-multi-channel`, `remote-process-log`, `remote-tool-interactions`, `right-sidepanel`, `settings-dashboard`, `settings-environments`, `sidebar-session-context-menu`, `sidebar-workspace-shortcuts`, `subagent-orchestrator`, `tool-call-image-preview`, `user-message-collapse`, `workspace-lifecycle`, `yobrowser-optimization`. - -## Active Issues - -`agent-tool-context-budget`, `e2e-smoke-regression`, `permission-flow-stabilization`, `question-tool-prompt-optimization`, `tool-output-guardrails`. - -## Active Architecture Targets - -`agent-provider-simplification`, `agent-refactor`, `architecture-simplification`, `chat-store-zero-migration`, `main-kernel-refactor`, `renderer-main-single-track`, `skill-runtime-hardening`, `startup-orchestration`. - -## Archived Targets - -`agent-cleanup`, `agent-tooling-v2`, `agentpresenter-mvp-replacement`, `ai-sdk-runtime`, `cua-runtime-plugin`, `default-model-settings`, `legacy-agentpresenter-retirement`, `legacy-llm-provider-runtime-retirement`, `mac-computer-use`, `multi-window-cleanup`, `new-agent`, `new-ui-agent-session`, `new-ui-agent-store`, `new-ui-chat-components`, `new-ui-implementation`, `new-ui-markdown-rendering`, `new-ui-page-state`, `new-ui-pages`, `new-ui-project-store`, `new-ui-session-store`, `new-ui-sidebar`, `new-ui-status-bar`, `provider-layer-simplification`, `remove-chat-mode`, `skills-system`, `skills-ux-redesign`, `telegram-remote-control`. - -## Deleted Stale Files - -- `agentpresenter-mvp-replacement/gap-analysis.md` -- `skills-system/code-review.md` -- `skills-system/create-skill-prompt.md` -- `skills-system/create-skill-spec.md` - -These files were removed because they only described superseded implementation paths or skill scaffolding plans that no longer match the current project-level skill model. diff --git a/docs/archives/telegram-remote-control/plan.md b/docs/archives/telegram-remote-control/plan.md deleted file mode 100644 index a5cbf30a2..000000000 --- a/docs/archives/telegram-remote-control/plan.md +++ /dev/null @@ -1,235 +0,0 @@ -# Telegram Remote Control Plan - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Feature: `telegram-remote-control` -Spec: [spec.md](./spec.md) -Tasks: [tasks.md](./tasks.md) - -## Summary - -This plan updates the original Telegram-only implementation notes to match the shipped -multi-channel `remoteControlPresenter` surface. Telegram-specific runtime behavior still -matters, but all renderer and preload work now lands on a shared presenter contract that also -serves Feishu/Lark, QQBot, Discord, and Weixin iLink. - -## Goals - -- Keep the `telegram-remote-control` feature docs aligned with the current shared presenter - surface. -- Document the generic per-channel settings, status, bindings, pairing, and account-login - flows that renderer and preload callers must use. -- Preserve Telegram runtime behavior while preventing follow-up work from regressing the IPC - contract back to a Telegram-only shape. -- Make channel-specific differences explicit for Telegram, QQBot, Discord, and Weixin iLink. - -## Readiness - -- Companion artifacts exist in `docs/archives/telegram-remote-control/`: - - `spec.md` - - `plan.md` - - `tasks.md` -- No open `[NEEDS CLARIFICATION]` markers remain in [spec.md](./spec.md). -- The current source of truth for the contract is - [remote-control.presenter.d.ts](../../../src/shared/types/presenters/remote-control.presenter.d.ts). - -## Presenter And IPC Contract - -The preferred renderer and preload surface is the shared `IRemoteControlPresenter` contract: - -- Discovery - - `listRemoteChannels()` -- Settings - - `getChannelSettings(channel: T)` - - `saveChannelSettings(channel: T, input: ChannelSettingsMap[T])` -- Runtime status - - `getChannelStatus(channel: RemoteChannel)` -- Bindings - - `getChannelBindings(channel: RemoteChannel)` - - `removeChannelBinding(channel: RemoteChannel, endpointKey: string)` - - `clearChannelBindings(channel: RemoteChannel)` -- Pairable-channel principal and pair-code flow - - `removeChannelPrincipal(channel: PairableRemoteChannel, principalId: string)` - - `getChannelPairingSnapshot(channel: PairableRemoteChannel)` - - `createChannelPairCode(channel: PairableRemoteChannel)` - - `clearChannelPairCode(channel: PairableRemoteChannel)` -- Weixin iLink account flow - - `startWeixinIlinkLogin(input?: { force?: boolean })` - - `waitForWeixinIlinkLogin({ sessionKey, timeoutMs? })` - - `removeWeixinIlinkAccount(accountId)` - - `restartWeixinIlinkAccount(accountId)` - -Compatibility notes: - -- Telegram compatibility helpers remain callable during migration. -- New multi-channel renderer or preload work should not add fresh Telegram-only methods when - the generic presenter surface already covers the use case. - -## Channel Flow Plan - -### 1. Shared Read And Write Flow - -- Renderer loads channel descriptors through `listRemoteChannels()`. -- Channel settings read and write go through `getChannelSettings()` and - `saveChannelSettings()`. -- Status cards, sidebar aggregation, and per-tab runtime summaries use `getChannelStatus()`. -- Binding tables use `getChannelBindings()`, `removeChannelBinding()`, and - `clearChannelBindings()`. - -### 2. Telegram - -- Settings remain `botToken`, `remoteEnabled`, and `defaultAgentId`. -- Status remains Telegram-specific: - - `pollOffset` - - `bindingCount` - - `allowedUserCount` - - `lastError` - - `botUser` -- Pairing flow remains pair-code based with `allowedUserIds` in the pairing snapshot. -- The original Telegram runtime behavior stays intact: - - detached sessions - - `/stop` - - `/sessions` - - `/use` - - `/model` - - temporary status messages - - plain-text delivery - -### 3. QQBot - -- Settings use `appId`, `clientSecret`, `remoteEnabled`, `defaultAgentId`, `defaultWorkdir`, - and `pairedUserIds`. -- Status uses `bindingCount`, `pairedUserCount`, `lastError`, and `botUser`. -- Pairing flow remains pair-code based, but the snapshot must expose both `pairedUserIds` and - `pairedGroupIds`. -- Group authorization stays separate from C2C pairing because QQ identity spaces differ. - -### 4. Discord - -- Settings use `botToken`, `remoteEnabled`, `defaultAgentId`, `defaultWorkdir`, and - `pairedChannelIds`. -- Status uses `bindingCount`, `pairedChannelCount`, `lastError`, and `botUser`. -- Pairing flow remains pair-code based with `pairedChannelIds` in the snapshot. - -### 5. Weixin iLink - -- Settings use `remoteEnabled`, `defaultAgentId`, `defaultWorkdir`, and connected `accounts`. -- Status uses `accountCount`, `connectedAccountCount`, `accounts`, `bindingCount`, and - `lastError`. -- Weixin iLink does not use pair-code APIs. -- Login and runtime ownership use: - - `startWeixinIlinkLogin()` - - `waitForWeixinIlinkLogin()` - - `removeWeixinIlinkAccount()` - - `restartWeixinIlinkAccount()` - -### 6. Feishu/Lark Compatibility - -- Feishu/Lark stays on the same presenter contract and remains pair-code based. -- This feature folder does not redefine Feishu-specific behavior, but follow-up work must not - remove Feishu from the shared remote presenter surface. - -## Milestones - -### M0 Spec Hygiene - -- Align `spec.md`, `plan.md`, and `tasks.md` with the shipped presenter contract. -- Record resolved clarification items so follow-up work does not re-open Telegram-only - assumptions. - -### M1 Shared Presenter Surface - -- Keep the documented remote presenter centered on `listRemoteChannels()`, generic per-channel - settings, generic per-channel status, generic bindings, and pairable-channel pairing APIs. -- Preserve Telegram compatibility helpers as migration shims only. - -Exit criteria: - -- This feature folder no longer documents a Telegram-only IPC surface. -- The expected method list matches `IRemoteControlPresenter`. - -### M2 Pairable Channel Variations - -- Document Telegram, QQBot, Discord, and Feishu/Lark pair-code flows with the correct channel - snapshots and principal-removal semantics. -- Keep QQBot group authorization explicit in the plan. - -Exit criteria: - -- Telegram, QQBot, and Discord pairing snapshots are documented with their channel-specific - fields. -- Pair-code APIs are clearly limited to `PairableRemoteChannel`. - -### M3 Weixin iLink Account Flow - -- Document Weixin iLink as an account-login flow instead of a pair-code flow. -- Keep account lifecycle methods and binding cleanup on the shared presenter surface. - -Exit criteria: - -- The feature docs describe Weixin iLink login and account restart/removal paths explicitly. -- The plan does not imply that Weixin iLink participates in pair-code APIs. - -### M4 Renderer And Preload Usage - -- Keep new Remote settings, status aggregation, and binding views aligned with the generic - presenter surface. -- Block new Telegram-only branching in follow-up renderer or preload work unless it is a true - compatibility shim. - -Exit criteria: - -- Shared methods are the preferred path for new UI work. -- Channel-specific differences are documented without changing the IPC shape. - -### M5 QA And Compatibility - -- Verify the feature docs mirror the current shared types. -- Verify Telegram runtime expectations remain documented. -- Keep compatibility guarantees explicit for Telegram compatibility methods and existing - non-Telegram channels. - -Exit criteria: - -- Acceptance criteria in [spec.md](./spec.md) line up with the current presenter type and - channel behavior. - -## Rollout Steps - -1. Treat the shared presenter type as the documentation baseline for this feature. -2. Route new renderer or preload work through generic channel methods first. -3. Use channel-specific sections only for payload or lifecycle differences: - Telegram allowlist pairing, QQBot group authorization, Discord paired channels, and Weixin - iLink account login. -4. Keep Telegram compatibility helpers only while existing callers still need them. -5. Verify docs and code stay aligned whenever the remote presenter surface changes. - -## QA And Compatibility Checks - -- Contract checks - - `spec.md`, `plan.md`, and `tasks.md` name the same channel set: - `telegram`, `feishu`, `qqbot`, `discord`, `weixin-ilink` - - The documented methods match the shared presenter type. -- Channel checks - - Telegram documents `allowedUserIds` pairing snapshots. - - QQBot documents `pairedUserIds` and `pairedGroupIds`. - - Discord documents `pairedChannelIds`. - - Weixin iLink documents login-session and account-management methods instead of pair-code - methods. -- Compatibility checks - - Telegram runtime behavior from the original feature remains documented. - - Telegram compatibility methods are still called out as supported shims. - - Feishu/Lark stays represented on the shared presenter surface. -- Quality gates - - `pnpm run format` - - `pnpm run i18n` - - `pnpm run lint` - -## Resolved Clarifications - -- Pair-code methods apply only to `telegram`, `feishu`, `qqbot`, and `discord`. -- Weixin iLink uses login-session methods and account lifecycle methods instead of pair codes. -- Binding APIs apply to all channels, including Weixin iLink account-scoped bindings. -- Telegram compatibility methods remain supported, but they are not the preferred long-term - contract for new work. diff --git a/docs/archives/telegram-remote-control/spec.md b/docs/archives/telegram-remote-control/spec.md deleted file mode 100644 index d8fe00a3e..000000000 --- a/docs/archives/telegram-remote-control/spec.md +++ /dev/null @@ -1,176 +0,0 @@ -# Telegram Remote Control - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Feature: `telegram-remote-control` -Plan: [plan.md](./plan.md) -Tasks: [tasks.md](./tasks.md) - -## Summary - -Telegram remote control is no longer a Telegram-only IPC contract. The shipped implementation -uses a shared `remoteControlPresenter` surface that serves Telegram, Feishu/Lark, QQBot, -Discord, and Weixin iLink from the same presenter and preload boundary. - -This spec keeps Telegram runtime behavior in scope, but the renderer and preload contract must -be documented as a multi-channel API so follow-up work does not regress non-Telegram channels -back to a Telegram-only shape. - -## User Stories - -- As a DeepChat desktop user, I can configure Telegram, QQBot, Discord, and Weixin iLink from - one Remote settings flow, while Feishu/Lark compatibility remains on the same presenter - surface. -- As a renderer maintainer, I can call one typed presenter API for reading and saving - per-channel settings, status, bindings, pairing state, and login state. -- As a Telegram user, I can pair numeric user ids and continue detached DeepChat sessions from - bot DMs. -- As a QQBot user, I can keep C2C user pairing separate from authorized groups so group - control does not disappear from the presenter state. -- As a Discord user, I can pair DMs or guild channels and inspect runtime state from the same - Remote settings surface. -- As a Weixin iLink user, I can connect accounts through a login session instead of a pair - code and manage account-scoped runtimes. - -## Multi-Channel Presenter Surface - -The expected shared presenter contract mirrors `IRemoteControlPresenter`: - -```ts -type RemoteChannel = 'telegram' | 'feishu' | 'qqbot' | 'discord' | 'weixin-ilink' -type PairableRemoteChannel = 'telegram' | 'feishu' | 'qqbot' | 'discord' - -interface IRemoteControlPresenter { - listRemoteChannels(): Promise - - getChannelSettings(channel: T): Promise - saveChannelSettings( - channel: T, - input: ChannelSettingsMap[T] - ): Promise - - getChannelStatus(channel: RemoteChannel): Promise - - getChannelBindings(channel: RemoteChannel): Promise - removeChannelBinding(channel: RemoteChannel, endpointKey: string): Promise - clearChannelBindings(channel: RemoteChannel): Promise - - removeChannelPrincipal(channel: PairableRemoteChannel, principalId: string): Promise - getChannelPairingSnapshot(channel: PairableRemoteChannel): Promise - createChannelPairCode( - channel: PairableRemoteChannel - ): Promise<{ code: string; expiresAt: number }> - clearChannelPairCode(channel: PairableRemoteChannel): Promise - - startWeixinIlinkLogin(input?: { force?: boolean }): Promise - waitForWeixinIlinkLogin(input: { - sessionKey: string - timeoutMs?: number - }): Promise - removeWeixinIlinkAccount(accountId: string): Promise - restartWeixinIlinkAccount(accountId: string): Promise -} -``` - -Notes: - -- Telegram compatibility helpers remain callable during migration, but new renderer work should - prefer the generic channel methods above. -- Pair-code methods do not apply to Weixin iLink because that channel uses an account-login - flow instead of a pairing code. - -## Channel Variations - -- Telegram - - Settings: `botToken`, `remoteEnabled`, `defaultAgentId` - - Status: `pollOffset`, `bindingCount`, `allowedUserCount`, `lastError`, `botUser` - - Pairing snapshot: `allowedUserIds` -- QQBot - - Settings: `appId`, `clientSecret`, `remoteEnabled`, `defaultAgentId`, `defaultWorkdir`, - `pairedUserIds` - - Status: `bindingCount`, `pairedUserCount`, `lastError`, `botUser` - - Pairing snapshot: `pairedUserIds` and `pairedGroupIds` -- Discord - - Settings: `botToken`, `remoteEnabled`, `defaultAgentId`, `defaultWorkdir`, - `pairedChannelIds` - - Status: `bindingCount`, `pairedChannelCount`, `lastError`, `botUser` - - Pairing snapshot: `pairedChannelIds` -- Weixin iLink - - Settings: `remoteEnabled`, `defaultAgentId`, `defaultWorkdir`, `accounts` - - Status: `accountCount`, `connectedAccountCount`, `accounts`, `bindingCount`, `lastError` - - Login flow: `startWeixinIlinkLogin`, `waitForWeixinIlinkLogin`, - `removeWeixinIlinkAccount`, `restartWeixinIlinkAccount` -- Feishu/Lark compatibility - - Remains on the same presenter contract with pair codes and paired open ids, even though - this feature folder focuses on Telegram-originated Remote UX. - -## Acceptance Criteria - -- This feature folder documents the shipped multi-channel presenter contract instead of a - Telegram-only IPC subset. -- The documented channel set includes `telegram`, `feishu`, `qqbot`, `discord`, and - `weixin-ilink`. -- Renderer and preload callers use generic `getChannelSettings` and `saveChannelSettings`, - `getChannelStatus`, `getChannelBindings`, `removeChannelBinding`, and `clearChannelBindings` - for all shipped channels. -- Pairable channels (`telegram`, `feishu`, `qqbot`, `discord`) use - `getChannelPairingSnapshot`, `createChannelPairCode`, `clearChannelPairCode`, and - `removeChannelPrincipal`. -- Telegram pairing snapshots expose `allowedUserIds`. -- QQBot pairing snapshots expose both `pairedUserIds` and `pairedGroupIds`. -- Discord pairing snapshots expose `pairedChannelIds`. -- Weixin iLink documents account-login and account-lifecycle methods instead of pair-code - methods. -- Remote settings follow per-channel Telegram, QQBot, Discord, and Weixin iLink flows rather - than reusing Telegram-only assumptions. -- Telegram runtime behavior from the original feature remains unchanged: - - detached-session creation from the remote flow - - `/stop`, `/sessions`, `/use`, and `/model` - - temporary status message handling - - plain-text-only remote delivery - -## Constraints - -- The shared presenter lives in Electron main and crosses renderer through the existing - presenter IPC path. -- Per-channel settings stay in their existing config roots: - - `remoteControl.telegram` - - `remoteControl.feishu` - - `remoteControl.qqbot` - - `remoteControl.discord` - - `remoteControl.weixinIlink` -- `RemoteBindingStore`, `RemoteConversationRunner`, and `remoteBlockRenderer` remain the - source of truth for bindings, session orchestration, and rendered delivery text. -- Pair-code APIs are limited to pairable channels; Weixin iLink uses login-session APIs. -- Existing Telegram compatibility methods remain callable during migration, but new renderer - work should prefer generic channel methods. - -## Non-Goals - -- Reverting to a Telegram-only presenter or reintroducing Telegram-specific IPC branches for - shared settings flows. -- Collapsing Weixin iLink account management into the pair-code flow. -- Removing Feishu/Lark compatibility from the shared presenter surface. -- Changing Telegram runtime transport rules that are already implemented outside this - documentation fix. - -## Compatibility - -- Existing Telegram runtime behavior and saved settings remain valid. -- Existing Feishu/Lark, QQBot, Discord, and Weixin iLink settings stay under their current - config shapes. -- Existing Telegram compatibility methods continue to work while generic multi-channel methods - remain the preferred path. -- Follow-up work may refine channel-specific UI, but it must not narrow the shared presenter - surface back to Telegram-only. - -## Resolved Clarifications - -- `PairableRemoteChannel` is `telegram | feishu | qqbot | discord`; Weixin iLink is excluded - because it uses login sessions, not pair codes. -- `getChannelBindings`, `removeChannelBinding`, and `clearChannelBindings` apply to all - channels, including Weixin iLink account-scoped bindings. -- Telegram compatibility methods are retained as migration shims, not as the preferred - long-term renderer contract. -- No open `[NEEDS CLARIFICATION]` markers remain in this feature folder. diff --git a/docs/archives/telegram-remote-control/tasks.md b/docs/archives/telegram-remote-control/tasks.md deleted file mode 100644 index ad07f0829..000000000 --- a/docs/archives/telegram-remote-control/tasks.md +++ /dev/null @@ -1,112 +0,0 @@ -# Telegram Remote Control Tasks - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -Feature: `telegram-remote-control` -Spec: [spec.md](./spec.md) -Plan: [plan.md](./plan.md) - -These tasks document the remote presenter contract that follow-up work must preserve. They are -tracked as completed because the current codebase already ships this surface; future changes -should use them as a regression checklist. - -## Readiness And Clarifications - -- [x] `T0.1` Keep companion artifacts in `docs/archives/telegram-remote-control/`: - `spec.md`, `plan.md`, and `tasks.md`. - Owner: Remote control maintainer - Effort: XS -- [x] `T0.2` Resolve the presenter-scope clarification: the feature now documents the shared - multi-channel `remoteControlPresenter` contract instead of a Telegram-only IPC surface. - Owner: Remote control maintainer - Effort: XS -- [x] `T0.3` Resolve the pairing clarification: pair-code methods apply only to - `telegram`, `feishu`, `qqbot`, and `discord`; Weixin iLink uses login-session methods. - Owner: Remote control maintainer - Effort: XS -- [x] `T0.4` Resolve the binding clarification: binding list, remove, and clear flows apply to - every remote channel, including Weixin iLink account-scoped bindings. - Owner: Remote control maintainer - Effort: XS - -## Epic E1 Shared Presenter Surface - -- [x] `T1.1` Document `listRemoteChannels()` and the shipped channel set: - `telegram`, `feishu`, `qqbot`, `discord`, `weixin-ilink`. - Owner: Electron main + preload - Effort: S -- [x] `T1.2` Document generic per-channel settings APIs: - `getChannelSettings()` and `saveChannelSettings()`. - Owner: Electron main + preload - Effort: S -- [x] `T1.3` Document generic runtime and binding APIs: - `getChannelStatus()`, `getChannelBindings()`, `removeChannelBinding()`, - `clearChannelBindings()`. - Owner: Electron main + renderer - Effort: S -- [x] `T1.4` Document pairable-channel APIs: - `removeChannelPrincipal()`, `getChannelPairingSnapshot()`, `createChannelPairCode()`, - `clearChannelPairCode()`. - Owner: Electron main + renderer - Effort: S -- [x] `T1.5` Document Weixin iLink account APIs: - `startWeixinIlinkLogin()`, `waitForWeixinIlinkLogin()`, `removeWeixinIlinkAccount()`, - `restartWeixinIlinkAccount()`. - Owner: Electron main + renderer - Effort: S -- [x] `T1.6` Record Telegram compatibility methods as supported migration shims rather than the - preferred long-term presenter surface. - Owner: Electron main + renderer - Effort: XS - -## Epic E2 Channel Variations - -- [x] `T2.1` Keep Telegram documentation explicit about settings, runtime status, pair-code - flow, `allowedUserIds`, and original command/runtime behavior. - Owner: Telegram runtime maintainer - Effort: M -- [x] `T2.2` Keep QQBot documentation explicit about settings, runtime status, pair-code flow, - `pairedUserIds`, and `pairedGroupIds`. - Owner: QQBot runtime maintainer - Effort: M -- [x] `T2.3` Keep Discord documentation explicit about settings, runtime status, pair-code - flow, and `pairedChannelIds`. - Owner: Discord runtime maintainer - Effort: M -- [x] `T2.4` Keep Weixin iLink documentation explicit about settings, runtime status, - account-login flow, and account lifecycle methods. - Owner: Weixin iLink runtime maintainer - Effort: M -- [x] `T2.5` Preserve Feishu/Lark on the shared presenter surface even though this feature - folder remains Telegram-originated. - Owner: Remote control maintainer - Effort: S - -## Epic E3 Renderer And Preload Adoption - -- [x] `T3.1` Treat generic channel methods as the preferred path for new Remote settings work. - Owner: Renderer - Effort: S -- [x] `T3.2` Keep status cards, binding views, and follow-up IPC work aligned with generic - channel reads instead of fresh Telegram-only branches. - Owner: Renderer + preload - Effort: S -- [x] `T3.3` Allow Telegram-only helpers to remain only for compatibility with existing callers. - Owner: Renderer + preload - Effort: XS - -## Epic E4 QA And Acceptance - -- [x] `T4.1` Verify the feature docs mirror - `src/shared/types/presenters/remote-control.presenter.d.ts`. - Owner: QA + Remote control maintainer - Effort: XS -- [x] `T4.2` Verify acceptance criteria cover Telegram, QQBot, Discord, and Weixin iLink - variations without dropping Feishu/Lark from the shared channel set. - Owner: QA + Remote control maintainer - Effort: S -- [x] `T4.3` Run repository quality gates after documentation updates: - `pnpm run format`, `pnpm run i18n`, `pnpm run lint`. - Owner: QA + Remote control maintainer - Effort: XS diff --git a/docs/archives/thread-presenter-migration-plan.md b/docs/archives/thread-presenter-migration-plan.md deleted file mode 100644 index 3dd7e5968..000000000 --- a/docs/archives/thread-presenter-migration-plan.md +++ /dev/null @@ -1,2727 +0,0 @@ -# ThreadPresenter 迁移方案:完全迁移到 SessionPresenter - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 概述 - -本文档详细描述了从 `threadPresenter` 到 `sessionPresenter` 的完整迁移方案。目标是消除 `threadPresenter`,将所有功能按职责分离到合适的模块中,建立清晰的架构边界。 - -### 核心原则 - -1. **sessionPresenter** - 负责核心会话管理(生命周期、消息、Agent Loop、ACP、权限协调) -2. **searchPresenter** - 独立模块,负责搜索引擎和搜索执行 -3. **exporter** - 通用模块,负责会话导出(跨 presenter 使用) -4. **共享模块** - 消息格式化、内容增强、权限检查等作为共享工具 - -### 迁移策略 - -- **完全迁移**:不保留向后兼容,直接替换 -- **功能归属**:所有会话相关功能都去 sessionPresenter;额外功能扩充到 utility 或其他合适的 presenter -- **保留功能**:子会话/父子会话、消息变体系统完全支持 - ---- - -## 当前架构分析 - -### ThreadPresenter 结构(待废弃) - -``` -threadPresenter/ -├── handlers/ # 9个处理器,负责特定领域的业务逻辑 -│ ├── baseHandler.ts (20行) - 基础处理器 -│ ├── streamGenerationHandler.ts (645行) - 流式生成处理 -│ ├── llmEventHandler.ts - LLM事件处理 -│ ├── toolCallHandler.ts - 工具调用处理 -│ ├── permissionHandler.ts (862行) - 权限处理 -│ ├── searchHandler.ts (350行) - 搜索处理 -│ ├── utilityHandler.ts (462行) - 工具处理 -│ ├── commandPermissionHandler.ts (275行) - 命令权限 -│ └── contentBufferHandler.ts (196行) - 流式缓冲 -├── managers/ # 3个管理器,负责资源管理和数据持久化 -│ ├── messageManager.ts (389行) - 消息管理 -│ ├── conversationManager.ts (524行) - 会话管理 -│ └── searchManager.ts (1405行) - 搜索管理 -├── exporters/ # 2个导出器,负责数据格式转换 -│ ├── conversationExporter.ts (691行) - 会话导出 -│ └── nowledgeMemExporter.ts (302行) - NowledgeMem导出 -├── utils/ # 工具类,提供辅助功能 -│ └── contentEnricher.ts (384行) - 内容增强 -├── templates/ -│ └── conversationExportTemplates.ts - 导出模板 -├── types.ts # 类型定义 -├── const.ts # 常量定义(搜索提示模板) -└── index.ts # 主入口(1140行) -``` - -### SessionPresenter 结构(目标) - -``` -sessionPresenter/ -├── index.ts # 主入口 (576行) -├── interface.ts # 接口定义 (88行) -├── types.ts # 类型定义 (81行) -├── events.ts # 事件定义 (13行) -│ -├── session/ -│ └── sessionManager.ts # 会话管理器 (247行) -│ -├── tab/ -│ ├── tabAdapter.ts -│ ├── tabManager.ts # Tab管理器 (210行) -│ └── index.ts -│ -├── loop/ -│ ├── agentLoopHandler.ts # Agent循环处理器 (670行) -│ ├── loopOrchestrator.ts # 循环编排器 (25行) -│ ├── loopState.ts # 循环状态 (26行) -│ ├── errorClassification.ts # 错误分类 -│ └── toolCallProcessor.ts # 工具调用处理器 (445行) -│ -├── persistence/ -│ ├── conversationPersister.ts # 会话持久化 (46行) -│ ├── messagePersister.ts # 消息持久化 (97行) -│ └── index.ts -│ -├── message/ -│ ├── messageBuilder.ts # 消息构造器 (285行) -│ ├── messageTruncator.ts -│ ├── messageFormatter.ts -│ ├── messageCompressor.ts -│ └── index.ts -│ -├── acp/ # ACP模块 (13个文件) -│ ├── acpProcessManager.ts # ACP进程管理 (1132行) -│ ├── acpSessionManager.ts # ACP会话管理 (370行) -│ ├── agentToolManager.ts # Agent工具 (577行) -│ ├── agentFileSystemHandler.ts# 文件系统 (960行) -│ └── ... (其他 ACP 相关文件) -│ -├── tool/ -│ ├── toolCallCenter.ts # 工具调用中心 (26行) -│ ├── toolRegistry.ts -│ ├── toolRouter.ts -│ └── index.ts -│ -└── utility/ - ├── promptEnhancer.ts # 提示词增强 - └── index.ts -``` - ---- - -## 功能归属表 - -### 现有模块归属 - -| 模块 | 源文件行数 | 所属 | 说明 | 优先级 | -|------|-----------|------|------|--------| -| **会话管理核心** | | | | | -| ConversationManager | 524行 | sessionPresenter | 会话CRUD、Tab绑定、分支、子会话 | P0 | -| MessageManager | 389行 | sessionPresenter | 消息CRUD、变体、历史、分页 | P0 | -| StreamGenerationHandler | 645行 | sessionPresenter | 流式生成协调、搜索集成 | P0 | -| LLMEventHandler | 389行 | sessionPresenter | LLM事件处理(响应/错误/结束) | P0 | -| ToolCallHandler | 525行 | sessionPresenter | 工具调用块管理、权限阶段 | P1 | -| PermissionHandler | 862行 | sessionPresenter | 权限响应协调、ACP/MCP/命令 | P0 | -| ContentBufferHandler | 196行 | sessionPresenter | 流式缓冲优化、节流发送 | P1 | -| **搜索功能** | | | | | -| SearchManager | 1405行 | searchPresenter | 搜索引擎管理、窗口管理、搜索执行 | P0 | -| SearchHandler | 350行 | searchPresenter | 搜索适配器、LLM查询优化 | P0 | -| 搜索提示模板 | - | searchPrompts | 搜索提示模板常量 | P2 | -| **导出功能** | | | | | -| ConversationExporter | 691行 | exporter | Markdown/HTML/TXT导出 | P1 | -| NowledgeMemExporter | 302行 | nowledgeMemPresenter | 已存在,保持独立 | 保持 | -| 导出模板 | - | exporter | HTML模板 | P2 | -| **辅助功能** | | | | | -| UtilityHandler::translateText | - | sessionPresenter/utility | 翻译服务 | P1 | -| UtilityHandler::askAI | - | sessionPresenter/utility | AI问答服务 | P1 | -| UtilityHandler::summaryTitles | - | sessionPresenter/utility | 标题生成 | P1 | -| UtilityHandler::exportConversation | - | exporter | 已存在 | 已迁移 | -| UtilityHandler::getMessageRequestPreview | - | sessionPresenter/utility | 调试预览 | P2 | -| CommandPermissionHandler | 275行 | permission | 命令权限检查、风险评估 | P0 | -| ContentEnricher | 384行 | content | URL内容提取、HTML解析 | P2 | - ---- - -## 目标架构 - -``` -src/main/presenter/ -├── sessionPresenter/ # 核心:所有会话管理功能 -│ ├── index.ts # 主入口,实现 ISessionPresenter -│ ├── interface.ts -│ ├── types.ts -│ ├── events.ts -│ │ -│ ├── session/ -│ │ └── sessionManager.ts -│ ├── tab/ -│ │ ├── tabAdapter.ts -│ │ ├── tabManager.ts -│ │ └── index.ts -│ ├── loop/ -│ │ ├── agentLoopHandler.ts -│ │ ├── loopOrchestrator.ts -│ │ ├── loopState.ts -│ │ ├── errorClassification.ts -│ │ ├── toolCallProcessor.ts -│ │ └── index.ts -│ ├── persistence/ -│ │ ├── conversationPersister.ts -│ │ ├── messagePersister.ts -│ │ └── index.ts -│ ├── message/ -│ │ ├── messageBuilder.ts -│ │ ├── messageTruncator.ts -│ │ ├── messageFormatter.ts -│ │ ├── messageCompressor.ts -│ │ └── index.ts -│ ├── acp/ -│ │ ├── acpProcessManager.ts -│ │ ├── acpSessionManager.ts -│ │ ├── agentToolManager.ts -│ │ ├── agentFileSystemHandler.ts -│ │ ├── agentBashHandler.ts -│ │ ├── acpFsHandler.ts -│ │ ├── acpTerminalManager.ts -│ │ ├── acpMessageFormatter.ts -│ │ ├── acpPersistence.ts -│ │ ├── shellEnvHelper.ts -│ │ ├── mcpTransportFilter.ts -│ │ ├── mcpConfigConverter.ts -│ │ ├── acpContentMapper.ts -│ │ ├── commandProcessTracker.ts -│ │ ├── types.ts -│ │ └── index.ts -│ ├── tool/ -│ │ ├── toolCallCenter.ts -│ │ ├── toolRegistry.ts -│ │ ├── toolRouter.ts -│ │ └── index.ts -│ ├── permission/ # 新增:权限协调 -│ │ ├── permissionCoordinator.ts -│ │ └── index.ts -│ ├── utility/ # 新增:辅助功能 -│ │ ├── aiService.ts -│ │ ├── debuggerService.ts -│ │ ├── titleGenerator.ts -│ │ ├── promptEnhancer.ts -│ │ └── index.ts -│ ├── managers/ # 新增:核心管理器 -│ │ ├── conversationManager.ts -│ │ ├── messageManager.ts -│ │ └── index.ts -│ ├── streaming/ -│ │ ├── streamingGenerator.ts # 新增整合类 -│ │ ├── streamEventHandler.ts -│ │ ├── contentBufferHandler.ts -│ │ └── index.ts -│ └── const.ts -│ -├── searchPresenter/ # 搜索功能独立模块 -│ ├── index.ts -│ ├── interface.ts -│ ├── managers/ -│ │ ├── searchManager.ts -│ │ └── index.ts -│ ├── handlers/ -│ │ ├── searchHandler.ts -│ │ └── index.ts -│ └── types.ts -│ -├── exporter/ # 导出服务(跨presenter) -│ ├── index.ts -│ ├── interface.ts -│ ├── formats/ -│ │ ├── conversationExporter.ts -│ │ ├── markdownExporter.ts -│ │ ├── htmlExporter.ts -│ │ ├── textExporter.ts -│ │ └── index.ts -│ ├── templates/ -│ │ └── conversationExportTemplates.ts -│ └── types.ts -│ -├── knowledgePresenter/ # 保留(不迁移) -├── nowledgeMemPresenter/ # 保留(独立功能) -│ -├── permission/ # 共享:权限工具 -│ ├── commandPermissionService.ts -│ ├── commandPermissionCache.ts -│ └── index.ts -│ -├── content/ # 共享:内容处理 -│ ├── contentEnricher.ts -│ ├── htmlParser.ts -│ └── index.ts -│ -├── searchPrompts/ # 共享:搜索提示 -│ ├── searchPrompts.ts -│ ├── templates/ -│ │ ├── searchPromptTemplate.ts -│ │ └── searchPromptArtifactsTemplate.ts -│ └── index.ts -│ -├── index.ts # 更新:移除 threadPresenter -└── ... (其他 presenter 保持不变) -``` - ---- - -## 迁移阶段计划 - -### 阶段 1:准备基础设施(1-2天) - -#### 1.1 创建新目录结构 - -```bash -mkdir -p src/main/presenter/searchPresenter/{managers,handlers} -mkdir -p src/main/presenter/exporter/{formats,templates} -mkdir -p src/main/presenter/searchPrompts/templates -mkdir -p src/main/presenter/permission -mkdir -p src/main/presenter/content -mkdir -p src/main/presenter/sessionPresenter/{permission,utility,managers,streaming} -``` - -#### 1.2 创建基础接口定义 - -**`src/main/presenter/searchPresenter/interface.ts`** - -```typescript -export interface ISearchPresenter { - // 搜索引擎管理 - getEngines(): Promise - getActiveEngine(): Promise - setActiveEngine(engineId: string): Promise - testEngine(query?: string): Promise - - // 搜索执行(通过 conversationId 关联到会话) - executeSearch(conversationId: string, query: string): Promise - stopSearch(conversationId: string): Promise - - // 搜索配置 - updateEngines(engines: SearchEngineConfig[]): Promise - addCustomEngine(engine: SearchEngineConfig): Promise - removeCustomEngine(engineId: string): Promise - - // 工具方法 - search(query: string): Promise - testSearch(query?: string): Promise -} -``` - -**`src/main/presenter/exporter/interface.ts`** - -```typescript -export interface IConversationExporter { - // 导出格式 - exportConversation( - conversationId: string, - format: 'markdown' | 'html' | 'txt' - ): Promise<{ filename: string; content: string }> - - // NowledgeMem 集成(委派给 NowledgeMemPresenter) - exportToNowledgeMem(conversationId: string): Promise<{ - success: boolean - data?: NowledgeMemThread - summary?: NowledgeMemExportSummary - errors?: string[] - }> - - submitToNowledgeMem(conversationId: string): Promise<{ - success: boolean - threadId?: string - data?: NowledgeMemThread - errors?: string[] - }> - - testNowledgeMemConnection(): Promise<{ - success: boolean - message?: string - error?: string - }> - - getNowledgeMemConfig(): NowledgeMemConfig -} -``` - ---- - -### 阶段 2:迁移搜索功能(2-3天) - -#### 2.1 创建 SearchPresenter - -**`src/main/presenter/searchPresenter/index.ts`** - -```typescript -import type { ISearchPresenter } from './interface' -import { SearchManager } from './managers/searchManager' -import { SearchHandler } from './handlers/searchHandler' -import type { IConfigPresenter, IWindowPresenter } from '@shared/presenter' -import type { ContentEnricher } from '@/main/presenter/content/contentEnricher' - -interface SearchPresenterDependencies { - configPresenter: IConfigPresenter - windowPresenter: IWindowPresenter - contentEnricher: ContentEnricher -} - -export class SearchPresenter implements ISearchPresenter { - private readonly searchManager: SearchManager - private readonly searchHandler: SearchHandler - - constructor(deps: SearchPresenterDependencies) { - this.searchManager = new SearchManager({ - configPresenter: deps.configPresenter, - windowPresenter: deps.windowPresenter, - contentEnricher: deps.contentEnricher - }) - this.searchHandler = new SearchHandler(this.searchManager) - } - - // 搜索引擎管理 - async getEngines(): Promise { - return this.searchManager.getEngines() - } - - async getActiveEngine(): Promise { - return this.searchManager.getActiveEngine() - } - - async setActiveEngine(engineId: string): Promise { - return this.searchManager.setActiveEngine(engineId) - } - - async testEngine(query?: string): Promise { - return this.searchManager.testSearch(query) - } - - // 搜索执行 - async executeSearch(conversationId: string, query: string): Promise { - return this.searchHandler.startStreamSearch(conversationId, query) - } - - async stopSearch(conversationId: string): Promise { - return this.searchManager.stopSearch(conversationId) - } - - // 搜索配置 - async updateEngines(engines: SearchEngineConfig[]): Promise { - return this.searchManager.updateEngines(engines) - } - - async addCustomEngine(engine: SearchEngineConfig): Promise { - return this.searchManager.addCustomEngine(engine) - } - - async removeCustomEngine(engineId: string): Promise { - return this.searchManager.removeCustomEngine(engineId) - } - - // 工具方法 - async search(query: string): Promise { - return this.searchManager.search(query) - } - - async testSearch(query?: string): Promise { - return this.searchManager.testSearch(query) - } - - destroy() { - this.searchManager.destroy() - } -} -``` - -#### 2.2 迁移文件清单 - -- `threadPresenter/managers/searchManager.ts` → `searchPresenter/managers/searchManager.ts` -- `threadPresenter/handlers/searchHandler.ts` → `searchPresenter/handlers/searchHandler.ts` -- `threadPresenter/const.ts` (搜索提示模板部分) → `searchPrompts/templates/searchPromptTemplate.ts` -- `threadPresenter/const.ts` (搜索提示 artifacts 模板) → `searchPrompts/templates/searchPromptArtifactsTemplate.ts` - -#### 2.3 更新主 Presenter 初始化 - -**修改 `src/main/presenter/index.ts`:** - -```typescript -export class Presenter implements IPresenter { - searchPresenter: ISearchPresenter - - private constructor(lifecycleManager: ILifecycleManager) { - // ... 现有初始化 ... - - // 初始化 SearchPresenter - this.searchPresenter = new SearchPresenter({ - configPresenter: this.configPresenter, - windowPresenter: this.windowPresenter, - contentEnricher: new ContentEnricher(this.configPresenter) - }) - } - - // ... -} -``` - ---- - -### 阶段 3:迁移导出功能(1-2天) - -#### 3.1 创建通用导出模块 - -**`src/main/presenter/exporter/index.ts`** - -```typescript -import type { IConversationExporter } from './interface' -import { ConversationExporter } from './formats/conversationExporter' -import { type ISQLitePresenter, type IConfigPresenter } from '@shared/presenter' - -interface ExporterDependencies { - sqlitePresenter: ISQLitePresenter - configPresenter: IConfigPresenter -} - -export class ConversationExporterService implements IConversationExporter { - private readonly exporter: ConversationExporter - - constructor(deps: ExporterDependencies) { - this.exporter = new ConversationExporter(deps) - } - - async exportConversation( - conversationId: string, - format: 'markdown' | 'html' | 'txt' - ): Promise<{ filename: string; content: string }> { - return this.exporter.export(conversationId, format) - } - - async exportToNowledgeMem(conversationId: string): Promise<{ - success: boolean - data?: NowledgeMemThread - summary?: NowledgeMemExportSummary - errors?: string[] - }> { - return this.exporter.exportToNowledgeMem(conversationId) - } - - async submitToNowledgeMem(conversationId: string): Promise<{ - success: boolean - threadId?: string - data?: NowledgeMemThread - errors?: string[] - }> { - return this.exporter.submitToNowledgeMem(conversationId) - } - - getNowledgeMemConfig(): NowledgeMemConfig { - return this.exporter.getConfig() - } - - async testNowledgeMemConnection(): Promise<{ - success: boolean - message?: string - error?: string - }> { - return this.exporter.testConnection() - } -} -``` - -#### 3.2 迁移文件清单 - -- `threadPresenter/exporters/conversationExporter.ts` → `exporter/formats/conversationExporter.ts` -- `threadPresenter/exporters/templates/conversationExportTemplates.ts` → `exporter/templates/conversationExportTemplates.ts` -- 保持 `nowledgeMemPresenter` 独立,不迁移 - -#### 3.3 更新接口定义 - -**`src/main/presenter/exporter/formats/conversationExporter.ts`** - -```typescript -export class ConversationExporter { - async export( - conversationId: string, - format: 'markdown' | 'html' | 'txt' - ): Promise<{ filename: string; content: string }> { - const conversation = await this.sqlitePresenter.getConversation(conversationId) - if (!conversation) { - throw new Error('Conversation not found') - } - - // 获取所有消息(分页处理) - const messages = await this.fetchAllMessages(conversationId) - - switch (format) { - case 'markdown': - return this.exportToMarkdown(conversation, messages) - case 'html': - return this.exportToHtml(conversation, messages) - case 'txt': - return this.exportToText(conversation, messages) - default: - throw new Error(`Unsupported format: ${format}`) - } - } - - private async exportToMarkdown( - conversation: CONVERSATION, - messages: Message[] - ): Promise<{ filename: string; content: string }> { - const content = buildMarkdownExportContent(conversation, messages) - const filename = generateExportFilename(conversation, 'md') - return { filename, content } - } - - private async exportToHtml( - conversation: CONVERSATION, - messages: Message[] - ): Promise<{ filename: string; content: string }> { - const content = buildHtmlExportContent(conversation, messages) - const filename = generateExportFilename(conversation, 'html') - return { filename, content } - } - - private async exportToTxt( - conversation: CONVERSATION, - messages: Message[] - ): Promise<{ filename: string; content: string }> { - const content = buildTextExportContent(conversation, messages) - const filename = generateExportFilename(conversation, 'txt') - return { filename, content } - } - - // NowledgeMem 集成 - async exportToNowledgeMem(conversationId: string): Promise<{ - success: boolean - data?: NowledgeMemThread - summary?: NowledgeMemExportSummary - errors?: string[] - }> { - const conversation = await this.sqlitePresenter.getConversation(conversationId) - if (!conversation) { - return { success: false, errors: ['Conversation not found'] } - } - - const messages = await this.fetchAllMessages(conversationId) - return buildNowledgeMemExportData(conversation, messages) - } -} -``` - ---- - -### 阶段 4:迁移会话核心功能到 sessionPresenter(3-4天) - -#### 4.1 迁移 ConversationManager - -**`src/main/presenter/sessionPresenter/managers/conversationManager.ts`** - -```typescript -import type { - CONVERSATION, - CONVERSATION_SETTINGS, - ISQLitePresenter, - IConfigPresenter, - ParentSelection -} from '@shared/presenter' -import type { Message } from '@shared/chat' -import { presenter } from '@/presenter' -import { eventBus, SendTarget } from '@/eventbus' -import { CONVERSATION_EVENTS, TAB_EVENTS } from '@/events' -import { DEFAULT_SETTINGS } from '../const' -import type { MessageManager } from './messageManager' -import type { TabManager } from '../tab/tabManager' - -export interface CreateConversationOptions { - forceNewAndActivate?: boolean - tabId?: number -} - -export interface CreateChildConversationParams { - parentConversationId: string - parentMessageId: string - parentSelection: ParentSelection | string - title: string - settings?: Partial - tabId?: number - openInNewTab?: boolean -} - -export class ConversationManager { - private readonly sqlitePresenter: ISQLitePresenter - private readonly configPresenter: IConfigPresenter - private readonly messageManager: MessageManager - private readonly tabManager: TabManager - private readonly activeConversationIds: Map - - constructor(options: { - sqlitePresenter: ISQLitePresenter - configPresenter: IConfigPresenter - messageManager: MessageManager - tabManager: TabManager - activeConversationIds: Map - }) { - this.sqlitePresenter = options.sqlitePresenter - this.configPresenter = options.configPresenter - this.messageManager = options.messageManager - this.tabManager = options.tabManager - this.activeConversationIds = options.activeConversationIds - } - - // === Tab绑定管理 === - - getActiveConversationIdSync(tabId: number): string | null { - return this.activeConversationIds.get(tabId) || null - } - - getTabsByConversation(conversationId: string): number[] { - return Array.from(this.activeConversationIds.entries()) - .filter(([, id]) => id === conversationId) - .map(([tabId]) => tabId) - } - - async setActiveConversation(conversationId: string, tabId: number): Promise { - // 验证会话存在 - const conversation = await this.sqlitePresenter.getConversation(conversationId) - if (!conversation) { - throw new Error(`Conversation not found: ${conversationId}`) - } - - // 检查是否已在其他 tab 中激活 - const existingTabId = await this.findTabForConversation(conversationId) - if (existingTabId && existingTabId !== tabId) { - // 可选:询问用户是否切换到已有的 tab - console.warn(`Conversation ${conversationId} is already active in tab ${existingTabId}`) - } - - // 设置激活状态 - this.activeConversationIds.set(tabId, conversationId) - eventBus.sendToRenderer(CONVERSATION_EVENTS.ACTIVATED, SendTarget.ALL_WINDOWS, { - tabId, - conversationId - }) - } - - async clearActiveConversation(tabId: number, options: { notify?: boolean } = {}): Promise { - if (!this.activeConversationIds.has(tabId)) { - return - } - this.activeConversationIds.delete(tabId) - if (options.notify) { - eventBus.sendToRenderer(CONVERSATION_EVENTS.DEACTIVATED, SendTarget.ALL_WINDOWS, { tabId }) - } - } - - async clearConversationBindings(conversationId: string): Promise { - for (const [tabId, activeId] of this.activeConversationIds.entries()) { - if (activeId === conversationId) { - this.activeConversationIds.delete(tabId) - eventBus.sendToRenderer(CONVERSATION_EVENTS.DEACTIVATED, SendTarget.ALL_WINDOWS, { - tabId - }) - } - } - } - - async findTabForConversation(conversationId: string): Promise { - for (const [tabId, activeId] of this.activeConversationIds.entries()) { - if (activeId === conversationId) { - try { - const tabView = await presenter.tabPresenter.getTab(tabId) - if (tabView && !tabView.webContents.isDestroyed()) { - return tabId - } - } catch (error) { - console.error('Error finding tab for conversation:', error) - } - } - } - return null - } - - // === 会话 CRUD === - - async createConversation( - title: string, - settings: Partial, - tabId: number, - options: CreateConversationOptions = {} - ): Promise { - const mergedSettings = { ...DEFAULT_SETTINGS, ...settings } - const conversationId = await this.sqlitePresenter.createConversation( - title, - mergedSettings - ) - - // 自动激活 - if (options.forceNewAndActivate || !this.activeConversationIds.has(tabId)) { - await this.setActiveConversation(conversationId, tabId) - } - - return conversationId - } - - async getConversation(conversationId: string): Promise { - const conversation = await this.sqlitePresenter.getConversation(conversationId) - if (!conversation) { - throw new Error(`Conversation not found: ${conversationId}`) - } - return conversation - } - - async getConversationList( - page: number, - pageSize: number - ): Promise<{ total: number; list: CONVERSATION[] }> { - return this.sqlitePresenter.getConversationList(page, pageSize) - } - - async renameConversation(conversationId: string, title: string): Promise { - return this.sqlitePresenter.updateConversation(conversationId, { title }) - } - - async deleteConversation(conversationId: string): Promise { - // 清除绑定 - this.clearConversationBindings(conversationId) - - // 清除消息 - await this.sqlitePresenter.deleteAllMessages(conversationId) - - // 删除会话 - await this.sqlitePresenter.deleteConversation(conversationId) - } - - async toggleConversationPinned(conversationId: string, pinned: boolean): Promise { - return this.sqlitePresenter.updateConversation(conversationId, { pinned: pinned ? 1 : 0 }) - } - - async updateConversationSettings( - conversationId: string, - settings: Partial - ): Promise { - return this.sqlitePresenter.updateConversation(conversationId, { settings }) - } - - async updateConversationTitle(conversationId: string, title: string): Promise { - return this.sqlitePresenter.updateConversation(conversationId, { title }) - } - - async loadMoreThreads(): Promise<{ hasMore: boolean; total: number }> { - // 实现分页加载逻辑 - // 这里需要根据实际需求实现 - return { hasMore: false, total: 0 } - } - - async broadcastThreadListUpdate(): Promise { - eventBus.sendToRenderer(CONVERSATION_EVENTS.LIST_UPDATED, SendTarget.ALL_WINDOWS, {}) - } - - // === 会话分支 === - - async forkConversation( - targetConversationId: string, - targetMessageId: string, - newTitle: string, - settings?: Partial, - selectedVariantsMap?: Record - ): Promise { - // 1. 获取源会话和消息 - const sourceConversation = await this.sqlitePresenter.getConversation(targetConversationId) - if (!sourceConversation) { - throw new Error('Source conversation not found') - } - - const targetMessage = await this.messageManager.getMessage(targetMessageId) - if (!targetMessage) { - throw new Error('Target message not found') - } - - // 2. 创建新会话 - const mergedSettings = { - ...sourceConversation.settings, - ...settings, - selectedVariantsMap, - parentConversationId: targetConversationId, - parentMessageId: targetMessageId, - is_new: 0 - } - mergedSettings.selectedVariantsMap = {} - - const newConversationId = await this.sqlitePresenter.createConversation(newTitle, mergedSettings) - - // 3. 复制消息(只到目标消息及其父消息) - await this.copyMessagesUpTo( - targetConversationId, - newConversationId, - targetMessageId, - selectedVariantsMap - ) - - // 4. 广播更新 - await this.broadcastThreadListUpdate() - - return newConversationId - } - - async createChildConversationFromSelection( - payload: CreateChildConversationParams - ): Promise { - const { - parentConversationId, - parentMessageId, - parentSelection, - title, - settings, - tabId, - openInNewTab - } = payload - - const parentConversation = await this.sqlitePresenter.getConversation(parentConversationId) - if (!parentConversation) { - throw new Error('Parent conversation not found') - } - - await this.messageManager.getMessage(parentMessageId) - - const mergedSettings = { - ...parentConversation.settings, - ...settings - } - mergedSettings.selectedVariantsMap = {} - - const newConversationId = await this.sqlitePresenter.createConversation(title, mergedSettings) - const resolvedParentSelection = - typeof parentSelection === 'string' - ? (() => { - try { - return JSON.parse(parentSelection) as ParentSelection - } catch { - throw new Error('Invalid parent selection payload') - } - })() - : parentSelection - await this.sqlitePresenter.updateConversation(newConversationId, { - is_new: 0, - parentConversationId, - parentMessageId, - parentSelection: resolvedParentSelection - }) - - const shouldOpenInNewTab = openInNewTab ?? true - if (shouldOpenInNewTab && tabId) { - const sourceWindowId = presenter.tabPresenter.getWindowIdByWebContentsId(tabId) - const fallbackWindowId = presenter.windowPresenter.getFocusedWindow()?.id - const windowId = sourceWindowId ?? fallbackWindowId - - if (windowId) { - const newTabId = await presenter.tabPresenter.createTab(windowId, 'local://chat', { - active: true - }) - if (newTabId) { - await this.setActiveConversation(newConversationId, newTabId) - await this.broadcastThreadListUpdate() - return newConversationId - } - } - } - - if (tabId) { - await this.setActiveConversation(newConversationId, tabId) - } - - await this.broadcastThreadListUpdate() - return newConversationId - } - - // === 子会话查询 === - - async listChildConversationsByParent(parentConversationId: string): Promise { - return this.sqlitePresenter.listChildConversationsByParent(parentConversationId) - } - - async listChildConversationsByMessageIds(parentMessageIds: string[]): Promise { - return this.sqlitePresenter.listChildConversationsByMessageIds(parentMessageIds) - } - - // === 私有方法 === - - private async copyMessagesUpTo( - sourceConversationId: string, - targetConversationId: string, - targetMessageId: string, - selectedVariantsMap?: Record - ): Promise { - const allMessages = await this.messageManager.getMessageHistory(targetMessageId, 1000) - - for (let i = 0; i < allMessages.length; i++) { - const msg = allMessages[i] - if (msg.id === targetMessageId) { - // 到达目标消息,停止复制 - break - } - - // 应用变体选择 - let content = msg.content - if (msg.role === 'assistant' && selectedVariantsMap && selectedVariantsMap[msg.id] && msg.variants) { - const variant = msg.variants.find((v) => v.id === selectedVariantsMap[msg.id]) - if (variant) { - content = variant.content - } - } - - // 插入消息到目标会话 - await this.sqlitePresenter.insertMessage( - targetConversationId, - JSON.stringify(content), - msg.role, - msg.parentId || '', - JSON.stringify(msg.usage), - msg.is_variant, - msg.timestamp || Date.now(), - msg.status - ) - } - } -} -``` - -#### 4.2 迁移 MessageManager - -**`src/main/presenter/sessionPresenter/managers/messageManager.ts`** - -```typescript -import { - IMessageManager, - MESSAGE_METADATA, - MESSAGE_ROLE, - MESSAGE_STATUS, - ISQLitePresenter, - SQLITE_MESSAGE -} from '@shared/presenter' -import { - Message, - AssistantMessageBlock, - UserMessageContent, - UserMessageTextBlock, - UserMessageMentionBlock, - UserMessageCodeBlock -} from '@shared/chat' -import { eventBus, SendTarget } from '@/eventbus' -import { CONVERSATION_EVENTS } from '@/events' - -export class MessageManager implements IMessageManager { - private sqlitePresenter: ISQLitePresenter - - constructor(sqlitePresenter: ISQLitePresenter) { - this.sqlitePresenter = sqlitePresenter - } - - // === 消息 CRUD === - - async sendMessage( - conversationId: string, - content: string, - role: MESSAGE_ROLE, - parentId: string, - isVariant: boolean, - metadata: MESSAGE_METADATA, - searchResults?: string - ): Promise { - const maxOrderSeq = await this.sqlitePresenter.getMaxOrderSeq(conversationId) - const msgId = await this.sqlitePresenter.insertMessage( - conversationId, - content, - role, - parentId, - JSON.stringify(metadata), - isVariant, - maxOrderSeq + 1, - Date.now(), - 'pending' - ) - - // 如果有搜索结果,保存为附件 - if (searchResults) { - await this.sqlitePresenter.insertMessageAttachment(msgId, 'search_result', searchResults) - } - - return this.getMessage(msgId) - } - - async getMessage(messageId: string): Promise { - const sqliteMessage = await this.sqlitePresenter.getMessage(messageId) - if (!sqliteMessage) { - throw new Error(`Message not found: ${messageId}`) - } - return this.convertToMessage(sqliteMessage) - } - - async editMessage(messageId: string, content: string): Promise { - await this.sqlitePresenter.updateMessage(messageId, { content }) - return this.getMessage(messageId) - } - - async deleteMessage(messageId: string): Promise { - await this.sqlitePresenter.deleteMessage(messageId) - } - - async retryMessage(messageId: string, metadata: MESSAGE_METADATA): Promise { - const originalMessage = await this.getMessage(messageId) - if (originalMessage.role !== 'assistant') { - throw new Error('Can only retry assistant messages') - } - - if (!originalMessage.parentId) { - throw new Error('Assistant message must have a parent') - } - - const newMessage = await this.sendMessage( - originalMessage.conversationId, - JSON.stringify([]), - 'assistant', - originalMessage.parentId, - false, - metadata - ) - - return newMessage - } - - async updateMessageStatus(messageId: string, status: MESSAGE_STATUS): Promise { - await this.sqlitePresenter.updateMessage(messageId, { status }) - } - - async updateMessageMetadata(messageId: string, metadata: Partial): Promise { - const existing = await this.getMessage(messageId) - const currentMetadata: MESSAGE_METADATA = existing.usage - const merged = { ...currentMetadata, ...metadata } - - await this.sqlitePresenter.updateMessage(messageId, { - metadata: JSON.stringify(merged) - }) - } - - async markMessageAsContextEdge(messageId: string, isEdge: boolean): Promise { - await this.updateMessageMetadata(messageId, { contextEdge: isEdge ? 1 : 0 }) - } - - // === 消息查询 === - - async getMessageThread( - conversationId: string, - page: number, - pageSize: number - ): Promise<{ total: number; list: Message[] }> { - return this.sqlitePresenter.getMessageThread(conversationId, page, pageSize) - } - - async getMessageHistory(messageId: string, limit: number = 100): Promise { - const message = await this.getMessage(messageId) - const { list } = await this.getMessageThread(message.conversationId, 1, limit) - - // 找到目标消息的位置 - const index = list.findIndex(msg => msg.id === messageId) - if (index === -1) { - return list - } - - return list.slice(index + 1) - } - - async getContextMessages(conversationId: string, messageCount: number): Promise { - const { list } = await this.getMessageThread(conversationId, 1, messageCount) - return list.slice(-messageCount) - } - - async getMessageVariants(messageId: string): Promise { - const message = await this.getMessage(messageId) - return message.variants || [] - } - - async getMainMessageByParentId( - conversationId: string, - parentId: string - ): Promise { - const messages = await this.sqlitePresenter.getMessagesByParentId(conversationId, parentId) - if (messages.length === 0) { - return null - } - - // 返回第一条(通常是按 order_seq 排序的) - return this.convertToMessage(messages[0]) - } - -// ... (继续补充剩余方法) -``` - -#### 4.3 补充 SessionPresenter 缺失的方法 - -**需要在 `sessionPresenter/index.ts` 中添加的方法:** - -```typescript -// 从 StreamGenerationHandler 迁移 -async startStreamCompletion( - sessionId: string, - queryMsgId?: string, - selectedVariantsMap?: Record -): Promise { - const streamingGenerator = new StreamingGenerator(this.dependencies) - return streamingGenerator.startStream(sessionId, queryMsgId, selectedVariantsMap) -} - -async continueStreamCompletion( - sessionId: string, - queryMsgId: string, - selectedVariantsMap?: Record -): Promise { - const streamingGenerator = new StreamingGenerator(this.dependencies) - return streamingGenerator.continueStream(sessionId, queryMsgId, selectedVariantsMap) -} - -async regenerateFromUserMessage( - sessionId: string, - userMessageId: string, - selectedVariantsMap?: Record -): Promise { - const userMessage = await this.messagePersister.getMessage(userMessageId) - if (!userMessage || userMessage.role !== 'user') { - throw new Error('Can only regenerate based on user messages.') - } - - const conversation = await this.conversationPersister.getConversation(sessionId) - const { providerId, modelId } = conversation.settings - - const assistantMessage = await this.messagePersister.insertMessage( - sessionId, - JSON.stringify([]), - 'assistant', - userMessageId, - JSON.stringify({ - totalTokens: 0, - generationTime: 0, - firstTokenTime: 0, - tokensPerSecond: 0, - contextUsage: 0, - inputTokens: 0, - outputTokens: 0, - model: modelId, - provider: providerId - }), - 0, - Date.now(), - 'pending', - 0, - 0 - ) - - // 启动流式生成 - await this.startStreamCompletion(sessionId, userMessageId, selectedVariantsMap) - - return (await this.getMessage(assistantMessage)) as AssistantMessage -} - -// 从 PermissionHandler 迁移 -async handlePermissionResponse( - messageId: string, - toolCallId: string, - granted: boolean, - permissionType: 'read' | 'write' | 'all' | 'command', - remember: boolean = true -): Promise { - const permissionCoordinator = new PermissionCoordinator(this.dependencies) - return permissionCoordinator.handlePermissionResponse( - messageId, - toolCallId, - granted, - permissionType, - remember - ) -} - -// 从 MessageManager 迁移 -async editMessage(messageId: string, content: string): Promise { - const messageManager = new MessageManager(this.sqlitePresenter) - return messageManager.editMessage(messageId, content) -} - -async retryMessage(messageId: string): Promise { - const messageManager = new MessageManager(this.sqlitePresenter) - const message = await messageManager.getMessage(messageId) - - const metadata: MESSAGE_METADATA = { - contextUsage: 0, - totalTokens: 0, - generationTime: 0, - firstTokenTime: 0, - tokensPerSecond: 0, - inputTokens: 0, - outputTokens: 0, - model: message.model_id, - provider: message.model_provider - } - - return messageManager.retryMessage(messageId, metadata) -} - -async getMessageThread( - sessionId: string, - page: number, - pageSize: number -): Promise<{ total: number; messages: Message[] }> { - const messageManager = new MessageManager(this.sqlitePresenter) - const result = await messageManager.getMessageThread(sessionId, page, pageSize) - return { total: result.total, messages: result.list } -} - -async getContextMessages(sessionId: string): Promise { - const conversation = await this.conversationPersister.getConversation(sessionId) - let messageCount = Math.ceil(conversation.settings.contextLength / 300) - if (messageCount < 2) { - messageCount = 2 - } - const messageManager = new MessageManager(this.sqlitePresenter) - return messageManager.getContextMessages(sessionId, messageCount) -} - -async clearContext(sessionId: string): Promise { - await this.sqlitePresenter.deleteAllMessages(sessionId) -} - -async clearAllMessages(sessionId: string): Promise { - await this.sqlitePresenter.deleteAllMessages(sessionId) - - // 停止所有正在生成的消息 - const tabs = this.getActiveTabsBySession(sessionId) - for (const tabId of tabs) { - await this.stopConversationGeneration(sessionId) - } -} - -async stopConversationGeneration(sessionId: string): Promise { - const messageIds = Array.from(this.activeLoops.entries()) - .filter(([, state]) => state.conversationId === sessionId) - .map(([messageId]) => messageId) - - await Promise.all(messageIds.map((messageId) => this.cancelLoop(sessionId, messageId))) -} -``` - -#### 4.4 创建流式生成协调器 - -**`src/main/presenter/sessionPresenter/streaming/streamingGenerator.ts`** (新整合类) - -```typescript -import type { SessionPresenterDependencies } from '../index' -import type { ISearchPresenter } from '@/main/presenter/searchPresenter' -import type { Message, AssistantMessage } from '@shared/chat' -import type { SearchResult, CONVERSATION } from '@shared/presenter' -import { LoopOrchestrator } from '../loop/loopOrchestrator' -import { preparePromptContent } from '../message/messageBuilder' -import { buildUserMessageContext, formatUserMessageContent } from '../message/messageFormatter' -import { ContentEnricher } from '@/main/presenter/content/contentEnricher' - -export class StreamingGenerator { - constructor(private deps: SessionPresenterDependencies) {} - - async startStream( - sessionId: string, - userMessageId?: string, - selectedVariantsMap?: Record - ): Promise { - const session = await this.deps.sessionManager.getSession(sessionId) - - // 1. 准备上下文 - const { conversation, userMessage, contextMessages } = await this.prepareConversationContext( - sessionId, - userMessageId, - selectedVariantsMap - ) - - // 2. 处理用户消息内容 - const { userContent, urlResults, imageFiles } = await this.processUserMessageContent( - userMessage - ) - - // 3. 执行搜索(如果需要) - let searchResults: SearchResult[] | null = null - if ((userMessage.content as any).search) { - searchResults = await this.executeSearch(sessionId, userContent) - } - - // 4. 准备提示内容 - const { finalContent, promptTokens } = await preparePromptContent({ - conversation, - userContent, - contextMessages, - searchResults, - urlResults, - userMessage, - vision: false, - imageFiles: [], - supportsFunctionCall: false, - modelType: 'chat' - }) - - // 5. 更新生成状态 - await this.updateGenerationState(sessionId, userMessage?.id || '', promptTokens) - - // 6. 启动 Agent Loop - const stream = this.deps.llmProviderPresenter.startStreamCompletion( - conversation.settings.providerId, - finalContent, - conversation.settings.modelId, - userMessageId || '', - conversation.settings.temperature, - conversation.settings.maxTokens, - conversation.settings.enabledMcpTools, - conversation.settings.thinkingBudget, - conversation.settings.reasoningEffort, - conversation.settings.verbosity, - conversation.settings.enableSearch, - conversation.settings.forcedSearch, - conversation.settings.searchStrategy, - sessionId - ) - - // 7. 通过 LoopOrchestrator 消费流 - const loopOrchestrator = new LoopOrchestrator({ - handleLLMAgentResponse: async (msg) => this.handleResponse(msg), - handleLLMAgentError: async (msg) => this.handleError(msg), - handleLLMAgentEnd: async (msg) => this.handleEnd(msg) - }) - - await loopOrchestrator.consume(stream) - } - - async continueStream( - sessionId: string, - queryMsgId: string, - selectedVariantsMap?: Record - ): Promise { - const session = await this.deps.sessionManager.getSession(sessionId) - - // 1. 获取查询消息 - const queryMessage = await this.deps.messagePersister.getMessage(queryMsgId) - if (!queryMessage) { - throw new Error('Message not found') - } - - // 2. 处理待定的工具调用 - 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) { - // 执行工具调用 - const toolCallResponse = await this.executeToolCall(lastActionBlock.tool_call, sessionId) - - // 发送工具调用事件 - await this.sendToolCallEvents(lastActionBlock.tool_call, toolCallResponse, queryMsgId) - } - - // 3. 准备上下文并继续生成 - const { conversation, contextMessages, userMessage } = await this.prepareConversationContext( - sessionId, - queryMsgId, - selectedVariantsMap - ) - - const { finalContent } = await preparePromptContent({ - conversation, - userContent: 'continue', - contextMessages, - searchResults: null, - urlResults: [], - userMessage, - vision: false, - imageFiles: [], - supportsFunctionCall: false, - modelType: 'chat' - }) - - const stream = this.deps.llmProviderPresenter.startStreamCompletion( - conversation.settings.providerId, - finalContent, - conversation.settings.modelId, - queryMsgId, - conversation.settings.temperature, - conversation.settings.maxTokens, - conversation.settings.enabledMcpTools, - conversation.settings.thinkingBudget, - conversation.settings.reasoningEffort, - conversation.settings.verbosity, - conversation.settings.enableSearch, - conversation.settings.forcedSearch, - conversation.settings.searchStrategy, - sessionId - ) - - const loopOrchestrator = new LoopOrchestrator({ - handleLLMAgentResponse: async (msg) => this.handleResponse(msg), - handleLLMAgentError: async (msg) => this.handleError(msg), - handleLLMAgentEnd: async (msg) => this.handleEnd(msg) - }) - - await loopOrchestrator.consume(stream) - } - - private async prepareConversationContext( - sessionId: string, - queryMsgId?: string, - selectedVariantsMap?: Record - ): Promise<{ - conversation: CONVERSATION - userMessage: Message - contextMessages: Message[] - }> { - const conversation = await this.deps.conversationPersister.getConversation(sessionId) - let contextMessages: Message[] = [] - let userMessage: Message | null = null - - if (queryMsgId) { - const queryMessage = await this.deps.messagePersister.getMessage(queryMsgId) - if (!queryMessage) { - throw new Error('Message not found') - } - - if (queryMessage.role === 'user') { - userMessage = queryMessage - } else if (queryMessage.role === 'assistant') { - if (!queryMessage.parentId) { - throw new Error('Assistant message missing parentId') - } - userMessage = await this.deps.messagePersister.getMessage(queryMessage.parentId) - if (!userMessage) { - throw new Error('Trigger message not found') - } - } else { - throw new Error('Unsupported message type') - } - - contextMessages = await this.getMessageHistory(userMessage.id, 100) - } else { - userMessage = await this.deps.messagePersister.getLastUserMessage(sessionId) - if (!userMessage) { - throw new Error('User message not found') - } - contextMessages = await this.getContextMessages(sessionId) - } - - return { conversation, userMessage: userMessage!, contextMessages } - } - - private async processUserMessageContent( - userMessage: Message - ): Promise<{ userContent: string; urlResults: SearchResult[]; imageFiles: any[] }> { - const userContent = buildUserMessageContext(userMessage.content) - const normalizedText = (userMessage.content as any).text || userContent - const urlResults = await ContentEnricher.extractAndEnrichUrls(normalizedText) - const imageFiles = [] - - return { userContent, urlResults, imageFiles } - } - - private async executeSearch(sessionId: string, query: string): Promise { - // 集成 searchPresenter - return [] // 实现搜索逻辑 - } - - private async executeToolCall(toolCall: any, sessionId: string): Promise { - // 实现工具调用逻辑 - return { content: '', rawData: {} } - } - - private async handleResponse(msg: any): Promise { - // 处理 LLM 响应 - } - - private async handleError(msg: any): Promise { - // 处理 LLM 错误 - } - - private async handleEnd(msg: any): Promise { - // 处理 LLM 结束 - } - - private async updateGenerationState(sessionId: string, messageId: string, promptTokens: number): Promise { - // 更新生成状态 - } - - private async getMessageHistory(messageId: string, limit: number): Promise { - return [] - } - - private async getContextMessages(sessionId: string): Promise { - return [] - } - - private async sendToolCallEvents(toolCall: any, response: any, messageId: string): Promise { - // 发送工具调用事件 - } -} -``` - -#### 4.5 创建权限协调器 - -**`src/main/presenter/sessionPresenter/permission/permissionCoordinator.ts`** - -```typescript -import type { SessionPresenterDependencies } from '../index' -import type { IToolPresenter, ILlmProviderPresenter, IMCPPresenter } from '@shared/presenter' -import type { PermissionState } from '../types' -import { CommandPermissionService } from '@/main/presenter/permission/commandPermissionService' -import type { AssistantMessageBlock } from '@shared/chat' -import { buildContinueToolCallContext, buildPostToolExecutionContext } from '../message/messageBuilder' - -export class PermissionCoordinator { - constructor(private deps: SessionPresenterDependencies) {} - - async handlePermissionResponse( - messageId: string, - toolCallId: string, - granted: boolean, - permissionType: 'read' | 'write' | 'all' | 'command', - remember: boolean = true - ): Promise { - const message = await this.deps.messagePersister.getMessage(messageId) - if (!message || message.role !== 'assistant') { - throw new Error(`Message not found or not assistant message (${messageId})`) - } - - const content = message.content as AssistantMessageBlock[] - const permissionBlock = content.find( - (block) => - block.type === 'action' && - block.action_type === 'tool_call_permission' && - block.tool_call?.id === toolCallId - ) - - if (!permissionBlock) { - throw new Error( - `Permission block not found (messageId: ${messageId}, toolCallId: ${toolCallId})` - ) - } - - const isAcpPermission = this.isAcpPermissionBlock(permissionBlock) - - // 更新权限块状态 - permissionBlock.status = granted ? 'granted' : 'denied' - if (permissionBlock.extra) { - permissionBlock.extra.needsUserAction = false - if (granted) { - permissionBlock.extra.grantedPermissions = permissionType - } - } - - await this.deps.messagePersister.updateMessage(messageId, { - content: JSON.stringify(content), - metadata: JSON.stringify({ ...message, content }) - }) - - if (isAcpPermission) { - await this.handleAcpPermissionFlow(messageId, permissionBlock, granted) - this.deps.sessionManager.clearPendingPermission(message.conversationId) - this.deps.sessionManager.setStatus(message.conversationId, 'generating') - return - } - - if (permissionType === 'command') { - if (granted) { - await this.handleCommandPermission(messageId, permissionBlock, remember) - await this.restartAgentLoopAfterPermission(messageId) - } else { - await this.continueAfterPermissionDenied(messageId) - } - return - } - - // MCP 权限 - if (granted) { - const serverName = permissionBlock?.extra?.serverName as string - if (!serverName) { - throw new Error(`Server name not found in permission block (${messageId})`) - } - - await this.deps.mcpPresenter.grantPermission(serverName, permissionType, remember) - await this.waitForMcpServiceReady(serverName) - await this.restartAgentLoopAfterPermission(messageId) - } else { - await this.continueAfterPermissionDenied(messageId) - } - } - - private async handleAcpPermissionFlow( - messageId: string, - block: AssistantMessageBlock, - granted: boolean - ): Promise { - const requestId = this.getExtraString(block, 'permissionRequestId') - if (!requestId) { - throw new Error(`Missing ACP permission request identifier for message ${messageId}`) - } - - await this.deps.llmProviderPresenter.resolveAgentPermission(requestId, granted) - } - - private async handleCommandPermission( - messageId: string, - block: AssistantMessageBlock, - remember: boolean - ): Promise { - const conversationId = (await this.deps.messagePersister.getMessage(messageId)).conversationId - const command = this.getCommandFromPermissionBlock(block) - if (!command) { - throw new Error(`Unable to extract command from permission block (${messageId})`) - } - - const signature = CommandPermissionService.extractCommandSignature(command) - CommandPermissionService.approve(conversationId, signature, remember) - } - - private async restartAgentLoopAfterPermission(messageId: string): Promise { - const message = await this.deps.messagePersister.getMessage(messageId) - if (!message) { - throw new Error(`Message not found (${messageId})`) - } - - const sessionId = message.conversationId - await this.deps.sessionManager.startLoop(sessionId, messageId) - - // 恢复流式生成 - const streamingGenerator = new (await import('../streaming/streamingGenerator')).StreamingGenerator(this.deps) - await streamingGenerator.continueStream(sessionId, messageId) - } - - private async continueAfterPermissionDenied(messageId: string): Promise { - const message = await this.deps.messagePersister.getMessage(messageId) - if (!message || message.role !== 'assistant') { - throw new Error(`Message not found or not assistant message (${messageId})`) - } - - const sessionId = message.conversationId - const content = message.content as AssistantMessageBlock[] - const deniedPermissionBlock = content.find( - (block) => - block.type === 'action' && - block.action_type === 'tool_call_permission' && - block.status === 'denied' - ) - - if (!deniedPermissionBlock?.tool_call) { - console.warn('[PermissionCoordinator] No denied permission block for', messageId) - return - } - - const toolCall = deniedPermissionBlock.tool_call - const errorMessage = `Tool execution failed: Permission denied by user for ${ - toolCall.name || 'this tool' - }` - - // 发送工具调用结束事件 - // ... 发送事件 - - // 继续生成 - await this.deps.sessionManager.startLoop(sessionId, messageId) - - const streamingGenerator = new (await import('../streaming/streamingGenerator')).StreamingGenerator(this.deps) - await streamingGenerator.continueStream(sessionId, messageId) - } - - private async waitForMcpServiceReady(serverName: string, maxWaitTime: number = 3000): Promise { - const startTime = Date.now() - - return new Promise((resolve) => { - const checkReady = async () => { - try { - const isRunning = await this.deps.mcpPresenter.isServerRunning(serverName) - if (isRunning) { - setTimeout(() => resolve(), 200) - return - } - - if (Date.now() - startTime > maxWaitTime) { - console.warn('[PermissionCoordinator] Timeout waiting for MCP service', serverName) - resolve() - return - } - - setTimeout(checkReady, 100) - } catch (error) { - console.error('[PermissionCoordinator] Error checking MCP service status:', error) - resolve() - } - } - - checkReady() - }) - } - - private isAcpPermissionBlock(block: AssistantMessageBlock): boolean { - const providerIdFromExtra = this.getExtraString(block, 'providerId') - return providerIdFromExtra === 'acp' - } - - private getExtraString(block: AssistantMessageBlock, key: string): string | undefined { - const extraValue = block.extra?.[key] - return typeof extraValue === 'string' ? extraValue : undefined - } - - private getCommandFromPermissionBlock(block: AssistantMessageBlock): string | undefined { - const extraCommandInfo = this.getExtraString(block, 'commandInfo') - if (extraCommandInfo) { - try { - const parsed = JSON.parse(extraCommandInfo) as { command?: string } - if (parsed?.command) { - return parsed.command - } - } catch (error) { - console.warn('[PermissionCoordinator] Failed to parse commandInfo:', error) - } - } - - const params = block.tool_call?.params - if (typeof params === 'string' && params.trim()) { - try { - const parsed = JSON.parse(params) as { command?: string } - if (typeof parsed.command === 'string') { - return parsed.command - } - } catch { - // Ignore parse errors - } - } - - return undefined - } -} -``` - ---- - -### 阶段 5:迁移辅助功能(2天) - -#### 5.1 拆分 UtilityHandler - -**`src/main/presenter/sessionPresenter/utility/aiService.ts`** - -```typescript -import type { Message } from '@shared/chat' -import { preparePromptContent } from '../message/messageBuilder' - -export class AIService { - constructor( - private llmProviderPresenter: any, - private configPresenter: any - ) {} - - async translateText(text: string, sessionId: string): Promise { - const conversation = await this.conversationPersister.getConversation(sessionId) - - const stream = this.llmProviderPresenter.startStreamCompletion( - conversation.settings.providerId, - [ - { - role: 'system', - content: 'You are a professional translator. Translate the following text into the user\'s language.' - }, - { - role: 'user', - content: text - } - ], - conversation.settings.modelId, - `${Date.now()}-translate`, - 0.3, - 2048, - [], - undefined, - undefined, - undefined, - false, - false, - undefined, - sessionId - ) - - let result = '' - for await (const event of stream) { - if (event.type === 'response') { - result += event.data.content || '' - } - } - - return result || text - } - - async askAI(text: string, sessionId: string): Promise { - const conversation = await this.conversationPersister.getConversation(sessionId) - - const stream = this.llmProviderPresenter.startStreamCompletion( - conversation.settings.providerId, - [ - { - role: 'system', - content: 'You are a helpful AI assistant.' - }, - { - role: 'user', - content: text - } - ], - conversation.settings.modelId, - `${Date.now()}-ask-ai`, - 0.7, - 2048, - [], - undefined, - undefined, - undefined, - false, - false, - undefined, - sessionId - ) - - let result = '' - for await (const event of stream) { - if (event.type === 'response') { - result += event.data.content || '' - } - } - - return result || 'Sorry, I could not generate a response.' - } -} -``` - -**`src/main/presenter/sessionPresenter/utility/titleGenerator.ts`** - -```typescript -export class TitleGenerator { - constructor(private llmProviderPresenter: any, private configPresenter: any) {} - - async generateTitle(conversationId: string, maxRetries: number = 3): Promise { - const conversation = await this.conversationPersister.getConversation(conversationId) - const messages = await this.messagePersister.queryMessages(conversationId) - - if (messages.length < 3) { - return 'New Chat' - } - - const userMessages = messages.filter((msg) => msg.role === 'user') - if (userMessages.length === 0) { - return 'New Chat' - } - - // 使用前3条用户消息生成标题 - const contextText = userMessages.slice(0, 3).map((msg) => { - const content = JSON.parse(msg.content) - return (content.text || content.content as string || '...').slice(0, 200) - }).join('\n\n') - - const prompt = `Generate a concise title (max 50 characters) for this conversation based on the user messages:\n\n${contextText}\n\nOnly output the title, no other text.` - - const stream = this.llmProviderPresenter.startStreamCompletion( - conversation.settings.providerId, - [ - { - role: 'system', - content: 'You are a helpful assistant that generates concise conversation titles.' - }, - { - role: 'user', - content: prompt - } - ], - conversation.settings.modelId, - `${Date.now()}-title`, - 0.7, - 100, - [], - undefined, - undefined, - undefined, - false, - false, - undefined, - conversationId - ) - - let result = '' - for await (const event of stream) { - if (event.type === 'response') { - result += event.data.content || '' - } - } - - const title = result.trim().slice(0, 50) - return title || 'New Chat' - } -} -``` - -**`src/main/presenter/sessionPresenter/utility/debuggerService.ts`** - -```typescript -export class DebuggerService { - constructor( - private sessionManager: any, - private messagePersister: any - ) {} - - async getMessageRequestPreview(messageId: string): Promise { - const message = await this.messagePersister.getMessage(messageId) - const conversation = await this.conversationPersister.getConversation(message.conversationId) - - const { contextMessages, userMessage } = await this.deps.streamingGenerator.prepareConversationContext( - message.conversationId, - message.parentId - ) - - // 构建请求预览(脱敏敏感信息) - const preview = { - conversationId: message.conversationId, - messageId: message.id, - settings: { - providerId: conversation.settings.providerId, - modelId: conversation.settings.modelId, - temperature: conversation.settings.temperature, - maxTokens: conversation.settings.maxTokens, - enabledMcpTools: conversation.settings.enabledMcpTools - }, - contextMessagesCount: contextMessages.length, - userMessage: { - role: userMessage.role, - content: typeof userMessage.content === 'string' - ? userMessage.content.slice(0, 200) + '...' - : '[Complex content]' - }, - toolDefinitions: this.previewToolDefinitions(conversation), - reasoningBudget: conversation.settings.thinkingBudget, - searchEnabled: conversation.settings.enableSearch - } - - return preview - } - - private previewToolDefinitions(conversation: any): Array<{ name: string; count: number }> { - // 返回工具定义的摘要信息 - return [] - } -} -``` - -**`src/main/presenter/sessionPresenter/utility/index.ts`** - -```typescript -export { AIService } from './aiService' -export { TitleGenerator } from './titleGenerator' -export { DebuggerService } from './debuggerService' -``` - -#### 5.2 创建独立的权限检查服务 - -**`src/main/presenter/permission/commandPermissionService.ts`** - -```typescript -import { CommandPermissionCache } from './commandPermissionCache' - -export type RiskLevel = 'low' | 'medium' | 'high' | 'critical' - -export interface PermissionCheckResult { - allowed: boolean - reason: 'whitelist' | 'session' | 'permission' | 'invalid' - riskLevel?: RiskLevel -} - -export class CommandPermissionService { - private static instance: CommandPermissionService - private readonly cache: CommandPermissionCache = new CommandPermissionCache() - - private static readonly SAFE_COMMANDS = new Set([ - 'ls', 'pwd', 'cd', 'cat', 'head', 'tail', 'grep', 'find', - 'echo', 'printf', 'date', 'whoami', 'id', 'env' - ]) - - private static readonly RISK_PATTERNS = [ - { pattern: /rm\s+-r?f?\s*\//, level: 'critical' as const }, - { pattern: /:\s*\(.*\)/, level: 'medium' as const }, // 命令链 - { pattern: /sudo\s+[^-]/, level: 'high' as const }, - { pattern: /\.\/|sh\s+/, level: 'medium' as const }, - { pattern: /curl|wget|nc|telnet/, level: 'medium' as const } - ] - - static getInstance(): CommandPermissionService { - if (!CommandPermissionService.instance) { - CommandPermissionService.instance = new CommandPermissionService() - } - return CommandPermissionService.instance - } - - checkPermission(conversationId: string, command: string): PermissionCheckResult { - // 1. 检查白名单 - const baseCommand = this.extractBaseCommand(command) - if (CommandPermissionService.SAFE_COMMANDS.has(baseCommand)) { - return { allowed: true, reason: 'whitelist', riskLevel: 'low' } - } - - // 2. 检查会话缓存 - const signature = this.extractCommandSignature(command) - const cached = this.cache.get(conversationId, signature) - if (cached) { - return { allowed: true, reason: 'session', riskLevel: cached.riskLevel } - } - - // 3. 评估风险 - const riskLevel = this.assessCommandRisk(command) - if (riskLevel === 'low') { - return { allowed: true, reason: 'whitelist', riskLevel } - } - - return { allowed: false, reason: 'permission', riskLevel } - } - - assessCommandRisk(command: string): RiskLevel { - // 检查高风险模式 - for (const { pattern, level } of CommandPermissionService.RISK_PATTERNS) { - if (pattern.test(command)) { - return level - } - } - - // 检查构建命令 - if (/^npm run|gradlew|mvn\b/.test(command)) { - return 'medium' - } - - // 检查文件操作 - if (/^rm\s+|^mv\s+|^cp\s+/.test(command)) { - const target = command.split(/\s+/).pop() - if (target?.includes('/') && !target.includes('.')) { - return 'high' // 删除/移动目录 - } - } - - return 'low' - } - - approve(conversationId: string, signature: string, remember: boolean): void { - const command = this.parseCommandFromSignature(signature) - const riskLevel = this.assessCommandRisk(command) - this.cache.set(conversationId, signature, { riskLevel, remember }) - } - - clearConversation(conversationId: string): void { - this.cache.clearConversation(conversationId) - } - - clearAll(): void { - this.cache.clearAll() - } - - extractBaseCommand(command: string): string { - return command.trim().split(/\s+/)[0].replace(/^-+/, '') - } - - extractCommandSignature(command: string): string { - const baseCommand = this.extractBaseCommand(command) - const args = command.split(/\s+/).slice(1).join(' ') - return `${baseCommand} ${args}`.trim() - } - - private parseCommandFromSignature(signature: string): string { - return signature - } -} - -// 导出缓存类 -export { CommandPermissionCache } from './commandPermissionCache' -``` - -**`src/main/presenter/permission/commandPermissionCache.ts`** - -```typescript -import type { RiskLevel } from './commandPermissionService' - -interface CacheEntry { - riskLevel: RiskLevel - remember: boolean - timestamp: number -} - -export class CommandPermissionCache { - private readonly cache: Map> = new Map() - private readonly GLOBAL_CACHE_KEY = '_global' - - get(conversationId: string, signature: string): CacheEntry | null { - const sessionCache = this.cache.get(conversationId) - if (sessionCache) { - const entry = sessionCache.get(signature) - if (entry && entry.remember) { - return entry - } - } - - // 检查全局缓存 - const globalCache = this.cache.get(this.GLOBAL_CACHE_KEY) - if (globalCache) { - return globalCache.get(signature) || null - } - - return null - } - - set(conversationId: string, signature: string, entry: Omit): void { - const cache = remember ? this.cache.get(this.GLOBAL_CACHE_KEY) : this.cache.get(conversationId) - if (!cache) { - if (entry.remember) { - this.cache.set(this.GLOBAL_CACHE_KEY, new Map()) - } else { - this.cache.set(conversationId, new Map()) - } - } - - const targetCache = entry.remember - ? this.cache.get(this.GLOBAL_CACHE_KEY)! - : this.cache.get(conversationId)! - - targetCache.set(signature, { - ...entry, - timestamp: Date.now() - }) - } - - clearConversation(conversationId: string): void { - this.cache.delete(conversationId) - } - - clearAll(): void { - this.cache.clear() - } -} -``` - -**`src/main/presenter/permission/index.ts`** - -```typescript -export { CommandPermissionService } from './commandPermissionService' -export { CommandPermissionCache } from './commandPermissionCache' -export type { RiskLevel, PermissionCheckResult } from './commandPermissionService' -``` - -#### 5.3 创建内容处理服务 - -**`src/main/presenter/content/contentEnricher.ts`** - -```typescript -import axios from 'axios' -import * as cheerio from 'cheerio' -import { HttpsProxyAgent } from 'https-proxy-agent' -import type { IConfigPresenter, SearchResult } from '@shared/presenter' - -export class ContentEnricher { - private static instance: ContentEnricher - - constructor(private configPresenter: IConfigPresenter) {} - - static getInstance(configPresenter: IConfigPresenter): ContentEnricher { - if (!ContentEnricher.instance) { - ContentEnricher.instance = new ContentEnricher(configPresenter) - } - return ContentEnricher.instance - } - - static async extractAndEnrichUrls(text: string): Promise { - const urlRegex = /https?:\/\/[^\s]+/g - const urls = text.match(urlRegex) || [] - - const results = await Promise.all( - urls.map(async (url) => { - try { - return await ContentEnricher.enrichUrl(url, {}) - } catch (error) { - console.error('Failed to enrich URL:', url, error) - return null - } - }) - ) - - return results.filter((r): r is SearchResult => r !== null) - } - - static async enrichUrl(url: string, config: any = {}): Promise { - const response = await axios.get(url, { - timeout: 10000, - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' - } - }) - - const $ = cheerio.load(response.data) - - const title = $('title').text().trim() - const description = $('meta[name="description"]').attr('content') || '' - const favicon = ContentEnricher.extractFavicon(url, $) - const content = ContentEnricher.extractMainContent($) - - return { - title, - url, - description, - favicon, - content: content.slice(0, 5000) - } - } - - static extractFavicon(url: string, $: cheerio.CheerioAPI): string { - const faviconUrl = $('link[rel="icon"], link[rel="shortcut icon"]').attr('href') - if (!faviconUrl) { - return '' - } - - try { - return new URL(faviconUrl, url).href - } catch { - return '' - } - } - - static extractMainContent($: cheerio.CheerioAPI): string { - // 移除不需要的元素 - $('script, style, nav, footer, aside, header').remove() - - // 尝试多种选择器 - const selectors = [ - 'article', - 'main', - '[role="main"]', - '.content', - '.post-content', - '#content', - 'body' - ] - - for (const selector of selectors) { - const element = $(selector).first() - if (element.length > 0) { - return element.text().trim().slice(0, 10000) - } - } - - return $('body').text().trim().slice(0, 10000) - } - - static async htmlToMarkdown(html: string): Promise { - // 使用 turndown 或类似库 - return html.replace(/<[^>]+>/g, '') - } -} - -export { ContentEnricher } from './contentEnricher' -``` - ---- - -### 阶段 6:更新 IPC 接口(1-2天) - -#### 6.1 更新 `@shared/presenter.d.ts` - -```typescript -export interface ISessionPresenter extends IThreadPresenter { - // === Session Lifecycle === - createSession(params: CreateSessionParams): Promise - getSession(sessionId: string): Promise - getSessionList(page: number, pageSize: number): Promise<{total: number; sessions: Session[]}> - renameSession(sessionId: string, title: string): Promise - deleteSession(sessionId: string): Promise - toggleSessionPinned(sessionId: string, pinned: boolean): Promise - updateSessionSettings(sessionId: string, settings: Partial): Promise - - // === Session-Tab Binding === - bindToTab(sessionId: string, tabId: number): Promise - unbindFromTab(tabId: number): Promise - activateSession(tabId: number, sessionId: string): Promise - getActiveSession(tabId: number): Promise - findTabForSession(sessionId: string, preferredWindowType?: 'main' | 'floating'): Promise - - // === Message === - sendMessage(sessionId: string, content: string, tabId?: number, selectedVariantsMap?: Record): Promise - editMessage(messageId: string, content: string): Promise - deleteMessage(messageId: string): Promise - retryMessage(messageId: string): Promise - getMessage(messageId: string): Promise - getMessageVariants(messageId: string): Promise - getMessageThread(sessionId: string, page: number, pageSize: number): Promise<{ total: number; messages: Message[] }> - updateMessageStatus(messageId: string, status: MESSAGE_STATUS): Promise - updateMessageMetadata(messageId: string, metadata: Partial): Promise - markMessageAsContextEdge(messageId: string, isEdge: boolean): Promise - getContextMessages(sessionId: string): Promise - getLastUserMessage(sessionId: string): Promise - - // === Loop Control === - startStreamCompletion(sessionId: string, queryMsgId?: string, selectedVariantsMap?: Record): Promise - continueStreamCompletion(sessionId: string, queryMsgId: string, selectedVariantsMap?: Record): Promise - stopStreamCompletion(sessionId: string, messageId?: string): Promise - regenerateFromUserMessage(sessionId: string, userMessageId: string, selectedVariantsMap?: Record): Promise - - // === Session Branching === - forkSession(targetSessionId: string, targetMessageId: string, newTitle: string, settings?: Partial, selectedVariantsMap?: Record): Promise - createChildSessionFromSelection(params: CreateChildSessionParams): Promise - listChildSessionsByParent(parentSessionId: string): Promise - listChildSessionsByMessageIds(parentMessageIds: string[]): Promise - - //=== Session Helpers === - clearContext(sessionId: string): Promise - clearAllMessages(sessionId: string): Promise - stopSessionGeneration(sessionId: string): Promise - - // === Permission === - handlePermissionResponse(messageId: string, toolCallId: string, granted: boolean, permissionType: 'read' | 'write' | 'all' | 'command', remember?: boolean): Promise - - // === Utility === - translateText(text: string, sessionId: string): Promise - askAI(text: string, sessionId: string): Promise - generateTitle(sessionId: string): Promise - getMessageRequestPreview(messageId: string): Promise - - // === ACP Workdir === - getAcpWorkdir(conversationId: string, agentId: string): Promise - setAcpWorkdir(conversationId: string, agentId: string, workdir: string | null): Promise - warmupAcpProcess(agentId: string, workdir: string): Promise - getAcpProcessModes(agentId: string, workdir: string): Promise<{availableModes?: any; currentModeId?: string}> - setAcpPreferredProcessMode(agentId: string, workdir: string, modeId: string) - setAcpSessionMode(conversationId: string, modeId: string): Promise - getAcpSessionModes(conversationId: string): Promise<{current: string; available: any[]}> -} - -export interface ISearchPresenter { - getEngines(): Promise - getActiveEngine(): Promise - setActiveEngine(engineId: string): Promise - testEngine(query?: string): Promise - updateEngines(engines: SearchEngineConfig[]): Promise - addCustomEngine(engine: SearchEngineConfig): Promise - removeCustomEngine(engineId: string): Promise -} - -export interface IExporter { - exportConversation(conversationId: string, format: 'markdown' | 'html' | 'txt'): Promise<{filename: string; content: string}> -} -``` - -#### 6.2 更新渲染进程调用示例 - -在 `src/renderer/` 中寻找所有 `threadPresenter` 的调用: - -```typescript -// 之前: -await presenter.threadPresenter.createConversation(title, settings, tabId) -await presenter.threadPresenter.sendMessage(conversationId, content, 'user') -await presenter.threadPresenter.getSearchEngines() - -// 之后: -await presenter.sessionPresenter.createSession({ title, settings, tabId }) -await presenter.sessionPresenter.sendMessage(conversationId, content, tabId) -await presenter.searchPresenter.getEngines() -``` - ---- - -### 阶段 7:清理与测试(2-3天) - -#### 7.1 删除 threadPresenter - -```bash -# 确认所有功能已迁移后执行 -rm -rf src/main/presenter/threadPresenter -``` - -#### 7.2 更新导入 - -全局搜索并替换: - -```bash -# 搜索所有引用 threadPresenter 的文件 -grep -r "threadPresenter" src/main src/renderer --include="*.ts" --include="*.tsx" - -# 替换导入语句 -from '@/presenter/threadPresenter' - → -from '@/presenter/sessionPresenter' -``` - -#### 7.3 更新主 Presenter - -**修改 `src/main/presenter/index.ts`:** - -```typescript -export class Presenter implements IPresenter { - windowPresenter: IWindowPresenter - sqlitePresenter: ISQLitePresenter - llmproviderPresenter: ILlmProviderPresenter - configPresenter: IConfigPresenter - - // 新增 - sessionPresenter: ISessionPresenter - searchPresenter: ISearchPresenter - - // 移除 - // threadPresenter: IThreadPresenter - - agentPresenter: IAgentPresenter - // ... 其他 presenter - - private constructor(lifecycleManager: ILifecycleManager) { - this.lifecycleManager = lifecycleManager - const context = lifecycleManager.getLifecycleContext() - this.configPresenter = context.config as IConfigPresenter - this.sqlitePresenter = context.database as ISQLitePresenter - - // 初始化其他 presenter - this.windowPresenter = new WindowPresenter(this.configPresenter) - this.tabPresenter = new TabPresenter(this.windowPresenter) - this.llmproviderPresenter = new LLMProviderPresenter(this.configPresenter, this.sqlitePresenter) - - // 初始化 SessionPresenter (包含会话管理、Agent Loop、ACP等) - const commandPermissionService = CommandPermissionService.getInstance() - this.sessionPresenter = new SessionPresenter({ - sessionManager: new SessionManager({...}), - tabManager: new TabManager(...), - conversationPersister: new ConversationPersister(this.sqlitePresenter), - messagePersister: new MessagePersister(this.sqlitePresenter), - agentLoopHandler: new AgentLoopHandler(...), - toolCallCenter: new ToolCallCenter(...), - configPresenter: this.configPresenter, - toolPresenter: this.toolPresenter, - llmProviderPresenter: this.llmproviderPresenter - }) - - // 初始化 SearchPresenter - this.searchPresenter = new SearchPresenter({ - configPresenter: this.configPresenter, - windowPresenter: this.windowPresenter, - contentEnricher: ContentEnricher.getInstance(this.configPresenter) - }) - - // 初始化其他 presenter... - } - - // ... -} -``` - -#### 7.4 测试清单 - -**核心会话功能:** -- ✅ 创建会话(createSession) -- ✅ 删除会话(deleteSession) -- ✅ 重命名会话(renameSession) -- ✅ 获取会话列表(getSessionList) -- ✅ 会话固定/取消固定(toggleSessionPinned) - -**消息功能:** -- ✅ 发送消息(sendMessage) -- ✅ 编辑消息(editMessage) -- ✅ 删除消息(deleteMessage) -- ✅ 重试消息(retryMessage) -- ✅ 获取消息(getMessage) -- ✅ 获取消息变体(getMessageVariants) -- ✅ 分页加载消息(getMessageThread) -- ✅ 获取上下文消息(getContextMessages) - -**会话分支:** -- ✅ 创建分支会话(forkSession) -- ✅ 创建子会话(createChildSessionFromSelection) -- ✅ 查询子会话(listChildSessionsByParent) - -**Tab 绑定:** -- ✅ 绑定会话到 tab(bindToTab) -- ✅ 解绑会话(unbindFromTab) -- ✅ 激活会话(activateSession) -- ✅ 获取激活会话(getActiveSession) -- ✅ 查找会话的 tab(findTabForSession) - -**Agent Loop:** -- ✅ 启动流式生成(startStreamCompletion) -- ✅ 继续流式生成(continueStreamCompletion) -- ✅ 停止生成(cancelLoop) -- ✅ 从用户消息重新生成(regenerateFromUserMessage) - -**权限系统:** -- ✅ 处理权限响应(handlePermissionResponse) -- ✅ 命令权限检查(CommandPermissionService) -- ✅ ACP 权限流程 -- ✅ MCP 权限流程 - -**搜索功能:** -- ✅ 获取搜索引擎列表(getEngines) -- ✅ 设置激活的搜索引擎(setActiveEngine) -- ✅ 测试搜索引擎(testEngine) -- ✅ 执行搜索(executeSearch) - -**辅助功能:** -- ✅ 翻译文本(translateText) -- ✅ AI 问答(askAI) -- ✅ 生成标题(generateTitle) -- ✅ 导出会话(exportConversation) - ---- - -## 依赖更新清单 - -### 需要更新的文件 - -1. **`src/main/presenter/index.ts`** - - 添加 `sessionPresenter`, `searchPresenter`, `exporter` 实例 - - 移除 `threadPresenter` - - 更新依赖注入 - -2. **`src/main/presenter/toolPresenter/index.ts`** - - 更新 `CommandPermissionHandler` 导入路径为 `@/main/presenter/permission` - -3. **`src/shared/presenter.d.ts`** - - 更新 `ISessionPresenter`, `ISearchPresenter`, `IExporter` 接口 - -4. **所有引用 threadPresenter 的渲染进程文件** - - 更新导入语句 - - 更新方法调用名称 - ---- - -## 关键风险点与缓解措施 - -### 1. 权限系统耦合 - -**风险**:`PermissionHandler` 依赖多个 presenter (LLMProvider, MCP, Tool) - -**缓解措施**: -- 创建 `PermissionCoordinator` 在 sessionPresenter 内部协调 -- 通过接口依赖注入,避免直接引用 - -### 2. 循环依赖 - -**风险**:StreamGenerationHandler 依赖 SearchHandler,反之亦然 - -**缓解措施**: -- 使用 `ISearchPresenter` 接口 -- 通过依赖注入,避免直接引用 - -### 3. 消息变体 - -**风险**:上下文消息构建依赖变体系统 - -**缓解措施**: -- 在 `MessageManager` 中完整实现变体逻辑 -- 在 `prepareConversationContext` 中处理变体选择 - -### 4. 搜索提示模板 - -**风险**:`const.ts` 包含硬编码的搜索提示 - -**缓解措施**: -- 提取到 `searchPrompts/`,保持可配置性 -- 支持模板的动态加载和更新 - -### 5. 测试覆盖 - -**风险**:大规模重构可能导致现有测试失败 - -**缓解措施**: -- 先迁移并测试单个模块 -- 逐步集成,每个阶段都运行测试 -- 补充新的测试用例 - ---- - -## 工作量估算 - -| 阶段 | 工作内容 | 预计时间 | 风险 | -|------|----------|----------|------| -| 1 | 准备基础设施 | 1-2天 | 低 | -| 2 | 迁移搜索功能 | 2-3天 | 中 | -| 3 | 迁移导出功能 | 1-2天 | 低 | -| 4 | 迁移会话核心功能 | 3-4天 | **高** | -| 5 | 迁移辅助功能 | 2天 | 中 | -| 6 | 更新 IPC 接口 | 1-2天 | 中 | -| 7 | 清理与测试 | 2-3天 | **高** | -| **总计** | | **12-16天** | | - ---- - -## 后续优化建议 - -### 1. 单元测试 -在迁移过程中或完成后,为关键模块添加单元测试: -- `SessionManager` 测试 -- `MessageManager` 测试 -- `PermissionCoordinator` 测试 - -### 2. 类型安全 -完善 TypeScript 类型定义,减少 `any` 类型的不必要使用 - -### 3. 性能优化 -- 优化大消息的加载和传输 -- 实现消息分页的虚拟滚动 -- 优化搜索结果的提取和缓存 - -### 4. 文档更新 -更新 API 文档和架构说明文档 - ---- - -## 附录:文件移动清单 - -### 从 threadPresenter 迁移到 sessionPresenter - -| 源文件 | 目标位置 | 优先级 | -|--------|----------|--------| -| `managers/conversationManager.ts` | `sessionPresenter/managers/` | P0 | -| `managers/messageManager.ts` | `sessionPresenter/managers/` | P0 | -| `handlers/streamGenerationHandler.ts` | 整合到 `sessionPresenter/streaming/` | P0 | -| `handlers/llmEventHandler.ts` | 整合到 `sessionPresenter/streaming/` | P0 | -| `handlers/toolCallHandler.ts` | 整合到 `sessionPresenter/loop/` | P1 | -| `handlers/permissionHandler.ts` | `sessionPresenter/permission/` | P0 | -| `handlers/contentBufferHandler.ts` | 整合到 `sessionPresenter/streaming/` | P1 | -| `handlers/searchHandler.ts` | `searchPresenter/handlers/` | P0 | -| `handlers/utilityHandler.ts` | 拆分到多个模块 | P0 | -| `handlers/commandPermissionHandler.ts` | `permission/` | P0 | -| `exporters/conversationExporter.ts` | `exporter/formats/` | P1 | -| `utils/contentEnricher.ts` | `content/` | P2 | -| `const.ts` (搜索提示) | `searchPrompts/templates/` | P2 | -| `types.ts` | 部分到 `sessionPresenter/types.ts` | P0 | - -### 从 threadPresenter 迁移到 searchPresenter - -| 源文件 | 目标位置 | 优先级 | -|--------|----------|--------| -| `managers/searchManager.ts` | `searchPresenter/managers/` | P0 | -| `handlers/searchHandler.ts` | `searchPresenter/handlers/` | P0 | - -### 从 threadPresenter 迁移到共享模块 - -| 源文件 | 目标位置 | 优先级 | -|--------|----------|--------| -| `handlers/commandPermissionHandler.ts` | `permission/commandPermissionService.ts` | P0 | -| `utils/contentEnricher.ts` | `content/contentEnricher.ts` | P2 | - ---- - -## 总结 - -这份迁移方案提供了从 `threadPresenter` 到 `sessionPresenter` 的完整迁移路径,包括: - -1. **清晰的模块划分**:根据功能职责将功能分配到合适的模块 -2. **详细的实施步骤**:7个阶段,每个阶段都有具体的任务和代码示例 -3. **完整的风险分析**:识别关键风险点并提供缓解措施 -4. **准确的工作量估算**:12-16天,可分阶段实施 -5. **测试清单**:确保迁移后所有功能正常运行 - -核心原则是: -- **完全迁移**:不保留向后兼容 -- **功能归位**:会话相关功能去 sessionPresenter,辅助功能去合适的地方 -- **保留特性**:子会话、消息变体、搜索、导出等所有功能完整保留 - -迁移完成后,将获得: -- 更清晰的架构和职责分离 -- 更好的可维护性和可测试性 -- 更容易扩展的功能模块 -- 统一的术语概念(session 替代 thread) diff --git a/docs/archives/workspace-agent-refactoring-summary.md b/docs/archives/workspace-agent-refactoring-summary.md deleted file mode 100644 index f4d76c36a..000000000 --- a/docs/archives/workspace-agent-refactoring-summary.md +++ /dev/null @@ -1,323 +0,0 @@ -# 通用 Workspace 和 Agent 能力重构实施总结 - -> Archive note: This document is a historical record. File paths and implementation names can reference code that has since moved or been removed. - - -## 概述 - -本次重构围绕“统一工具路由 + 通用 Workspace 视图 + Mode 化能力开关”推进:工具调用统一经 ToolPresenter/ToolMapper 管控,Agent 工具拆为 Yo Browser + Agent FileSystem(仅 agent 模式启用),ACP agent 仍走 ACP provider 内置工具流;Workspace UI 对 agent/acp agent 通用,路径选择与会话设置同步,并补齐安全边界与文件刷新机制。 - -## 架构概览 - -```mermaid -graph TB - subgraph "Agent Loop" - AL[AgentLoopHandler] - TCP[ToolCallProcessor] - end - - subgraph "统一工具路由" - TP[ToolPresenter] - TM[ToolMapper] - end - - subgraph "工具源" - MCP[MCP Tools] - AGENT[Agent Tools (agent mode)] - end - - subgraph "Agent 工具" - YO[Yo Browser] - FS[Agent FileSystem] - end - - subgraph "Workspace" - WS[WorkspaceView] - FILES[Files Section] - PLAN[Plan Section] - TERM_UI[Terminal Section] - BROWSER_UI[Browser Tabs Section] - end - - AL --> TCP - TCP --> TP - TP --> TM - TM --> MCP - TM --> AGENT - AGENT --> YO - AGENT --> FS - WS --> FILES - WS --> PLAN - WS --> TERM_UI - WS --> BROWSER_UI -``` - -> Browser Tabs 仅在 agent 模式展示;acp agent 模式仍使用 ACP workdir 与 ACP provider 工具流。 - -## 已完成的工作 - -### 1. 统一工具路由架构 ✅ - -**实现文件**: -- `src/main/presenter/toolPresenter/index.ts` -- `src/main/presenter/toolPresenter/toolMapper.ts` - -**功能**: -- `ToolPresenter` 统一汇总 MCP + Agent 工具,输出 MCP 规范 `MCPToolDefinition` -- `ToolMapper` 维护工具名 → 来源映射,冲突时优先 MCP -- 工具调用统一经 `ToolPresenter.callTool()`,参数解析失败时尝试 `jsonrepair` - -### 2. Agent 工具管理 ✅ - -**实现文件**: -- `src/main/presenter/agentPresenter/acp/agentToolManager.ts` - -**功能**: -- Agent 工具包含 Yo Browser + Agent FileSystem -- **仅在 `agent` 模式下注入**(`acp agent` 不注入 Agent 工具) -- Yo Browser 工具根据 `supportsVision` 动态注入 -- 缺省工作目录生成于 `temp/deepchat-agent/workspaces` - -### 3. Agent 文件系统能力 ✅ - -**实现文件**: -- `src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts` - -**功能**: -- 内置文件工具:`read_file`, `write_file`, `list_directory`, `create_directory`, `move_files`, - `edit_text`, `glob_search`, `grep_search`, `text_replace`, `directory_tree`, `get_file_info` -- 强制路径白名单 + `realpath` 校验,阻断越界与 symlink 绕过 -- 正则工具使用 `validateRegexPattern` 防 ReDoS;`text_replace`/`edit_text` 支持 diff -- 工具以 `agent-filesystem` server 标识返回 - -### 4. Chat Mode Switch 配置 ✅ - -**实现文件**: -- `src/renderer/src/components/chat-input/composables/useChatMode.ts` -- `src/renderer/src/components/chat-input/ChatInput.vue` - -**功能**: -- `chatMode` 存储在 `input_chatMode` -- 无 ACP agents 时隐藏 `acp agent`,并自动回退到 `chat` -- `isAgentMode` 用于统一控制 UI 与工具注入 - -### 5. Workspace 组件通用化 ✅ - -**实现文件**: -- `src/main/presenter/workspacePresenter/index.ts` -- `src/renderer/src/stores/workspace.ts` -- `src/renderer/src/components/workspace/WorkspaceView.vue` -- `src/renderer/src/components/workspace/WorkspaceFiles.vue` -- `src/renderer/src/components/workspace/WorkspaceFileNode.vue` -- `src/renderer/src/components/workspace/WorkspacePlan.vue` -- `src/renderer/src/components/workspace/WorkspaceTerminal.vue` -- `src/renderer/src/components/workspace/WorkspaceBrowserTabs.vue` -- `src/renderer/src/components/ChatView.vue` - -**功能**: -- Workspace UI 对 agent/acp agent 统一开放,Files/Plan/Terminal 共用 -- agent 模式额外展示 Browser Tabs(Yo Browser) -- Store 根据 `chatMode` 选择 `workspacePresenter` 或 `acpWorkspacePresenter` -- 文件树按需展开(lazy loading),支持打开文件/定位路径/插入路径 - -### 6. Workspace 路径选择(统一化)✅ - -**实现文件**: -- `src/renderer/src/components/chat-input/composables/useAgentWorkspace.ts` - -**功能**: -- `agent` 模式通过 `devicePresenter.selectDirectory` 选择目录 -- `acp agent` 模式走 ACP workdir(`useAcpWorkdir`) -- 路径与会话设置同步(会话未创建时暂存并补写) - -### 7. 模型选择逻辑更新 ✅ - -**实现文件**: -- `src/renderer/src/components/ModelChooser.vue` -- `src/renderer/src/components/ModelSelect.vue` - -**功能**: -- `acp agent` 模式仅展示 ACP provider -- 其他模式隐藏 ACP provider - -### 8. Agent Loop / 提示词与工具执行 ✅ - -**实现文件**: -- `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts` -- `src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` -- `src/main/presenter/threadPresenter/utils/promptBuilder.ts` -- `src/main/presenter/threadPresenter/handlers/streamGenerationHandler.ts` - -**功能**: -- `agent` 模式自动补全默认工作区并落库 -- system prompt 在 `agent` 模式追加当前工作目录 -- Yo Browser context 仅在 `agent` 模式下注入 -- ACP provider 的 tool call 由 provider 侧执行,流中直接返回结果 - -### 9. Workspace 文件刷新机制 ✅ - -**实现文件**: -- `src/main/presenter/agentPresenter/loop/agentLoopHandler.ts` -- `src/renderer/src/stores/workspace.ts` - -**功能**: -- `agent-filesystem` 调用完成时触发 `WORKSPACE_EVENTS.FILES_CHANGED` -- Workspace Store 对文件刷新做防抖合并 -- ACP provider 在流结束后触发刷新 - -### 10. 类型定义与 i18n ✅ - -**实现文件**: -- `src/shared/types/presenters/tool.presenter.d.ts` -- `src/shared/types/presenters/workspace.d.ts` -- `src/renderer/src/i18n/*/chat.json` -- `src/renderer/src/i18n/*/toolCall.json` - -**功能**: -- ToolPresenter、Workspace、ChatMode 相关类型补齐 -- 新增模式/Workspace/工具调用相关文案 - -## 关键文件 - -- `src/main/presenter/toolPresenter/index.ts`:统一工具定义与路由 -- `src/main/presenter/agentPresenter/acp/agentToolManager.ts`:Agent 工具装配 -- `src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts`:文件系统工具实现 -- `src/main/presenter/workspacePresenter/index.ts`:通用 Workspace Presenter -- `src/renderer/src/stores/workspace.ts`:Workspace 状态与事件同步 -- `src/renderer/src/components/workspace/WorkspaceView.vue`:Workspace 入口 UI -- `src/renderer/src/components/chat-input/composables/useChatMode.ts`:Mode 管理 -- `src/renderer/src/components/chat-input/composables/useAgentWorkspace.ts`:Workspace 路径选择 - -## 遗留/兼容 - -- `src/main/presenter/acpWorkspacePresenter/` 仍保留并在 `acp agent` 模式使用 -- Renderer 的 ACP Workspace 旧组件已移除,统一使用通用 Workspace 组件 - -## 关键技术点 - -### 工具命名规范 - -- MCP 工具:保持原始命名 -- Agent FileSystem 工具:不加前缀(`read_file` 等) -- Yo Browser:使用 `yo_browser_` 前缀 - -### 工具路由机制 - -- ToolPresenter 统一输出 MCP 规范 `MCPToolDefinition` -- ToolMapper 维护工具名 → 来源映射,冲突时偏向 MCP -- Agent 工具参数解析失败时尝试 `jsonrepair` - -### Agent 工具注入机制(基于 Mode) - -- `chat`:仅 MCP 工具 -- `agent`:MCP + Yo Browser + Agent FileSystem -- `acp agent`:MCP 工具;ACP provider 自执行工具调用 - -### 配置持久化 - -- `chatMode` 存储为 `input_chatMode` -- `agentWorkspacePath` 持久化到会话 `settings` -- `agent` 模式缺省路径自动写入会话设置 - -### Mode Switch 与 ACP Session Mode 的区别 - -- Chat Mode Switch:全局模式(chat/agent/acp agent) -- ACP Session Mode:ACP agent 内部会话模式,互不干扰 - -### 路径安全 - -- WorkspacePresenter:基于 `allowedWorkspaces` + `realpath` 限制访问 -- AgentFileSystemHandler:路径白名单 + symlink 校验 + regex 安全验证 - -### 默认工作区路径 - -- `agent` 模式缺省使用 `temp/deepchat-agent/workspaces[/conversationId]` -- 路径会持久化到会话设置,供后续恢复 - -### 向后兼容 - -- ACP provider 与 ACP workspace 逻辑保留 -- UI 统一收口到通用 Workspace 组件 - -## 如何测试 - -### Mode Switch - -1. 进入 ChatInput,确认 `acp agent` 仅在配置 ACP agents 时出现 -2. 切换模式,确认 UI 与模型列表同步更新 - -### Agent Workspace - -1. 切换到 `agent` 模式,选择目录 -2. 切换/重启应用后确认路径恢复 -3. 切换到 `acp agent`,确认使用 ACP workdir - -### 工具路由 - -1. `agent` 模式调用 `read_file` 等文件工具,确认走 Agent FileSystem -2. MCP 工具调用仍走 MCP Presenter -3. ACP provider 下 tool call 直接显示执行结果(不再本地执行) - -### Workspace UI - -1. `agent`/`acp agent` 模式下打开 Workspace -2. 文件树可展开并通过右键菜单打开/定位 -3. Browser Tabs 仅在 `agent` 模式显示 -4. 执行文件工具后文件树自动刷新 - -## 架构说明 - -### 数据流 - -``` -ChatMode - ↓ -ChatInput (Mode Switch) - ↓ -AgentLoopHandler (resolve workspace & tools) - ↓ -ToolPresenter → ToolMapper → MCP/Agent tools -``` - -### Workspace 数据流 - -``` -Workspace Path Select - ↓ -useAgentWorkspace / useAcpWorkdir - ↓ -WorkspacePresenter (register) - ↓ -WorkspaceStore - ↓ -WorkspaceView -``` - -### 工具调用流程 - -``` -Agent Loop - ↓ -ToolCallProcessor - ↓ -ToolPresenter.callTool() - ↓ -MCP Presenter / AgentToolManager - ↓ -Tool response → Workspace refresh (agent-filesystem) -``` - -> ACP provider 的 tool call 由 provider 侧执行,流中直接返回结果。 - -## 注意事项 - -1. Agent 工具仅在 `agent` 模式生效,`acp agent` 走 ACP provider 工具流 -2. Workspace 访问必须先注册允许路径 -3. 正则相关工具调用需遵循安全限制(pattern 长度与验证) - -## 未来扩展 - -1. Terminal 工具执行与 Workspace Terminal 的联动 -2. 工具注入更细粒度控制(按需加载) -3. 工具去重策略可配置化 -4. 多 Workspace 支持与模板化配置 diff --git a/docs/features/acp-agent-uninstall/plan.md b/docs/features/acp-agent-uninstall/plan.md deleted file mode 100644 index 117ec3693..000000000 --- a/docs/features/acp-agent-uninstall/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# ACP Agent Uninstall Plan - -## Main Process - -- Add `configPresenter.uninstallAcpRegistryAgent(agentId)` as the orchestration entrypoint. -- Keep sqlite persistence in `AgentRepository`. -- Add uninstall cleanup to `AcpLaunchSpecService` for registry install artifacts with path-boundary checks. -- After uninstall, mark the agent disabled and set install state back to `not_installed`. -- Reuse `handleAcpAgentsMutated([agentId])` so ACP processes are released and renderer state refreshes. - -## Renderer - -- Add uninstall actions in `AcpSettings.vue` for both installed cards and registry overlay rows. -- Confirm uninstall with a lightweight AlertDialog-based confirmation flow (`AlertDialog` component/modal) so the renderer uses the built-in AlertDialog UI for lightweight confirmation. -- Refresh ACP settings data after uninstall completes. - -## Tests - -- Cover binary uninstall cleanup and safe path handling in `AcpLaunchSpecService`. -- Cover repository/state reset for registry agents. -- Cover renderer uninstall CTA wiring in `AcpSettings.vue`. diff --git a/docs/features/acp-agent-uninstall/spec.md b/docs/features/acp-agent-uninstall/spec.md deleted file mode 100644 index 2b7922bea..000000000 --- a/docs/features/acp-agent-uninstall/spec.md +++ /dev/null @@ -1,25 +0,0 @@ -# ACP Agent Uninstall - -## Summary - -Add uninstall support for registry-backed ACP agents. The current ACP settings flow can install and enable registry agents, but it cannot uninstall them after they are added. - -## User Stories - -- As a user, I can uninstall an installed ACP registry agent from settings. -- As a user, uninstall removes local install artifacts and hides the agent from enabled ACP model choices. -- As a user, old sessions referencing that agent are preserved and can recover by reinstalling later. - -## Acceptance Criteria - -- ACP settings shows an uninstall action for installed registry agents. -- Uninstall deletes local binary install directories when the agent uses a binary distribution. -- Uninstall resets registry install state to `not_installed` and disables the agent. -- Uninstalled registry agents no longer appear in the enabled ACP model list. -- Existing session records keep their `agentId`; uninstall does not delete session history. - -## Non-Goals - -- No hard-delete of the registry agent row. -- No schema migration. -- No forced rewrite of historical session, remote binding, or subagent references. diff --git a/docs/features/acp-agent-uninstall/tasks.md b/docs/features/acp-agent-uninstall/tasks.md deleted file mode 100644 index 5927a0e95..000000000 --- a/docs/features/acp-agent-uninstall/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# ACP Agent Uninstall Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/acp-session-config-options/plan.md b/docs/features/acp-session-config-options/plan.md deleted file mode 100644 index cb625505c..000000000 --- a/docs/features/acp-session-config-options/plan.md +++ /dev/null @@ -1,127 +0,0 @@ -# ACP Session Config Options 实施计划 - -## 1. 当前基线 - -1. ACP 状态栏当前只把 provider/model 锁定为外层 ACP agent。 -2. ACP 内部 model/mode 能力部分来自旧 `models/modes`,没有统一配置状态。 -3. 新版 SDK `0.16.1` 已支持 `configOptions`、`config_option_update`、`session_info_update`、`usage_update`。 - -## 2. 设计决策 - -### 2.1 统一配置状态 - -新增共享类型: - -1. `AcpConfigOptionValue` -2. `AcpConfigOption` -3. `AcpConfigState` - -主进程通过 `normalizeAcpConfigState()` 统一把两类来源归一为同一结构: - -1. `configOptions` 直接映射,标记 `source=configOptions` -2. legacy `models/modes` 合成为伪 config option,标记 `source=legacy` - -### 2.2 Warmup / Session 双缓存 - -1. `AcpProcessHandle` 缓存 process 级 warmup config state -2. `AcpSessionRecord` 缓存 session 级 config state -3. `prepareAcpSession` 在 draft 建立后立即发出 config-ready 事件,让 renderer 无缝从 process cache 切到 session cache - -### 2.3 事件策略 - -新增事件: - -1. `ACP_WORKSPACE_EVENTS.SESSION_CONFIG_OPTIONS_READY` - -触发时机: - -1. process warmup 完成 -2. `prepareAcpSession` / `coreStream` 绑定 session 后 -3. 收到 `config_option_update` -4. `setSessionConfigOption` / legacy mode/model 写入成功后 - -### 2.4 Renderer 展示策略 - -状态栏维持双轨: - -1. 非 ACP:继续显示普通模型列表和 generation settings -2. ACP:显示 ACP options 面板,不再清空设置区 - -排序规则: - -1. `model` -2. `thought_level` -3. 其余按 agent 原顺序 - -读写规则: - -1. 有 `draftSessionId` 或活动 ACP session 时,走 session 级读写 -2. 仅有 process warmup 数据时,面板只读 - -UI refinement: - -1. ACP 状态栏隐藏 permission mode 入口,右侧只保留 `更多` 与最右侧 `MCP` -2. inline 只展示前 3 个 `select` 类型配置,触发器复用 MCP 风格的 ghost button + popover header -3. `boolean` 与剩余配置统一进入 `更多` 面板,不再 inline 展示 - -## 3. 分阶段实施 - -### Phase 1:SDK 与主进程兼容 - -1. 升级 SDK 到 `0.16.1` -2. 修正 schema import 路径 -3. 兼容 `unstable_setSessionModel` / `KillTerminalRequest` 等 SDK 差异 - -### Phase 2:ACP 配置状态归一 - -1. 新增 `acpConfigState.ts` -2. `AcpProcessManager` warmup 归一 `configOptions/models/modes` -3. `AcpSessionManager` 建 session 时继承并缓存统一 state -4. `AcpContentMapper` 支持 `config_option_update` - -### Phase 3:Presenter 与事件 - -1. `AcpProvider` 增加 process/session config 读写接口 -2. `LLMProviderPresenter` 和 `AgentSessionPresenter` 暴露代理方法 -3. 发出 `SESSION_CONFIG_OPTIONS_READY` - -### Phase 4:Renderer 状态栏 - -1. `NewThreadPage` 透传 ACP draft sessionId -2. `ChatStatusBar` 加入 ACP config 同步、只读控制和更新逻辑 -3. trigger 显示 `ACP agent / internal model` - -## 4. 测试策略 - -### 4.1 Main - -1. `AcpContentMapper` 覆盖 `config_option_update` -2. `AcpProvider.prepareSession` 发出 config-ready 事件 -3. `AcpProvider.setSessionConfigOption` 使用 agent 返回的全量 state 回写缓存 -4. `AgentSessionPresenter` 覆盖 ACP session config 读写代理 - -### 4.2 Renderer - -1. ACP draft 首屏读取 process warmup config -2. draft sessionId 就绪后切换到 session config -3. 写入 ACP select/boolean option 时调用 session presenter -4. 非 ACP 原有状态栏行为无回归 - -## 5. 风险与缓解 - -1. 风险:不同 ACP agent 同时返回 `configOptions` 与 legacy 字段。 - 缓解:统一以 `configOptions` 为准,legacy 仅在缺失时启用。 - -2. 风险:renderer 在 draft 早期拿不到 sessionId,导致控件误可写。 - 缓解:基于 `activeAcpSessionId` 做只读门控。 - -3. 风险:新 SDK 增加的 session 通知被误判为未处理异常。 - 缓解:`session_info_update` 与 `usage_update` 静默兼容。 - -## 6. 质量门槛 - -1. `pnpm run typecheck` -2. `pnpm run format` -3. `pnpm run i18n` -4. `pnpm run lint` -5. 关键 main/renderer 测试通过 diff --git a/docs/features/acp-session-config-options/spec.md b/docs/features/acp-session-config-options/spec.md deleted file mode 100644 index 124c15d10..000000000 --- a/docs/features/acp-session-config-options/spec.md +++ /dev/null @@ -1,93 +0,0 @@ -# ACP Session Config Options 规格 - -## 概述 - -为 ACP 接入协议级 `session config options`,让 ACP agent 的内部 `model`、`thought_level`、`mode`、布尔开关等能力在 DeepChat 状态栏中直接展示和修改。 - -本次改动同时完成: - -1. 升级 `@agentclientprotocol/sdk` 到 `0.16.1` -2. ACP 优先走新版 `configOptions` -3. 旧 `models/modes` 仅作为兼容兜底 -4. 预热阶段拿到的配置立即可展示,不等待首条消息 - -## 背景与动机 - -1. 当前 ACP 外层模型选择器只表示“ACP agent”,内部 session model 被固定,用户无法直观看到真实内部模型。 -2. 协议已提供 `session config options`,可以统一承载 `model`、`thought_level`、`mode` 以及其他 agent 自定义配置。 -3. ACP warmup 阶段已经能拿到这些信息,继续延迟到首条消息后再展示会造成首屏空窗。 - -## 用户故事 - -### US-1:看到 ACP 内部真实模型 - -作为用户,我希望状态栏显示 `ACP agent / 内部 model`,这样我能确认当前 ACP session 真正在用哪个模型。 - -### US-2:在新线程阶段就能看配置 - -作为用户,我希望 ACP draft 刚建立时就能看到 agent 的可配置项,而不是要先发一条消息。 - -### US-3:统一修改 ACP session 配置 - -作为用户,我希望在同一个状态栏面板里修改 ACP 的 `model`、`thought_level`、`mode` 和布尔类开关。 - -## 功能需求 - -### A. 配置来源与兼容策略 - -- [ ] ACP 主路径使用协议 `configOptions` -- [ ] 当 agent 未返回 `configOptions` 时,回退到 legacy `models/modes` -- [ ] 若同时返回两套字段,只采用 `configOptions` - -### B. 外层 agent 与内层 session model 分离 - -- [ ] 状态栏外层仍显示 ACP agent -- [ ] ACP 内部 `model` 不写回现有通用 `SessionGenerationSettings` -- [ ] ACP agent 不允许通过普通 `setSessionModel` 切走 provider/model - -### C. Warmup 与缓存 - -- [ ] warmup 的 `newSession/loadSession` 结果要归一为统一 `AcpConfigState` -- [ ] 预热缓存应绑定到 process handle -- [ ] `prepareAcpSession` 建立 draft/session 后立即把缓存灌入 session,并发出 ready 事件 - -### D. Renderer 状态栏 - -- [ ] 非 ACP 路径保持现有模型选择和 generation settings 行为 -- [ ] ACP 路径改为展示 ACP options 面板 -- [ ] `category=model` 作为首要选项,并参与 trigger 文案展示 -- [ ] `category=thought_level` 排在 `model` 后,保留 agent 返回的 label/value -- [ ] `category=mode` 与其他 generic 选项保持 agent 顺序 -- [ ] draft 无 sessionId 时展示 warmup 数据但控件只读 -- [ ] draft sessionId 就绪后切到 session 级读写 - -### E. 事件与接口 - -- [ ] 新增 `AcpConfigOption`、`AcpConfigOptionValue`、`AcpConfigState` -- [ ] `ILlmProviderPresenter` 增加 ACP process/session config 读写接口 -- [ ] `IAgentSessionPresenter` 增加 ACP session config 读写接口 -- [ ] 新增 renderer 事件 `ACP_WORKSPACE_EVENTS.SESSION_CONFIG_OPTIONS_READY` - -## 验收标准 - -- [ ] ACP trigger 可显示 `agentId / internal model` -- [ ] 新线程 ACP draft 能先显示 warmup config -- [ ] draft sessionId 就绪后可切到 session config 并允许写入 -- [ ] `config_option_update` 会整组替换当前 config state -- [ ] 旧 ACP agent 只有 `models/modes` 时仍可正常展示和切换 - -## 非目标 - -1. 不把 ACP `model/thought_level/mode` 混入通用 provider generation settings。 -2. 不重做非 ACP 状态栏 UI。 -3. 不在本次把 `systemPrompt/temperature/contextLength/maxTokens` 合并进 ACP options。 - -## 约束 - -1. 继续遵循现有 Presenter + EventBus 架构。 -2. 所有兼容逻辑集中在 ACP 主进程归一层,不在 renderer 分散判断 legacy 字段。 -3. UI 不新增独立 ACP 设置页,先复用状态栏入口。 - -## 开放问题 - -无。 diff --git a/docs/features/acp-session-config-options/tasks.md b/docs/features/acp-session-config-options/tasks.md deleted file mode 100644 index cac639748..000000000 --- a/docs/features/acp-session-config-options/tasks.md +++ /dev/null @@ -1,52 +0,0 @@ -# ACP Session Config Options 任务清单 - -## T0 规格文档 - -- [x] 新建 `spec.md` -- [x] 新建 `plan.md` -- [x] 新建 `tasks.md` - -## T1 SDK 与共享类型 - -- [x] 升级 `@agentclientprotocol/sdk` 到 `0.16.1` -- [x] 修正 ACP schema import 路径 -- [x] 新增 `AcpConfigOptionValue` -- [x] 新增 `AcpConfigOption` -- [x] 新增 `AcpConfigState` - -## T2 ACP 主进程归一层 - -- [x] 新增 `acpConfigState.ts` -- [x] warmup 解析 `configOptions` -- [x] legacy `models/modes` 回退归一 -- [x] `config_option_update` 整组替换 session config state -- [x] `session_info_update` / `usage_update` 静默兼容 - -## T3 Presenter / EventBus - -- [x] `AcpProcessHandle` / `AcpSessionRecord` 缓存统一 config state -- [x] 新增 `SESSION_CONFIG_OPTIONS_READY` 事件 -- [x] `ILlmProviderPresenter` 增加 ACP process/session config 读写接口 -- [x] `IAgentSessionPresenter` 增加 ACP session config 读写接口 - -## T4 Renderer 状态栏 - -- [x] `NewThreadPage` 透传 `acpDraftSessionId` -- [x] `ChatStatusBar` 增加 ACP config 拉取与事件订阅 -- [x] ACP trigger 显示 `agent / internal model` -- [x] draft 无 sessionId 时显示 warmup config 且只读 -- [x] draft sessionId 就绪后切换到 session 级读写 - -## T5 测试 - -- [x] 更新 `acpContentMapper.test.ts` -- [x] 更新 `acpProvider.test.ts` -- [x] 更新 `agentSessionPresenter.test.ts` -- [x] 更新 `ChatStatusBar.test.ts` - -## T6 质量门禁 - -- [x] `pnpm run format` -- [x] `pnpm run i18n` -- [x] `pnpm run lint` -- [x] 运行关键测试并记录结果 diff --git a/docs/features/active-input-routing/plan.md b/docs/features/active-input-routing/plan.md deleted file mode 100644 index cf98f0e24..000000000 --- a/docs/features/active-input-routing/plan.md +++ /dev/null @@ -1,40 +0,0 @@ -# Active Input Routing Plan - -## Behavior Model - -- `sendMessage`: normal turn submission. It starts immediately when the session is idle. -- `steerActiveTurn`: active-turn correction. If a generation is running, interrupt it cleanly and - continue with the steering message in the same session. If the session is idle, fall back to - normal turn submission. -- `queuePendingInput`: explicit future turn. It never claims active-turn semantics and remains - editable until claimed. - -## Event Flow - -1. Renderer checks whether the session is generating. -2. Enter emits `submit`; ChatPage routes it to `steerActiveTurn` while generating and to - `sendMessage` while idle. -3. Tab emits `queue-submit`; ChatPage routes it to `queuePendingInput`. -4. Runtime steer stores the steering payload as an interrupt continuation, aborts the active stream, - marks the partial assistant message as sent, then starts a new user turn with the steering payload. -5. Queue draining remains FIFO and waits until the session is idle, errored-and-resumed, or completed - without pending tool interactions. - -## IPC Surface - -- Add typed route `chat.steerActiveTurn`. -- Add renderer client method `steerActiveTurn`. -- Keep existing `sessions.queuePendingInput` for explicit queue. - -## UI - -- Input Enter remains the primary submit gesture. -- When generating, the toolbar send button tooltip/action label becomes Steer. -- Tab in the editor queues the draft while generating. -- Pending lane focuses on queued follow-ups; steer rows remain visible only for compatibility. - -## Test Strategy - -- Main presenter tests for idle send, explicit queue, and active steer interruption. -- Renderer tests for Enter versus Tab routing. -- Existing queue behavior should continue passing. diff --git a/docs/features/active-input-routing/spec.md b/docs/features/active-input-routing/spec.md deleted file mode 100644 index 3d973f5e4..000000000 --- a/docs/features/active-input-routing/spec.md +++ /dev/null @@ -1,32 +0,0 @@ -# Active Input Routing - -## User Story - -When a session is already working, users can correct the active task immediately or queue a -follow-up intentionally. Pressing Enter should feel like Codex CLI steer: it directs the current -work. Pressing Tab or the explicit queue action should feel like Claude Code queued messages: it -waits for the current work to settle, then runs in order. - -## Acceptance Criteria - -- Idle Enter sends a normal user message immediately and does not create a pending queue item. -- Running Enter steers the active turn instead of silently queueing a future turn. -- Running Tab queues the draft as the next user turn and keeps the queue editable and reorderable. -- Running queue items drain FIFO only after the active turn can safely accept another user turn. -- Steer does not answer tool permission or question prompts; those prompts keep their existing - interaction UI. -- A stream interrupted by steer should not leave a pending assistant message stuck forever. -- Existing queue edit, move, delete, resume behavior remains available for explicit queue items. - -## Non-Goals - -- Add Claude Code `/btw` side questions in this increment. -- Change ACP provider session modes or provider process management. -- Build provider-native mid-token input injection. Providers that cannot accept that use an - interrupt-and-continue flow. - -## Compatibility - -Stored pending queue rows remain valid. Stored steer rows are still consumable by the runtime so -old sessions do not lose pending instructions, but the new UI no longer makes steer look like an -editable future queue item. diff --git a/docs/features/active-input-routing/tasks.md b/docs/features/active-input-routing/tasks.md deleted file mode 100644 index f473e3f29..000000000 --- a/docs/features/active-input-routing/tasks.md +++ /dev/null @@ -1,10 +0,0 @@ -# Active Input Routing Tasks - -- [x] Add active input routing spec artifacts. -- [x] Add typed `chat.steerActiveTurn` route and client method. -- [x] Add AgentSession/AgentRuntime active steer entrypoint. -- [x] Route Enter and Tab distinctly in ChatPage/ChatInputBox. -- [x] Update toolbar labels and queue/steer i18n. -- [x] Add focused tests. -- [x] Run format, i18n, lint, and relevant tests. -- [x] Review diff before commit. diff --git a/docs/features/agent-db-legacy-import/plan.md b/docs/features/agent-db-legacy-import/plan.md deleted file mode 100644 index 78b9ad031..000000000 --- a/docs/features/agent-db-legacy-import/plan.md +++ /dev/null @@ -1,46 +0,0 @@ -# Agent DB Legacy Import Plan - -## Implementation Steps - -1. Database baseline - - `DatabaseInitializer` 默认路径改为 `agent.db` - - `SQLitePresenter` 仅自动创建新栈表(保留 `acp_sessions`) - - 新增表: - - `deepchat_message_search_results` - - `legacy_import_status` - -2. Searchresult migration - - `agentRuntimePresenter/dispatch.ts` 解析 `application/deepchat-webpage` - - 同时写入: - - assistant `search` block - - `deepchat_message_search_results` - - `agentSessionPresenter.getSearchResults()` 从新表读取 - - 前端引用点改为 `agentSessionPresenter.getSearchResults()` - -3. Legacy import pipeline - - 新增 `LegacyChatImportService` - - after-start hook 非阻塞触发后台导入 - - 从 `chat.db` 读取旧表并映射写入新表 - - 变体策略:assistant 仅保留最后一个 variant - - 导入状态与错误写入 `legacy_import_status` - -4. Retry and observability - - `agentSessionPresenter` 暴露: - - `getLegacyImportStatus()` - - `retryLegacyImport()` - - 失败后允许手动重试,重复导入保持幂等 - -5. Supporting updates - - `SyncPresenter` 与 `DevicePresenter` 路径切到 `agent.db` - - `SyncPresenter` 计数逻辑兼容 `new_sessions` / `conversations` - -## Risks - -1. 旧链路若仍访问 legacy chat 表,会因不建表而失败。 -2. 导入过程中的异常 JSON 需要降级处理并持续导入后续数据。 -3. ACP 旧会话字段可能存在版本差异,需以可选字段方式解析。 - -## Rollout - -1. 先发布后台导入与状态能力,不删旧库文件。 -2. 观察导入状态与错误分布后,再移除剩余 legacy 调用点。 diff --git a/docs/features/agent-db-legacy-import/spec.md b/docs/features/agent-db-legacy-import/spec.md deleted file mode 100644 index 7c75a6f15..000000000 --- a/docs/features/agent-db-legacy-import/spec.md +++ /dev/null @@ -1,32 +0,0 @@ -# Agent DB Legacy Import Spec - -## Goal - -将主数据库从 `chat.db` 切换到 `agent.db`,并在不删除旧库数据的前提下,把历史数据异步导入新表结构,且支持失败后手动重试。 - -## Scope - -1. 主库文件切换为 `app_db/agent.db`。 -2. 启动后后台任务读取旧库 `app_db/chat.db` 并导入: - - `conversations` -> `new_sessions` / `deepchat_sessions` - - `messages` -> `deepchat_messages`(变体仅保留最后一个) - - `message_attachments(search_result/search_results)` -> `deepchat_message_search_results` -3. `searchresult` 读写统一走新结构: - - 新链路写入:tool 结果中的 `application/deepchat-webpage` - - 新链路读取:`agentSessionPresenter.getSearchResults()` -4. 导入状态持久化与重试: - - 新表 `legacy_import_status` - - IPC:`getLegacyImportStatus` / `retryLegacyImport` -5. 不删除旧库文件,仅在导入任务中按需只读打开并在完成后关闭连接。 - -## Non-Goals - -1. 不实现旧 chat 表结构的兼容兜底建表。 -2. 不在本阶段删除所有 legacy 组件,仅注释/迁移关键调用点。 -3. 不处理跨版本全量回填的 UI 引导。 - -## Constraints - -1. 旧库读取需使用 `SELECT *`,避免字段漂移导致导入失败。 -2. 导入任务必须非阻塞启动流程。 -3. 数据导入需要幂等,可重复执行。 diff --git a/docs/features/agent-db-legacy-import/tasks.md b/docs/features/agent-db-legacy-import/tasks.md deleted file mode 100644 index e906057cf..000000000 --- a/docs/features/agent-db-legacy-import/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Agent DB Legacy Import Spec Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/agent-input-advanced-config/plan.md b/docs/features/agent-input-advanced-config/plan.md deleted file mode 100644 index be421ee45..000000000 --- a/docs/features/agent-input-advanced-config/plan.md +++ /dev/null @@ -1,63 +0,0 @@ -# Agent 输入区高级配置回归实施计划 - -## 1. 类型与接口 - -1. 新增 `SessionGenerationSettings`(共享类型)。 -2. `CreateSessionInput` 增加 `generationSettings?: Partial`。 -3. `IAgentSessionPresenter` 增加: - - `getSessionGenerationSettings(sessionId)` - - `updateSessionGenerationSettings(sessionId, settings)` -4. `IAgentImplementation` 增加可选: - - `getGenerationSettings?` - - `updateGenerationSettings?` - -## 2. 主进程(newAgent + deepchat) - -1. `agentSessionPresenter.createSession` 透传 `generationSettings` 到 `agent.initSession`。 -2. `agentSessionPresenter` 新增 generation settings 读写代理,保持 permission 相关接口不变。 -3. `agentRuntimePresenter`: - - `initSession` 构造并持久化会话配置(模型默认 + 默认 system prompt + 覆盖值)。 - - `processMessage` / `resumeAssistantMessage` 读取会话配置构建上下文。 - - `runStreamForMessage` 使用会话 `temperature/maxTokens`,并将 - `contextLength/thinkingBudget/reasoningEffort/verbosity` 合并到 `modelConfig`。 - - 新增 `getGenerationSettings` / `updateGenerationSettings`。 - - `updateGenerationSettings` 做 sanitize/clamp 并同步内存与 DB。 - -## 3. 持久化与迁移 - -1. `deepchat_sessions` 迁移版本升级至 `12`。 -2. 新增 generation settings 列,旧行允许 `NULL`。 -3. `DeepChatSessionStore` 新增 generation settings 的 get/update 封装。 - -## 4. 渲染层 - -1. `draftStore` 新增 generation settings 草稿字段与 `toGenerationSettings`。 -2. `NewThreadPage` 创建会话时附带 `generationSettings`。 -3. `ChatStatusBar`: - - 权限左侧新增高级配置按钮。 - - overlay modal 覆盖输入框上方(非全屏遮罩)。 - - modal 字段:system prompt 下拉 + 温度 + 上下文长度 + 最大输出 + 按能力显示 thinking budget / verbosity。 - - Effort 外置,按能力显隐,走会话/草稿双路径写回。 - - 保存策略 300ms 防抖。 - -## 5. i18n - -1. 新增 `chat.advancedSettings.*`。 -2. system prompt 的 `Empty` 复用 `promptSetting.emptySystemPromptOption`。 - -## 6. 测试与验证 - -1. 更新 main 单测:deepchat/newAgent generation settings 透传、读写、sanitize。 -2. 更新 renderer 单测:NewThreadPage 携带 draft generation settings。 -3. 执行: - - `pnpm run format` - - `pnpm run lint` - - 相关测试集 - -## 7. 风险与回退 - -1. 风险:旧会话 `NULL` 字段导致运行时配置不完整。 - - 方案:运行时统一 fallback。 -2. 风险:前端多入口写回导致状态竞争。 - - 方案:会话/草稿分路径 + 防抖聚合写回。 -3. 回退:仅需隐藏新入口并停用 generation settings 写回,数据库新增列可兼容保留。 diff --git a/docs/features/agent-input-advanced-config/spec.md b/docs/features/agent-input-advanced-config/spec.md deleted file mode 100644 index 0da6d6587..000000000 --- a/docs/features/agent-input-advanced-config/spec.md +++ /dev/null @@ -1,80 +0,0 @@ -# Agent 输入区高级配置回归(默认 Agent 模式) - -## 背景 - -`newAgent` 链路上线后,默认 Agent 输入区丢失了旧输入框里的高级模型配置能力(system prompt、temperature、contextLength、maxTokens 等),导致用户无法在默认 Agent 体验中按会话精细控制生成参数。 - -## 目标 - -1. 在默认 Agent 模式恢复高级配置能力,并保持新 UI 风格。 -2. 高级配置入口放在权限按钮左侧,以 overlay modal 覆盖输入框上方展示。 -3. 参数按会话级持久化,切会话/重启可恢复。 -4. `Effort` 保持外置快捷入口,不进入 modal。 - -## 已确认决策 - -1. 仅默认 Agent(DeepChat)展示该能力,ACP 模式不展示。 -2. `Effort` 外置并按模型能力显隐。 -3. 保存策略固定为实时防抖(300ms),无“应用/取消”。 -4. system prompt 来源复用 settings 的系统提示词数据源(含 `Empty`)。 -5. 历史会话若存在非预设 system prompt,UI 显示只读临时项 `Current custom`。 - -## 范围 - -1. 共享类型新增会话生成配置模型,并接入 newAgent IPC。 -2. deepchat 会话表新增生成配置字段与迁移。 -3. deepchat 运行时统一按会话配置构造上下文和模型参数。 -4. 渲染层补齐草稿态与活动会话态双路径写回。 -5. 状态栏新增高级配置入口与 overlay modal。 -6. i18n 增补 `zh-CN`、`en-US`。 - -## 非目标 - -1. 不在 modal 中放 `Effort`。 -2. 不改动当前“活动会话切模型”语义。 -3. 不改 ACP 现有权限与输入交互。 - -## 数据与持久化 - -`deepchat_sessions` 新增列: - -1. `system_prompt` -2. `temperature` -3. `context_length` -4. `max_tokens` -5. `thinking_budget` -6. `reasoning_effort` -7. `verbosity` - -迁移策略: - -1. 旧行允许 `NULL`。 -2. 运行时读取时对 `NULL` 回落为“当前模型默认值 + 默认 system prompt”。 -3. 不阻塞现有会话读取。 - -## 验收标准 - -1. NewThread 修改高级配置后,首条消息立即按该配置执行。 -2. Chat 页面中途修改高级配置,仅影响后续消息。 -3. 切会话后配置恢复;重启后仍可恢复。 -4. settings 调整系统提示词预设后,modal 下拉可读取最新列表。 -5. ACP 模式下不展示高级配置入口,权限行为不回归。 - -## 测试矩阵 - -主进程: - -1. deepchat_sessions 迁移后字段可读写。 -2. initSession 持久化会话生成配置。 -3. process/resume/runStream 使用会话配置。 -4. updateGenerationSettings 的 sanitize/clamp 正确。 -5. newAgent createSession 透传 generationSettings。 -6. newAgent get/updateSessionGenerationSettings 代理与异常正确。 - -渲染层: - -1. NewThread 创建会话携带 draft generationSettings。 -2. 默认 Agent 显示高级配置入口,ACP 不显示。 -3. Effort 仅在支持时显示且选项按模型能力变化。 -4. modal 仅提供 system prompt 下拉(无文本输入)。 -5. 连续修改参数时按 300ms 防抖写回。 diff --git a/docs/features/agent-input-advanced-config/tasks.md b/docs/features/agent-input-advanced-config/tasks.md deleted file mode 100644 index b4234e46f..000000000 --- a/docs/features/agent-input-advanced-config/tasks.md +++ /dev/null @@ -1,62 +0,0 @@ -# Agent 输入区高级配置回归任务清单 - -## T0 规格文档 - -- [x] 新建 `spec.md` -- [x] 新建 `plan.md` -- [x] 新建 `tasks.md` - -## T1 共享类型与接口 - -- [x] 新增 `SessionGenerationSettings` -- [x] `CreateSessionInput` 增加 `generationSettings` -- [x] `IAgentSessionPresenter` 增加 generation settings 读写接口 -- [x] `IAgentImplementation` 增加可选 generation settings 读写接口 - -## T2 持久化层 - -- [x] `deepchat_sessions` 增加 migration v12 与新字段 -- [x] `DeepChatSessionStore` 增加 generation settings get/update 封装 -- [x] 兼容旧行 `NULL` 数据 - -## T3 DeepChat 运行时 - -- [x] initSession 写入会话生成配置 -- [x] process/resume 使用会话配置构建上下文 -- [x] runStream 使用会话 temperature/maxTokens 与扩展 modelConfig -- [x] 实现 `getGenerationSettings/updateGenerationSettings` -- [x] update 统一 sanitize/clamp 并持久化 - -## T4 NewAgent 透传与代理 - -- [x] createSession 透传 `generationSettings` -- [x] 新增 get/updateSessionGenerationSettings 代理 -- [x] session 不存在时抛出一致错误 - -## T5 渲染层状态与 UI - -- [x] draftStore 新增 generation settings 草稿字段 -- [x] NewThreadPage 创建会话携带 generation settings -- [x] ChatStatusBar 新增高级配置入口(权限左侧) -- [x] overlay modal 实现(覆盖输入框上方) -- [x] modal 字段按能力显隐 -- [x] Effort 外置并按能力显隐 -- [x] 300ms 防抖写回会话/草稿 - -## T6 i18n - -- [x] `zh-CN/chat.json` 新增 `advancedSettings` 文案 -- [x] `en-US/chat.json` 新增 `advancedSettings` 文案 - -## T7 测试 - -- [x] 更新 `agentRuntimePresenter.test.ts` -- [x] 更新 `agentSessionPresenter.test.ts` -- [x] 更新 `NewThreadPage.test.ts` -- [x] 更新 `agentSessionPresenter/integration.test.ts` 兼容新参数 - -## T8 质量门禁 - -- [x] `pnpm run format` -- [x] `pnpm run lint` -- [x] 运行相关测试并记录结果 diff --git a/docs/features/agent-progress-todo/plan.md b/docs/features/agent-progress-todo/plan.md deleted file mode 100644 index ce7470244..000000000 --- a/docs/features/agent-progress-todo/plan.md +++ /dev/null @@ -1,175 +0,0 @@ -# Agent Progress Todo 实施计划 - -## 当前基线 - -- Agent 工具定义由 `AgentToolManager.getAllToolDefinitions()` 汇总,`ToolPresenter` 根据 `disabledAgentTools` 过滤内置工具。 -- 高级工具面板 `McpIndicator.vue` 已按 `tool.server.name` 对内置工具分组,YoBrowser 通过 `yobrowser` 分区显示和开关。 -- DeepChat agent 执行链路为 `agentRuntimePresenter -> processStream -> dispatch.executeTools -> ToolPresenter.callTool -> AgentToolManager.callTool`。 -- Renderer 实时更新走 `chat.stream.updated` typed event,payload 是完整 assistant blocks snapshot。 -- 当前 shared `@shared/chat` 和 renderer display type 已支持 `plan` block,但 `@shared/types/agent-interface.d.ts` 与 `AssistantMessageBlockSchema` 还未完整纳入 `plan`。 -- ACP 已能把外部 `plan` notification 映射为 `plan` block,但 `MessageBlockPlan.vue` 目前只显示摘要和进度条,不显示完整 checklist。 - -## 架构决策 - -1. `update_plan` 是 DeepChat built-in agent tool,不是 MCP server。 -2. 新工具归入现有 Core 工具分区: - - `server.name = 'agent-core'` - - `function.name = 'update_plan'` - - 这样可直接复用 `disabledAgentTools` 和高级工具面板的单工具开关,不额外增加单工具分区。 -3. `AgentToolManager` 持有 session-scoped `PlanState`,按 `conversationId` 维护 `current/revision/updatedAt`。 -4. 工具调用成功后通过 `AgentToolProgressUpdate` 扩展出 `kind: 'agent_plan'` 把 snapshot 交给 dispatch。 -5. `dispatch.ts` 负责发布 `chat.plan.updated` typed event,不把 DeepChat `update_plan` snapshot 插入当前 assistant message。 -6. `update_plan` tool call block 标记为 internal progress tool,renderer 默认隐藏该 pill,避免重复展示“update_plan 调用完成”。 -7. `chat.plan.updated` event 和 renderer plan store 是 DeepChat todo 的实时来源。若后续需要持久化,应设计独立 progress 存储,不复用 assistant message blocks。 -8. MVP 使用可收起浮层,不把 Progress 放入 Workspace 内容区,不增加 sidepanel 顶层 tab。 - -## 数据与类型 - -新增或扩展: - -- `src/shared/types/agent-plan.ts` - - `AgentPlanStepStatus` - - `AgentPlanItem` - - `UpdatePlanArgs` - - `AgentPlanSnapshot` - - `AgentPlanState` -- `src/shared/types/agent-interface.d.ts` - - `AssistantBlockType` 增加 `plan` - - `AssistantMessageExtra` 增加 plan 相关字段类型 -- `src/shared/contracts/common.ts` - - `AssistantMessageBlockSchema.type` 增加 `plan` - - `extra` schema 保持 json record -- `src/shared/contracts/events/chat.events.ts` - - 新增 `chatPlanUpdatedEvent` -- `src/shared/contracts/events.ts` - - 导出并登记新事件 -- `src/shared/types/presenters/tool.presenter.d.ts` - - `AgentToolProgressUpdate` 增加 `agent_plan` variant - -Plan snapshot: - -```ts -interface AgentPlanSnapshot { - sessionId: string - toolCallId?: string - explanation?: string - plan: AgentPlanItem[] - revision: number - updatedAt: string -} -``` - -## Main Process Flow - -```text -Model emits update_plan tool call - -> accumulator adds tool_call block - -> dispatch.executeTools runs ToolPresenter.callTool - -> AgentToolManager validates args and updates PlanState - -> AgentToolManager emits AgentToolProgressUpdate(kind: agent_plan) - -> dispatch marks the update_plan tool call internal and publishes chat.plan.updated - -> renderer receives chat.stream.updated snapshot and chat.plan.updated live event - -> model receives tiny success result -``` - -Implementation pieces: - -- Add `agentPlanTool.ts` under `src/main/presenter/toolPresenter/agentTools/`. -- Add zod schema with `.strict()` objects and max length 12. -- Register tool definition from `AgentToolManager.getAllToolDefinitions()` only in `chatMode === 'agent'`. -- Route `toolName === UPDATE_PLAN_TOOL_NAME` in `AgentToolManager.callTool()`. -- Add helper in `dispatch.ts`: - - `markInternalPlanToolCall(blocks, toolCallId)` - - `publishPlanUpdated(snapshot, messageId)` -- Keep tool output small and context-friendly. The tool response to the model should be `{}` or `Plan updated`, not the full plan. - -## Renderer Flow - -- `MessageBlockPlan.vue` - - Replace summary-only rendering with full checklist. - - Support existing ACP `plan_entries` with `{ content, status }` and new DeepChat `{ step, status }`. - - Keep progress count in the header. - - Render empty state when `plan_entries` is empty. - - This component is compatibility UI for ACP/history; DeepChat `update_plan` does not create these blocks. -- New `AgentProgressFloat.vue` - - Input: latest active plan snapshot for current session. - - Collapsed by default. Collapsed state stored in sidepanel/session UI store or local `useStorage` keyed by session id. - - Expands and collapses with a short height/opacity transition. - - Shows only when current active session has a non-empty active plan during generation or a latest settled plan from current turn. -- Chat page integration: - - Subscribe through a small renderer client method for `chat.plan.updated`. - - Maintain `latestPlanBySession` in a small Pinia/composable store. - - Clear active floating plan when a new user turn starts and no plan has arrived yet. -- `MessageItemAssistant.vue` - - Skip rendering internal `update_plan` tool_call blocks by checking `block.extra.internalTool === true` and `block.tool_call.name === 'update_plan'`. - -## Tool Toggle UI - -- `McpIndicator.vue` - - Keep `update_plan` under existing `agent-core` grouping. - - Do not add a separate Progress group or group label. -- Existing `disabledAgentTools` storage works without a schema migration because it stores tool names. -- New sessions inherit agent default `disabledAgentTools`; built-in DeepChat default remains enabled. - -## Prompting - -Update `ToolPresenter.buildToolSystemPrompt()`: - -- Add `buildProgressPrompt(toolNames)`. -- Include rules only when `toolNames.has('update_plan')`. -- Keep this separate from formal planning responses and from question tool rules. - -## Compatibility - -- Existing ACP plan blocks should continue rendering because `MessageBlockPlan` will normalize both `{ content }` and `{ step }`. -- Existing assistant messages with summary-only `plan_entries` still render. -- DeepChat `update_plan` no longer creates new assistant `plan` blocks; this intentionally keeps the message list free of process-state todo items. -- Sessions with `disabledAgentTools` do not need migration. If a user had disabled all tools manually, `update_plan` starts enabled unless agent config later explicitly disables it. -- If `update_plan` is disabled during an active generation, the current request's tool list is not retroactively mutated; the change applies to subsequent tool refreshes, matching existing tool toggle behavior. - -## Test Strategy - -Main tests: - -- `AgentPlanTool` validation rejects unknown status, empty step, extra fields, multiple `in_progress`, and more than 12 steps. -- Valid payload increments revision and normalizes trimmed steps. -- Empty plan clears current snapshot and emits a snapshot with `plan: []`. -- `AgentToolManager` lists `update_plan` in `agent-core` for DeepChat agent mode and omits it when disabled through `ToolPresenter`. -- `ToolPresenter.buildToolSystemPrompt()` includes progress rules only when enabled. -- `dispatch.executeTools` handles `agent_plan` progress update by publishing event and not inserting any `plan` block. - -Renderer tests: - -- `MessageBlockPlan` renders completed / in_progress / pending entries with accessible status text. -- Long step text wraps without changing icon alignment. -- ACP-style `{ content }` plan entries still render. -- Internal `update_plan` tool_call block is hidden. -- `McpIndicator` shows `update_plan` inside Agent Core and toggles it through `disabledAgentTools`. -- Floating panel renders collapsed by default, animates expand/collapse, and ignores stale lower revision updates. - -Validation commands after implementation: - -```bash -pnpm run format -pnpm run i18n -pnpm run lint -pnpm run typecheck -pnpm test -- test/main/presenter/toolPresenter test/main/presenter/agentRuntimePresenter test/renderer/components/message test/renderer/components/McpIndicator.test.ts -``` - -## Risks - -- Tool-call UI noise: hidden internal tool call must be scoped only to `update_plan`, not all agent-core tools. -- Message pollution: DeepChat `update_plan` must not append process-state todo items to assistant message blocks. -- Type drift: there are multiple assistant block type definitions; all active shared/renderer schemas must include `plan`. -- Event ordering: floating panel should compare `revision` and ignore stale updates for the same session. -- Overuse by model: system prompt must explicitly skip simple one-shot tasks. - -## Rollout - -1. Implement tool and validation behind normal tool availability. -2. Add event/block support and renderer checklist. -3. Add floating panel. -4. Add tool toggle group and prompt rules. -5. Run tests and validation commands. -6. If floating panel feels intrusive in QA, keep it collapsed by default and retain `MessageBlockPlan` only as ACP/history compatibility UI. diff --git a/docs/features/agent-progress-todo/spec.md b/docs/features/agent-progress-todo/spec.md deleted file mode 100644 index c21b22855..000000000 --- a/docs/features/agent-progress-todo/spec.md +++ /dev/null @@ -1,194 +0,0 @@ -# Agent Progress Todo 规格 - -## 背景 - -DeepChat agent 已经具备文件、命令、YoBrowser、skills、subagent、提问与权限交互等能力,但缺少一个轻量的“执行进度 / todo”工具。用户在多步骤任务中只能从文本和 tool call pill 推断当前状态,无法稳定看到“已完成、正在做、待处理”。 - -用户提供了 agent-progress-todo 参考文档;核心模型参考 Codex 的 `update_plan`:一次工具调用提交完整 checklist snapshot,runtime 替换当前计划并发出稳定事件,UI 渲染 progress checklist。 - -## 目标 - -- DeepChat agent 可调用 `update_plan` 更新当前任务进度。 -- 用户能在生成过程中看到当前 plan 的 completed / in_progress / pending 状态。 -- `update_plan` 作为内置 agent 核心工具出现,可在 Core 分组中按工具单独启用或停用。 -- checklist 是 agent turn 的过程态,不插入 assistant message 列表;实时展示由独立 plan event/store 驱动。 -- 生成中提供一个可收起的实时 Progress 浮层,避免用户滚动离开最新消息后失去进度可见性。 - -## 用户故事 - -### US-1:多步骤任务可见进度 - -作为用户,当我让 DeepChat agent 执行跨文件或跨阶段任务时,我希望看到已完成、正在处理、待处理的 checklist,而不是只能读 tool call 和中间文本。 - -### US-2:可控的核心工具 - -作为用户,我希望 Progress/Todo 保持在核心工具区域里,不额外增加单工具分区;如果我不希望 agent 展示计划,可以在 Core 分组中停用 `update_plan`。 - -### US-3:消息列表保持干净 - -作为用户,我希望 todo/progress 不被写入聊天消息列表,避免中间状态污染最终回答。即使后续需要持久化,也应进入独立的 progress 存储,而不是 assistant message blocks。 - -### US-4:实时但不打扰 - -作为用户,我希望执行中有一个可收起的 Progress 浮层显示最新进度;如果我不需要看,可以折叠,不影响输入区和消息阅读。 - -## MVP 范围 - -- 新增 DeepChat 内置 agent 工具 `update_plan`。 -- 工具 schema 与参考文档一致:`explanation?: string`,`plan: { step: string; status: "pending" | "in_progress" | "completed" }[]`。 -- 工具校验: - - `plan` 必须为数组,允许空数组用于清空 checklist。 - - `step` trim 后必须为非空字符串。 - - `status` 只允许 `pending | in_progress | completed`。 - - 同一 snapshot 最多一个 `in_progress`。 - - 拒绝额外字段。 - - MVP 最多 12 个 step,超出返回模型可读错误。 -- Runtime 维护 session-scoped latest plan state:`current`、`revision`、`updatedAt`。 -- DeepChat typed event 使用 `chat.plan.updated` 承载参考文档中的 `plan.update` 语义。 -- DeepChat `update_plan` 不新增 assistant `plan` block,也不在消息列表中渲染 todo。 -- 生成中浮层从最新 plan snapshot 渲染,默认收起,支持带动画的展开 / 折叠。 -- 高级工具面板在 `agent-core` 分区显示 `update_plan`,支持按单个工具停用。 -- Agent system tooling prompt 注入使用规则,避免简单任务滥用 checklist。 -- 覆盖 validation、handler、dispatch/event、UI 渲染、工具分区开关测试。 - -## UX 决策 - -MVP 不重做 Workspace 信息架构,也不把 Progress 作为 Workspace 主内容区的一部分。原因: - -- 当前右侧 sidepanel 已经有 `workspace` 与 `browser` 两个顶层 tab,Workspace 内部又包含 Files、Git、Artifacts。直接塞入实时任务状态会让 Workspace 承担过多职责。 -- Progress 是 agent turn 的执行状态,不是 workspace 文件资产。它应优先跟随 chat generation,而不是跟随文件预览。 -- 用户截图更接近一个轻量 progress panel;可收起浮层能满足实时可见性,同时保持消息列表不被过程态 todo 污染。 - -MVP UI 形态: - -- Message list:DeepChat `update_plan` 不插入 `plan` block;`update_plan` 自身 tool call pill 标记为 internal,默认不渲染。 -- Floating panel:仅当前 session 有 active plan 时显示;desktop 固定在输入区上方右侧,mobile 使用输入区上方全宽紧凑条;默认收起,可动画展开 / 折叠,折叠状态按 session 保存。 -- Tool toggle:在 Advanced Settings -> Built-in Tools 的 Core 分组中显示 `update_plan`。关闭该工具后,本轮之后的工具列表不再暴露该工具。 - -## Tool Contract - -工具名:`update_plan` - -```ts -type AgentPlanStepStatus = 'pending' | 'in_progress' | 'completed' - -interface AgentPlanItem { - step: string - status: AgentPlanStepStatus -} - -interface UpdatePlanArgs { - explanation?: string - plan: AgentPlanItem[] -} -``` - -成功结果应保持极简,推荐: - -```json -{} -``` - -错误结果必须可被模型自修复,例如: - -```text -invalid update_plan arguments: at most one step can be in_progress -``` - -## Event Contract - -Codex 参考文档里的 `plan.update` 在 DeepChat 中落为 typed event: - -```ts -interface ChatPlanUpdatedEvent { - sessionId: string - messageId: string - toolCallId?: string - plan: AgentPlanItem[] - explanation?: string - revision: number - updatedAt: string -} -``` - -语义: - -- 每个事件代表一次完整 snapshot 替换。 -- `revision` 在 session 内单调递增。 -- UI 收到更高 revision 后覆盖当前 checklist。 -- `plan.length === 0` 表示清空 active checklist。 -- 事件用于实时浮层;如后续需要持久化,应写入独立 progress 存储,不复用 assistant message blocks。 - -## Assistant Block Compatibility - -DeepChat `update_plan` 不写入 assistant `plan` block。现有 `plan` block 兼容逻辑仅用于 ACP agent notification 和已有历史消息。 - -兼容的 `plan` block `extra` 存储结构: - -```ts -{ - plan_entries: AgentPlanItem[] - plan_explanation?: string - plan_revision: number - plan_updated_at: string -} -``` - -渲染规则: - -- `explanation` 存在时显示在 checklist 上方一行。 -- `completed` 使用 dimmed style 和 check icon。 -- `in_progress` 使用 active style 和 running indicator。 -- `pending` 使用 normal/muted style 和 hollow circle。 -- 长 step 必须换行,第二行缩进到文本起始位置。 -- screen reader 文本包含本地化后的 status 与 step 文本。 - -## Agent 使用规则 - -注入到工具系统 prompt: - -```text -Use update_plan for non-trivial multi-step tasks. -Skip update_plan for simple one-shot answers or trivial edits. -Keep each plan step short, concrete, and verifiable. -Keep the plan current as work progresses. -At most one step may be in_progress at a time. -When a step completes, update the plan immediately and move the next active step to in_progress in the same call. -Use explanation only when the plan changes materially or when progress would otherwise be unclear. -``` - -## 验收标准 - -- `update_plan` 出现在 DeepChat agent 的内置工具列表中,且 `server.name` 为 `agent-core`。 -- Advanced Settings 的 Built-in Tools 中,`update_plan` 出现在 Core 分组内,可作为单个工具开关。 -- 关闭 `update_plan` 后,新请求工具定义不再包含该工具,系统 prompt 也不再包含 Progress 使用规则。 -- 有效 payload 更新 session plan state,`revision` 递增,`updatedAt` 为 ISO 8601 UTC string。 -- 无效 payload 返回模型可读错误,不更新 state,不发 `chat.plan.updated`。 -- 每次有效调用发出一个 `chat.plan.updated` event,并更新独立 renderer plan store。 -- `update_plan` 不会向当前 assistant message 插入 `plan` block。 -- `update_plan` 自身 tool call pill 不在默认消息视图中制造额外噪声。 -- 浮层默认收起,展开 / 折叠有过渡动画。 -- 浮层能渲染三种状态、长文本换行与空 plan。 -- ACP 或历史 assistant message 的 plan block 仍可正常显示。 -- 测试覆盖 validation、tool handler、dispatch/event、message block、floating panel、tool toggle。 - -## 非目标 - -- 不做 owner、deadline、priority、子任务层级。 -- 不接入外部项目管理系统。 -- 不做长期任务数据库或跨 session 任务看板。 -- 不做自动任务拆解 planner。 -- 不重做 Workspace 顶层布局。 -- 不改变 ACP agent 已有 plan notification 映射,只在必要时复用 UI 组件。 - -## 约束 - -- 遵循 DeepChat 新 renderer-main typed route / typed event 模式,不新增 legacy IPC。 -- 用户可见文案必须走 `src/renderer/src/i18n`。 -- DeepChat agent 新能力优先放在 `src/main/presenter/toolPresenter/agentTools` 与 `agentRuntimePresenter` 现有链路中。 -- 保持实现轻量,避免引入状态管理系统级复杂度。 -- 不破坏现有 ACP `plan` block 兼容展示。 - -## 开放问题 - -无。 diff --git a/docs/features/agent-progress-todo/tasks.md b/docs/features/agent-progress-todo/tasks.md deleted file mode 100644 index 3a2fb3e0a..000000000 --- a/docs/features/agent-progress-todo/tasks.md +++ /dev/null @@ -1,79 +0,0 @@ -# Agent Progress Todo 任务清单 - -## T0 规格冻结 - -- [x] 阅读 SDD 规范与用户提供的 Codex progress 设计文档。 -- [x] 梳理 DeepChat 当前 agent tool、dispatch、message block、tool toggle、sidepanel/workspace 基线。 -- [x] 明确 MVP UI 决策:聊天内 checklist + 可收起浮层,不重做 Workspace。 -- [x] 移除开放澄清项。 - -## T1 Shared Types 与 Event Contract - -- [x] 新增 `src/shared/types/agent-plan.ts`。 -- [x] 更新 `src/shared/types/agent-interface.d.ts` 的 assistant block 类型与 extra 字段。 -- [x] 更新 `src/shared/contracts/common.ts` 允许 `plan` block。 -- [x] 新增并登记 `chat.plan.updated` typed event。 -- [x] 更新 renderer display type 中 plan extra 的更具体类型。 - -## T2 update_plan 工具与校验 - -- [x] 新增 `src/main/presenter/toolPresenter/agentTools/agentPlanTool.ts`。 -- [x] 定义 `UPDATE_PLAN_TOOL_NAME` 与 zod schema。 -- [x] 实现 strict validation、最多 12 steps、最多一个 `in_progress`。 -- [x] 实现 session-scoped `PlanState` 和 revision 递增。 -- [x] 成功时返回极简 tool result,失败时返回模型可读错误。 -- [x] 添加 main 单测覆盖 validation 和 state 更新。 - -## T3 ToolPresenter 集成与提示词 - -- [x] 在 `AgentToolManager.getAllToolDefinitions()` 注册 `agent-core/update_plan`。 -- [x] 在 `AgentToolManager.callTool()` 路由 `update_plan`。 -- [x] 扩展 `AgentToolProgressUpdate` 支持 `agent_plan`。 -- [x] 在 `ToolPresenter.buildToolSystemPrompt()` 注入 Progress 使用规则。 -- [x] 确认 `disabledAgentTools` 可过滤 `update_plan`。 -- [x] 添加 ToolPresenter/AgentToolManager 单测。 - -## T4 Dispatch 与 Message Block 更新 - -- [x] 在 `dispatch.ts` 处理 `agent_plan` progress update。 -- [x] 不向 current assistant message 插入 `plan` block。 -- [x] 标记 `update_plan` tool_call block 为 internal。 -- [x] 发布 `chat.plan.updated` event。 -- [x] 确保 empty plan 清空 active checklist。 -- [x] 添加 dispatch 单测覆盖 update 只发 event 且不插入 plan block。 - -## T5 Renderer Checklist - -- [x] 重写 `MessageBlockPlan.vue` 为完整 checklist。 -- [x] 兼容 ACP `{ content, status }` 与 DeepChat `{ step, status }` 两种 entry。 -- [x] 增加 completed / in_progress / pending 三态样式。 -- [x] 增加 empty state 与 screen reader 文本。 -- [x] 在 `MessageItemAssistant.vue` 隐藏 internal `update_plan` tool call。 -- [x] 添加 renderer 组件测试。 - -## T6 Floating Progress Panel - -- [x] 新增 `AgentProgressFloat.vue`。 -- [x] 新增或扩展 renderer store/composable 维护 latest plan snapshot。 -- [x] 订阅 `chat.plan.updated`,按 session 和 revision 去重。 -- [x] 在 `ChatPage.vue` 输入区上方挂载浮层。 -- [x] 支持 per-session collapsed state,默认收起。 -- [x] 增加展开 / 收起动画。 -- [x] 添加浮层渲染与折叠测试。 - -## T7 工具分区开关与 i18n - -- [x] 确认 `update_plan` 出现在现有 Agent Core 分组中。 -- [x] 不新增单独 Progress 分组或分组 i18n 文案。 -- [x] 确认草稿会话和已有 session 都能开关 `update_plan`。 -- [x] 更新 DeepChat agent settings 工具列表展示需要的分组标签。 -- [x] 添加 `McpIndicator` 测试覆盖 Core 内单工具 toggle。 - -## T8 验证 - -- [x] 运行 `pnpm run format`。 -- [x] 运行 `pnpm run i18n`。 -- [x] 运行 `pnpm run lint`。 -- [x] 运行 `pnpm run typecheck`。 -- [x] 运行相关 main/renderer 测试。 -- [ ] 手动验证一个多步骤 agent 任务能显示、更新、完成和隐藏 progress。 diff --git a/docs/features/app-spotlight-search/plan.md b/docs/features/app-spotlight-search/plan.md deleted file mode 100644 index 1affdf968..000000000 --- a/docs/features/app-spotlight-search/plan.md +++ /dev/null @@ -1,300 +0,0 @@ -# App Spotlight Search 实施计划 - -## 1. 当前实现基线 - -### 1.1 已有搜索能力 - -1. 当前 PR 已在 `ChatPage.vue` 中实现会话内 inline 搜索,支持: - - `Cmd/Ctrl+F` - - 当前消息正文高亮 - - Enter / Shift+Enter 导航 - - Esc 关闭 -2. 消息正文 DOM 已统一暴露 `data-message-content="true"`,可作为会话内搜索与消息跳转高亮的稳定边界。 -3. 左侧 `WindowSideBar.vue` 已支持会话标题过滤与局部高亮,但范围仅限当前会话列表。 - -### 1.2 快捷键现状 - -1. `src/main/presenter/configPresenter/shortcutKeySettings.ts` 已维护 renderer/system 级默认快捷键。 -2. `ShortcutPresenter` 负责主进程注册与重注册快捷键。 -3. `SHORTCUT_EVENTS` 已经承载 main -> renderer 的快捷键分发。 - -### 1.3 设置导航现状 - -1. 设置窗口独立于主聊天窗口。 -2. `src/renderer/settings/main.ts` 已维护设置页路由,并在 `meta` 中提供 `titleKey`、`icon`、`position`。 -3. `SETTINGS_EVENTS.NAVIGATE` 已支持主进程打开/聚焦设置窗口后导航到某个设置页。 - -### 1.4 历史数据现状 - -1. 会话和消息已经持久化在 SQLite 相关链路中。 -2. 当前没有统一给 UI 使用的“历史全文搜索”主进程服务。 -3. 当前 PR 的会话内搜索主要基于 renderer DOM,而非历史索引。 - -## 2. 设计决策 - -### 2.1 功能边界 - -Spotlight 作为 follow-up feature 分两层: - -1. **主进程历史搜索** - - 只负责会话 / 历史消息检索 - - 输出结构化命中结果 -2. **渲染层统一混排** - - 将历史搜索结果与 `agent / setting / action` 本地条目合并 - - 统一排序、裁剪、active item 管理与执行行为 - -这样可以避免把 Agent / Setting / Action 这些天然本地导航项也塞进数据库索引。 - -### 2.2 默认快捷键 - -最终采用: - -- `QuickSearch = CommandOrControl+P` - -原因: - -1. 当前会话内搜索已经使用 `Cmd/Ctrl+F` -2. Spotlight 是“全局跳转器”,语义更接近 `Cmd/Ctrl+P` -3. 避免同一个快捷键在 inline search 与 Spotlight 之间抢焦点 - -### 2.3 搜索索引表 - -新增两张表: - -1. `deepchat_search_documents` - - `entity_kind` - - `entity_id` - - `session_id` - - `title` - - `body` - - `role` - - `updated_at` -2. `deepchat_search_documents_fts` - - 对 `title/body` 做 FTS5 全文索引 - -用途分离: - -1. 普通表作为真实数据表与降级查询源 -2. FTS 表只负责全文索引与匹配性能 - -### 2.4 索引数据来源 - -#### 会话文档 - -- `entity_kind = 'session'` -- `entity_id = session.id` -- `session_id = session.id` -- `title = session.title` -- `body = ''` - -#### 消息文档 - -- `entity_kind = 'message'` -- `entity_id = message.id` -- `session_id = message.sessionId` -- `title = session.title`(冗余存储,便于结果展示和排序) -- `body = 可见文本` -- `role = user | assistant` - -### 2.5 可见文本抽取规则 - -需要抽离一个可复用的“消息可见文本抽取”模块,供: - -1. Spotlight 历史搜索 -2. 未来 MCP history search -3. 可能的导出 / snippet 生成复用 - -抽取规则: - -1. 用户消息: - - 优先取结构化 `content[].type === 'text'` - - 回退到 `text` -2. 助手消息: - - 只收集 `content` 类型可见文本 - - 收集 `error` block 文本 - - 可选收集 `plan` 可见文本 -3. 忽略: - - tool call 原始 JSON - - image/audio 元数据 - - search result raw payload - - streaming 中但未落库内容 - -### 2.6 查询与降级 - -查询顺序: - -1. 优先 FTS5 -2. 若 FTS 执行失败,或 query 被 tokenizer 拒绝,则回退到 `LIKE` - -降级目标: - -1. 任何输入都尽量返回可用结果 -2. 避免特殊字符 query 直接“零结果 + 无解释” - -### 2.7 结果类型与共享接口 - -新增共享类型: - -1. `HistorySearchOptions` -2. `HistorySearchSessionHit` -3. `HistorySearchMessageHit` -4. `HistorySearchHit` - -新增 Presenter 接口: - -- `IAgentSessionPresenter.searchHistory(query, options?)` - -说明: - -1. Presenter 只返回 `session | message` 命中 -2. `agent | setting | action` 在 renderer 本地组装为 `SpotlightItem` - -### 2.8 设置导航 registry - -为避免把设置搜索逻辑散落在 Spotlight 组件中,新增一个共享 registry: - -- `routeName` -- `titleKey` -- `icon` -- `keywords[]` - -数据来源优先复用 `src/renderer/settings/main.ts` 路由元信息,必要时抽成共享常量,让: - -1. 设置窗口侧栏 -2. Spotlight setting items - -消费同一份元数据,避免标题 / icon 漂移。 - -### 2.9 Renderer 状态与执行流 - -新增 `spotlight store`,最少维护: - -1. `open` -2. `query` -3. `results` -4. `activeIndex` -5. `loading` -6. `requestSeq` -7. `pendingMessageJump` - -执行流程: - -1. 打开面板 -> 聚焦输入框 -2. 输入 80ms debounce -3. 发起历史搜索请求 -4. 本地合并 `agent / setting / action` -5. renderer 做统一排序和截断 -6. Enter / click 执行目标 - -### 2.10 消息跳转串联 - -`message` 结果执行后: - -1. 先写入 `pendingMessageJump = { sessionId, messageId }` -2. 切换会话 -3. Chat 页在消息加载完成后消费 `pendingMessageJump` -4. 找到目标消息 DOM -5. 滚动到目标 -6. 高亮约 2 秒 -7. 清空 `pendingMessageJump` - -这样可以避免在会话尚未激活、消息尚未加载完成时提前滚动失败。 - -## 3. 事件流 - -### 3.1 快捷键打开 - -1. `ShortcutPresenter` 注册 `QuickSearch` -2. 触发时发 `SHORTCUT_EVENTS.TOGGLE_SPOTLIGHT` -3. 主聊天 renderer 的 `App.vue` 或 Spotlight host 监听该事件 -4. 切换 Spotlight open 状态 - -### 3.2 设置命中跳转 - -1. renderer 执行 setting item -2. 调用主进程打开/聚焦设置窗口 -3. 发送 `SETTINGS_EVENTS.NAVIGATE` -4. 设置窗口跳到对应页面 - -### 3.3 Message 命中跳转 - -1. renderer 执行 message item -2. 写入 `pendingMessageJump` -3. 调用现有 session 选择逻辑 -4. `ChatPage` 在消息加载完成后消费跳转 - -## 4. 测试策略 - -### 4.1 Main - -1. session title hit 与 message hit 的排序基础正确 -2. 会话重命名后索引更新 -3. 消息编辑 / 删除后索引更新 -4. legacy import / schema rebuild 后全量回填可用 -5. FTS 查询失败时自动降级到 `LIKE` - -### 4.2 Renderer - -1. `Cmd/Ctrl+P` 打开 / `Esc` 关闭 -2. 输入框自动聚焦 -3. `↑/↓/Home/End/Enter` 全链路 -4. 混排列表稳定,不因 hover 破坏 active item -5. `message` 结果切会话后成功滚动并高亮 -6. `agent / setting / action` 执行正确 -7. 空查询 recent/agents/actions 可见 - -### 4.3 验收场景 - -1. sidebar 收起时 Spotlight 仍可用 -2. 有查询时最多显示 12 条结果 -3. 结果 hover 与 click 行为自然 -4. 设置窗口已有焦点时,从主聊天窗口触发 Spotlight 仍只打开主聊天窗口里的唯一实例 - -## 5. 风险与缓解 - -### 风险 1:FTS 索引同步遗漏 - -如果消息编辑 / 删除 / 导入路径漏掉同步,搜索结果会漂移。 - -缓解: - -1. 把索引同步集中到统一服务 -2. 对 create/update/delete/import 分别补 main tests - -### 风险 2:Spotlight 与 inline chat search 快捷键冲突 - -缓解: - -1. Spotlight 默认改为 `Cmd/Ctrl+P` -2. 保留 inline chat search 的 `Cmd/Ctrl+F` -3. 在快捷键设置页显式展示两者 - -### 风险 3:设置页 metadata 分散 - -缓解: - -1. 抽共享 registry -2. 让设置窗口与 Spotlight 共同消费 - -### 风险 4:消息跳转时序不稳定 - -缓解: - -1. 用 `pendingMessageJump` 记录目标 -2. 只在会话激活 + 消息加载完成后消费 - -### 风险 5:大库搜索性能抖动 - -缓解: - -1. 历史查询只请求 `session | message` -2. renderer 最多渲染 12 条 -3. 使用 debounce + requestSeq 丢弃过期结果 - -## 6. 质量门槛 - -1. `pnpm run format` -2. `pnpm run i18n` -3. `pnpm run lint` -4. `pnpm run typecheck` -5. 相关 main / renderer 测试通过 diff --git a/docs/features/app-spotlight-search/spec.md b/docs/features/app-spotlight-search/spec.md deleted file mode 100644 index 77b95d081..000000000 --- a/docs/features/app-spotlight-search/spec.md +++ /dev/null @@ -1,178 +0,0 @@ -# App Spotlight Search 规格 - -## 概述 - -新增一个单实例、App 级、Raycast 风格的 Spotlight 搜索面板,用于统一搜索: - -1. 会话标题 -2. 历史消息 -3. Agent -4. 设置页面 -5. 少量非破坏性动作 - -该面板作为当前“会话内搜索”的上层能力补充: - -- 当前会话内搜索继续保留轻量 inline 体验 -- Spotlight 负责全局检索与快速跳转 -- 两者不共享 UI,但允许复用消息文本抽取与跳转高亮能力 - -## 背景与动机 - -1. 当前 PR 已经补齐“当前会话内搜索 + 侧边栏标题过滤”,但还缺少“跨历史 / 跨导航项”的统一入口。 -2. 用户希望通过一个全局面板快速跳到会话、历史消息、Agent、设置页,而不是分别在不同区域查找。 -3. DeepChat 现有左侧 rail、欢迎页和浮层组件已经具备克制、柔和、半透明的视觉语言,Spotlight 应沿用这套风格,而不是引入一个新系统。 -4. 该功能会跨越主进程快捷键、历史索引、设置导航和会话跳转,适合先走 SDD,把产品与技术边界先固定下来。 - -## 关键决策 - -### 1. 快捷键默认值 - -为避免与已经落地的“当前会话 inline 搜索(`Cmd/Ctrl+F`)”冲突,Spotlight v1 默认快捷键定为: - -- `QuickSearch = CommandOrControl+P` - -同时: - -- 保留 `Cmd/Ctrl+F` 作为“当前会话内搜索” -- `QuickSearch` 进入现有快捷键设置页,允许用户自定义 - -### 2. UI 定位 - -Spotlight 是: - -- 单实例 -- 主聊天窗口顶层 modal overlay -- 不在设置窗口再做第二套面板 - -### 3. 搜索范围 - -Spotlight 搜索默认忽略当前 sidebar 的 agent 过滤,始终面向“全量历史 + 全量导航项”。 - -### 4. 设置项粒度 - -v1 只搜索“设置页面”,不下钻到单个开关或表单项。 - -### 5. 动作范围 - -v1 只包含非破坏性动作: - -- New Chat -- Open Settings -- 跳到 Providers / Agents / MCP / Shortcuts / Remote 等设置页 - -## 用户故事 - -### US-1:全局快速唤起 - -作为用户,我希望在主聊天窗口内按 `Cmd/Ctrl+P` 就能呼出统一搜索面板,而不必先决定去侧边栏、设置页还是聊天正文里找。 - -### US-2:统一搜索结果列表 - -作为用户,我希望在一个稳定的列表里同时看到 session / message / agent / setting / action 结果,并通过键盘连续浏览,不被多段分组打断。 - -### US-3:消息级跳转 - -作为用户,我希望命中某条历史消息后,应用能自动切到对应会话、等待消息加载完成、滚动到目标消息并做短暂高亮,而不是只打开会话顶部。 - -### US-4:快速导航 - -作为用户,我希望 Spotlight 能直接跳到 Agent、设置页和少量常用动作,从而减少点击层级。 - -### US-5:空查询可用 - -作为用户,我希望即使不输入关键词,也能看到最近会话、常用 Agent 和常用动作,从而把 Spotlight 当成轻量启动器使用。 - -## 功能需求 - -### A. 唤起与关闭 - -- [ ] 新增快捷键配置项 `QuickSearch` -- [ ] `QuickSearch` 默认值为 `CommandOrControl+P` -- [ ] 主进程注册并分发 `SHORTCUT_EVENTS.TOGGLE_SPOTLIGHT` -- [ ] 左侧 rail 增加常驻 Spotlight 入口 -- [ ] 面板打开时自动聚焦输入框 -- [ ] `Esc` 关闭面板 - -### B. 键盘与鼠标交互 - -- [ ] `↑/↓` 切换 active item -- [ ] `Home/End` 跳首尾 -- [ ] `Enter` 执行 active item -- [ ] 鼠标 hover 会同步 active item -- [ ] 鼠标 click 执行当前项 -- [ ] 键盘 active item 与 hover 状态不得互相打架 - -### C. 结果形态 - -- [ ] 空查询展示 `Recent Sessions + Agents + Actions` -- [ ] 有查询时展示单一混排结果列表 -- [ ] 每个结果都有 `kind pill`,仅使用: - - [ ] `Session` - - [ ] `Message` - - [ ] `Agent` - - [ ] `Setting` - - [ ] `Action` -- [ ] 单次请求最多渲染 12 条结果 - -### D. 命中行为 - -- [ ] `session` 命中切到目标会话 -- [ ] `message` 命中切到目标会话,并在消息加载完成后滚动到目标消息并高亮约 2 秒 -- [ ] `agent` 命中复用现有侧边栏切换逻辑 -- [ ] `setting` 命中打开/聚焦设置窗口并导航到页面 -- [ ] `action` 命中只执行非破坏性动作 - -### E. 历史索引 - -- [ ] 主进程提供统一的历史搜索入口 -- [ ] 默认使用 SQLite FTS5 做全文索引 -- [ ] FTS 失败或 query 不适配 tokenizer 时自动降级到 `LIKE` -- [ ] 首次启动或 schema 变更时支持全量回填 -- [ ] 会话与消息变更路径支持增量同步 - -### F. 索引内容边界 - -- [ ] 会话索引包含用户可见标题 -- [ ] 用户消息只索引可见文本 -- [ ] 助手消息只索引可见内容块与错误文本 -- [ ] 不索引工具调用原始 JSON -- [ ] 不索引图片元数据 -- [ ] 不索引尚未持久化的 streaming 临时内容 - -### G. 排序 - -- [ ] 标题前缀命中优先于名称前缀命中 -- [ ] 名称前缀命中优先于正文命中 -- [ ] `session` 结果略高于 `message` -- [ ] 同层级结果叠加轻量 recency boost - -## 验收标准 - -- [ ] 在主聊天窗口按 `Cmd/Ctrl+P` 可打开 Spotlight;按 `Esc` 可关闭 -- [ ] 空查询时能看到 Recent Sessions、Agents、Actions -- [ ] 输入关键词后,结果列表混排展示 `Session / Message / Agent / Setting / Action` -- [ ] 命中会话可直接切换;命中消息会切到会话并滚动到目标消息 -- [ ] 命中 Agent 时可复用现有 agent 切换流程 -- [ ] 命中设置页时会聚焦或打开设置窗口并导航到正确页面 -- [ ] FTS 查询失败时仍能通过降级查询返回结果 -- [ ] Spotlight 在 sidebar 收起状态下仍可用 - -## 非目标 - -1. v1 不做 prefix scopes(如 `> / @ / #`)。 -2. v1 不做设置项级搜索,仅支持设置页面级导航。 -3. v1 不提供 destructive actions。 -4. v1 不覆盖设置窗口内部的独立 Spotlight UI。 -5. v1 不索引未持久化的 streaming 消息。 - -## 约束 - -1. 保持现有 Presenter + EventBus + preload IPC 架构。 -2. 所有用户可见文案必须走 i18n。 -3. 视觉风格沿用现有侧边栏 / 欢迎页的半透明卡片样式:`rounded-2xl + border + bg-card/40 + backdrop-blur`。 -4. 动效保持轻量,并尊重 `prefers-reduced-motion`。 -5. Spotlight 与当前 inline chat search 不能互相抢占默认快捷键。 - -## 开放问题 - -无。 diff --git a/docs/features/app-spotlight-search/tasks.md b/docs/features/app-spotlight-search/tasks.md deleted file mode 100644 index 448261a74..000000000 --- a/docs/features/app-spotlight-search/tasks.md +++ /dev/null @@ -1,84 +0,0 @@ -# App Spotlight Search Tasks - -## T0 规格与设计 - -- [x] 完成 `docs/features/app-spotlight-search/spec.md` -- [x] 完成 `docs/features/app-spotlight-search/plan.md` -- [x] 完成 `docs/features/app-spotlight-search/tasks.md` - -## T1 快捷键与事件 - -- [x] 新增快捷键配置项 `QuickSearch` -- [x] 默认值设为 `CommandOrControl+P` -- [x] 新增 `SHORTCUT_EVENTS.TOGGLE_SPOTLIGHT` -- [x] `ShortcutPresenter` 注册 / 重注册 Spotlight 快捷键 -- [x] 快捷键设置页展示并允许修改 `QuickSearch` - -## T2 历史搜索服务 - -- [x] 抽离"消息可见文本抽取"公共逻辑 -- [x] 新增 `deepchat_search_documents` 普通表 -- [x] 新增 `deepchat_search_documents_fts` FTS5 虚表 -- [x] 实现首次回填 / schema rebuild -- [x] 实现会话创建 / 重命名 / 删除的索引同步 -- [x] 实现消息写入 / 编辑 / 删除的索引同步 -- [x] 实现 FTS 失败回退到 `LIKE` - -## T3 Presenter 与共享类型 - -- [x] 新增 `HistorySearchOptions` -- [x] 新增 `HistorySearchHit / SessionHit / MessageHit` -- [x] `IAgentSessionPresenter` 增加 `searchHistory(query, options?)` -- [x] 补充共享类型导出 - -## T4 设置导航 registry - -- [x] 抽取设置页共享 registry -- [x] 字段包含 `routeName / titleKey / icon / keywords[]` -- [x] 设置窗口侧栏复用该 registry -- [x] Spotlight setting items 复用该 registry - -## T5 Spotlight Renderer 状态 - -- [x] 新增 `spotlight store` -- [x] 管理 `open/query/results/activeIndex/loading/requestSeq/pendingMessageJump` -- [x] 输入 80ms debounce -- [x] 按 requestSeq 丢弃过期响应 -- [x] 结果截断到 12 条 - -## T6 Spotlight UI - -- [x] 新增主聊天窗口顶层 Spotlight overlay -- [x] 沿用 `rounded-2xl + border + bg-card/40 + backdrop-blur` 视觉样式 -- [x] 左侧 rail 增加 Spotlight 入口 -- [x] 空查询展示 `Recent Sessions + Agents + Actions` -- [x] 查询态展示单一混排结果列表 -- [x] 增加 `kind pill` -- [x] 支持 `Esc / ↑ / ↓ / Home / End / Enter / hover / click` -- [ ] 尊重 `prefers-reduced-motion` - -## T7 执行行为 - -- [x] `session` 命中切会话 -- [x] `message` 命中写入 `pendingMessageJump` -- [x] `ChatPage` 在消息加载完成后滚动并高亮目标消息 -- [x] `agent` 命中复用现有侧栏切换逻辑 -- [x] `setting` 命中打开 / 聚焦设置窗口并导航 -- [x] `action` 命中只执行非破坏性动作 - -## T8 测试 - -- [x] main tests:排序、回填、增量同步、降级查询 -- [x] renderer tests:打开关闭、自动聚焦、键盘链路 -- [x] renderer tests:混排与去重 -- [x] renderer tests:message jump + scroll highlight -- [x] renderer tests:agent / setting / action 执行 -- [ ] 验收场景:sidebar 收起、空查询、设置窗口聚焦 - -## T9 质量检查 - -- [x] `pnpm run format` -- [x] `pnpm run i18n` -- [x] `pnpm run lint` -- [x] `pnpm run typecheck` -- [x] 运行相关 main / renderer 测试 diff --git a/docs/features/cc-switch-provider-import/plan.md b/docs/features/cc-switch-provider-import/plan.md deleted file mode 100644 index 948048902..000000000 --- a/docs/features/cc-switch-provider-import/plan.md +++ /dev/null @@ -1,27 +0,0 @@ -# CC Switch Provider Import Plan - -## Implementation - -- Extend `ProviderImportService` with a `cc-switch` source that reads `~/.cc-switch/cc-switch.db` in readonly mode and uses the Windows HOME fallback only when the default profile path is missing. -- Put CC Switch first in the shared provider import source order. -- Keep scan results complete, but filter the first renderer source list to detected sources so missing apps are not shown. -- Query the `providers` table for supported CC Switch app types except `codex`. -- Parse app-specific JSON settings: - - Claude and Claude Desktop: `env.ANTHROPIC_AUTH_TOKEN` or `env.ANTHROPIC_API_KEY`, `env.ANTHROPIC_BASE_URL`, model env values, and desktop route metadata. - - Gemini: `env.GEMINI_API_KEY`, `env.GOOGLE_GEMINI_BASE_URL`, `env.GEMINI_MODEL`. - - OpenCode: `options.apiKey`, `options.baseURL`, AI SDK package name, and model keys. - - OpenClaw: `apiKey`, `baseUrl`, `api`, and `models`. - - Hermes: `api_key`, `base_url`, `api_mode`, and `models`. -- Filter all import sources after raw read so blank/template API keys never reach the public preview list. -- Add mapping metadata for credential-only imports so built-in providers keep their configured endpoint and runtime when CC Switch exposes a different wire protocol. - -## Compatibility - -- Existing import sessions remain in-memory and short-lived. -- Existing Alma, Cherry Studio, Hermes, and OpenClaw parsing keeps the same file formats but now hides empty provider configs. -- Custom provider API type override remains available for custom rows. - -## Risks - -- CC Switch may add new app types or settings shapes. Unknown rows should fail closed by hiding unsupported rows rather than guessing. -- Some translated descriptions may not mention CC Switch yet, but the source row itself is localized by provider name. diff --git a/docs/features/cc-switch-provider-import/spec.md b/docs/features/cc-switch-provider-import/spec.md deleted file mode 100644 index e813cbf67..000000000 --- a/docs/features/cc-switch-provider-import/spec.md +++ /dev/null @@ -1,30 +0,0 @@ -# CC Switch Provider Import - -## Goal - -Allow users to import configured provider credentials from CC Switch through the existing provider import dialog, without copying API keys by hand. - -## User Stories - -- As a DeepChat user, I can see CC Switch as a detected provider import source when `~/.cc-switch/cc-switch.db` exists. -- As a user with CC Switch providers, I only see rows that contain a real API key. -- As a user importing Claude-compatible CC Switch providers, I do not accidentally switch DeepChat built-in providers to the wrong runtime. - -## Acceptance Criteria - -- CC Switch appears first in the provider import source order. -- The first import page only lists detected sources, hiding sources whose config files are missing. -- The scan reads CC Switch provider rows for `claude`, `claude-desktop`, `gemini`, `opencode`, `openclaw`, and `hermes`. -- CC Switch `codex` rows are intentionally ignored; DeepChat does not parse TOML from CC Switch. -- Rows with blank API keys or placeholder/template API keys are not shown in scan results. -- DeepSeek rows exposed as Anthropic-compatible endpoints import only the API key into DeepChat's built-in `deepseek` provider, preserving its existing runtime and base URL. -- MiniMax rows map to DeepChat's built-in `minimax` provider and keep Anthropic runtime behavior. -- Unknown importable rows with an API key and HTTP endpoint import as custom providers with the safest inferred API type. -- Raw API keys stay in the main process scan session and are never returned to the renderer during preview. - -## Non-Goals - -- Importing CC Switch Codex providers. -- Reading CC Switch custom data directories. -- Importing CC Switch conversations, prompts, MCP servers, skills, usage data, or failover settings. -- Network validation of imported credentials. diff --git a/docs/features/cc-switch-provider-import/tasks.md b/docs/features/cc-switch-provider-import/tasks.md deleted file mode 100644 index 8a72dfd26..000000000 --- a/docs/features/cc-switch-provider-import/tasks.md +++ /dev/null @@ -1,11 +0,0 @@ -# CC Switch Provider Import Tasks - -- [x] Add `cc-switch` to shared provider import source ids and route schemas. -- [x] Move CC Switch to the front of the import source order. -- [x] Hide missing import sources from the first renderer selection page. -- [x] Add credential-only import mode and warning text. -- [x] Implement CC Switch SQLite scan support for non-Codex app types. -- [x] Filter blank/template API keys across all provider import sources. -- [x] Preserve built-in provider runtime/base URL for credential-only imports. -- [x] Add provider import unit tests for CC Switch and updated empty-key behavior. -- [x] Run format, i18n, lint, focused provider import tests, and full typecheck. diff --git a/docs/features/chat-input-hero-transition/plan.md b/docs/features/chat-input-hero-transition/plan.md deleted file mode 100644 index 42bc68c9b..000000000 --- a/docs/features/chat-input-hero-transition/plan.md +++ /dev/null @@ -1,32 +0,0 @@ -# Chat Input Hero Transition Plan - -## Approach - -- Add a small renderer utility that captures the source `ChatInputBox` DOM node from `NewThreadPage.vue`, clones it into a fixed overlay, and stores a pending flight in module state. -- Consume that pending flight from `ChatPage.vue` after mount by animating the overlay clone to the destination `ChatInputBox` bounds with the Web Animations API. -- Fade the destination composer in during the last part of the flight to avoid duplicate visible inputs. - -## Affected Files - -- `src/renderer/src/lib/chatInputHero.ts` -- `src/renderer/src/pages/NewThreadPage.vue` -- `src/renderer/src/pages/ChatPage.vue` - -## Data Flow - -- `NewThreadPage.vue` resolves the local `ChatInputBox` element and calls `prepareChatInputHeroFlight()` before triggering session navigation. -- The helper stores an in-memory pending flight plus an overlay clone attached to `document.body`. -- `ChatPage.vue` resolves its composer element on mount and calls `playChatInputHeroFlight()`. -- The helper animates overlay position, scale, and border radius, then removes the overlay and restores the destination element. - -## Compatibility And Risk - -- Reduced-motion users bypass the animation. -- Failed navigation or failed session creation explicitly cancel the pending overlay. -- Because the state is module-local and one-shot, unrelated route changes remain unaffected. - -## Validation - -- Run `pnpm run format` -- Run `pnpm run i18n` -- Run `pnpm run lint` diff --git a/docs/features/chat-input-hero-transition/spec.md b/docs/features/chat-input-hero-transition/spec.md deleted file mode 100644 index d26d131e3..000000000 --- a/docs/features/chat-input-hero-transition/spec.md +++ /dev/null @@ -1,28 +0,0 @@ -# Chat Input Hero Transition - -## Goal - -When the user sends the first message from the new thread page, the `ChatInputBox` should animate into the chat page instead of disappearing and reappearing abruptly. - -## User Need - -- As a user, when I move from the centered composer on `NewThreadPage.vue` to the sticky composer on `ChatPage.vue`, I want the transition to feel continuous. - -## Acceptance Criteria - -- Submitting from `NewThreadPage.vue` prepares a one-shot hero transition for the current `ChatInputBox`. -- When `ChatPage.vue` mounts for that navigation, the hero transition animates from the previous composer bounds to the new composer bounds. -- If reduced motion is enabled, the transition is skipped. -- If session creation or route activation fails, any temporary hero overlay is cleaned up. -- The transition only applies to the new-thread to chat handoff and does not affect normal chat-to-chat session switching. - -## Constraints - -- Reuse the existing `ChatInputBox` DOM structure instead of introducing a separate fake design. -- Keep implementation renderer-local; no new IPC or persisted state. -- Avoid changing user-facing copy. - -## Non-Goals - -- Animating the entire page layout. -- Introducing a generic shared-element framework for unrelated components. diff --git a/docs/features/chat-input-hero-transition/tasks.md b/docs/features/chat-input-hero-transition/tasks.md deleted file mode 100644 index ed5895340..000000000 --- a/docs/features/chat-input-hero-transition/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Chat Input Hero Transition Tasks - -1. Add SDD docs for the composer hero transition. -2. Implement a renderer helper for preparing, playing, and cancelling a one-shot composer flight. -3. Hook `NewThreadPage.vue` into flight preparation and failure cleanup. -4. Hook `ChatPage.vue` into flight playback after mount. -5. Run formatting and lint validation. diff --git a/docs/features/chat-settings-control/plan.md b/docs/features/chat-settings-control/plan.md deleted file mode 100644 index 5d93c77f7..000000000 --- a/docs/features/chat-settings-control/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Control Settings via Chat Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/chat-settings-control/spec.md b/docs/features/chat-settings-control/spec.md deleted file mode 100644 index 4e181d54c..000000000 --- a/docs/features/chat-settings-control/spec.md +++ /dev/null @@ -1,85 +0,0 @@ -# Control Settings via Chat - -## Overview - -Allow users to update a small, safe subset of DeepChat settings via natural language within conversations. Changes MUST be validated, persisted, and take effect immediately. For complex/high-risk settings (e.g., MCP configuration, prompts), the assistant MUST NOT apply changes directly; instead, it should explain where to edit them and automatically open settings (ideally deep-linked to the relevant section). - -This specification is intentionally split into two increments: - -- **Step 1**: Provide a safe, validated settings application API (main process) that can be called from controlled entry points (renderer UI and/or agent tools) to change settings and trigger live updates. -- **Step 2**: Deliver natural language behavior as a **DeepChat skill** so that additional context is injected only when relevant. - -## Goals - -- Allow in-conversation updates to: - - Toggle settings: Sound, Copy COT details. - - Enum settings: Language, Theme, Font size. -- Apply changes immediately (current window + relevant other windows). -- Persist changes to existing configuration store. -- Keep surface area safe: do not expose arbitrary configuration keys. -- Use skills to control context: - - Settings modification guidance MUST be injected ONLY when user actually requests to change DeepChat settings. - -## Non-Goals - -- Do NOT allow users to set arbitrary `ConfigPresenter.setSetting(key, value)` keys via chat. -- Do NOT allow setting sensitive values via chat (API keys, tokens, environment variables, file paths, command arguments). -- Do NOT implement editing of MCP servers, prompts, providers, or other complex nested config via natural language. -- Do NOT change how settings are stored on disk (no migrations in this feature). - -## User Stories - -- As a user, I can say "turn on sound" and it enables sound immediately. -- As a user, I can say "copy COT details when copying" and it enables/disables the toggle. -- As a user, I can say "set language to English" and UI language switches immediately. -- As a user, I can say "use dark theme" or "follow system theme" and theme updates immediately. -- As a user, I can say "make text larger" and font size changes immediately. -- As a user, if I ask "add MCP server" or "edit prompts", the assistant tells me where in settings and opens settings there. - -## Acceptance Criteria - -### Step 1: Safe Settings Application API (No NLP) - -- A main process API exists that accepts restricted, validated requests to change one supported setting. -- Only allowlisted settings from this specification can be changed via this API. -- Setting tools are NOT injected into LLM tool list when `deepchat-settings` skill is **NOT** active. -- On success: - - Setting value is persisted (existing underlying storage). - - Changes take effect immediately in current renderer. - - Cross-window/tab updates happen where existing event flow supports (e.g., theme/language/font size/sound). -- On failure: - - Invalid inputs are rejected with structured, user-presentable errors (no partial writes). -- API is safe to call with untrusted input (strict validation + allowlist). - -### Step 2: Natural Language via Skill (Context Control) - -- A built-in skill exists (suggested: `deepchat-settings`) describing this functionality. -- This skill is NOT intended to remain active by default: - - It should activate only when user requests to change DeepChat's own settings. - - It should deactivate after setting change is complete. -- When active, assistant: - - Explains user intent, normalizes to canonical values, and calls Step 1 API. - - For disallowed/complex settings (MCP, prompts, etc.), provides guidance and opens settings to best-match section. - -## Open Questions [NEEDS CLARIFICATION] - -1. Skill Mode Availability - - Skill prompt injection currently seems tied to `chatMode === 'agent'`. Do we want this feature to work in: - - Agent mode only (suggested first increment), OR - - Also in chat/ACP agent modes (requires additional work)? -2. Font Size Representation - - Should chat use semantic labels (`small/medium/large`) mapping to `fontSizeLevel`, or accept explicit numeric levels? -3. Settings Deep Link Targets - - What are the canonical settings tab/section IDs we want to support deep-linking to (e.g., `mcp`, `prompts`, `appearance`, `language`)? -4. UX: Confirm vs Silent Apply - - Should assistant always confirm before applying changes, or apply immediately with "undo" capability? - -## Security & Privacy Notes - -- Step 1 API MUST: - - Use an allowlist of setting IDs. - - Validate input types and enum ranges. - - Avoid any generic "set arbitrary key" functionality. -- Defense in depth (recommended): Setting tools/entry points should verify the relevant skill is active for the conversation before applying. -- Step 2 MUST NOT allow indirect privilege escalation: - - MUST NOT change file system paths, command arguments, environment variables, or settings that hold secrets via natural language. diff --git a/docs/features/chat-settings-control/tasks.md b/docs/features/chat-settings-control/tasks.md deleted file mode 100644 index b61dc932e..000000000 --- a/docs/features/chat-settings-control/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Control Settings via Chat Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/chat-sidebar-input-polish/plan.md b/docs/features/chat-sidebar-input-polish/plan.md deleted file mode 100644 index b137327d3..000000000 --- a/docs/features/chat-sidebar-input-polish/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Chat Sidebar Input Polish Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/chat-sidebar-input-polish/spec.md b/docs/features/chat-sidebar-input-polish/spec.md deleted file mode 100644 index 95eb0e447..000000000 --- a/docs/features/chat-sidebar-input-polish/spec.md +++ /dev/null @@ -1,21 +0,0 @@ -# Chat Sidebar Input Polish - -## Summary -- Keep a visible `+` entry for starting a new conversation when the session sidebar is collapsed. -- Make the collapsed `+` behave exactly like the sidebar header action, including the `All Agents` fallback rules. -- Reduce the default chat input height from `80px` to `60px` while preserving the current max height and scrolling behavior. - -## Acceptance Criteria -- When the chat sidebar is expanded, the existing sidebar header `+` continues to start a new conversation. -- When the chat sidebar is collapsed, a `+` button appears at the top-left of the main chat workspace. -- The collapsed `+` is available on `AgentWelcomePage`, `NewThreadPage`, and `ChatPage`. -- If `All Agents` is selected and there is no active session, clicking `+` selects the first enabled agent and opens a new thread for that agent. -- If `All Agents` is selected and there is an active session, clicking `+` keeps the current session agent and closes the session into that agent's new-thread state. -- If a specific agent is selected, clicking `+` starts a new conversation for that same agent. -- When the collapsed `+` is visible on `ChatPage`, the session header content shifts right enough to avoid overlap. -- The chat input editor keeps `max-h-[240px]` and scrolling, but its default minimum height becomes `60px`. - -## Non-Goals -- No shared-element animation between the sidebar and the collapsed button. -- No changes to toolbar layout, message editor layout, or global welcome page behavior. -- No new i18n keys, IPC changes, or shared type contracts. diff --git a/docs/features/chat-sidebar-input-polish/tasks.md b/docs/features/chat-sidebar-input-polish/tasks.md deleted file mode 100644 index c2b700f0d..000000000 --- a/docs/features/chat-sidebar-input-polish/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Chat Sidebar Input Polish Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/context-engineering-label/plan.md b/docs/features/context-engineering-label/plan.md deleted file mode 100644 index 8fdd276ce..000000000 --- a/docs/features/context-engineering-label/plan.md +++ /dev/null @@ -1,10 +0,0 @@ -# Implementation Plan - -## Change - -- Update `settings.controlCenter.groups.knowledge` in Chinese locale files. -- Keep `settings.controlCenter.overview.knowledge` unchanged. - -## Validation - -- Run format, i18n, lint, and web typecheck. diff --git a/docs/features/context-engineering-label/spec.md b/docs/features/context-engineering-label/spec.md deleted file mode 100644 index 7770ac63e..000000000 --- a/docs/features/context-engineering-label/spec.md +++ /dev/null @@ -1,15 +0,0 @@ -# Context Engineering Label - -## User Story - -As a Chinese-language settings user, I want the sidebar group that contains Skills, Prompt, and Knowledge Base to be named "上下文工程", so the section better describes its purpose. - -## Acceptance Criteria - -- The settings sidebar group currently labeled "知识" displays "上下文工程" in Chinese locales. -- Existing individual item labels such as Skills, Prompt, and Knowledge Base remain unchanged. - -## Non-goals - -- No route or navigation structure changes. -- No changes to overview metric labels. diff --git a/docs/features/context-engineering-label/tasks.md b/docs/features/context-engineering-label/tasks.md deleted file mode 100644 index a263c5c55..000000000 --- a/docs/features/context-engineering-label/tasks.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Update Chinese sidebar group label. -- [x] Run validation commands. diff --git a/docs/features/data-settings-danger-zone-entry/plan.md b/docs/features/data-settings-danger-zone-entry/plan.md deleted file mode 100644 index 0662abb85..000000000 --- a/docs/features/data-settings-danger-zone-entry/plan.md +++ /dev/null @@ -1,19 +0,0 @@ -# Implementation Plan - -## UI - -- Replace the three always-visible Danger Zone destructive buttons in `DataSettings.vue` with one outline reset entry. -- Keep the existing confirmation dialog and radio choices as the authoritative place for choosing the reset type. -- Add stable test IDs for the reset entry and dialog choices. - -## Behavior - -- Opening the reset dialog resets the selected type to `chat`. -- Confirmation continues to call `devicePresenter.resetDataByType(resetType.value)`. -- Existing disabled states for import and backup still gate both the entry and confirmation action. - -## Validation - -- Update focused renderer tests for the single entry and dialog options. -- Verify selected reset type still reaches the presenter. -- Run format, i18n, lint, and the focused Data Settings test. diff --git a/docs/features/data-settings-danger-zone-entry/spec.md b/docs/features/data-settings-danger-zone-entry/spec.md deleted file mode 100644 index f0bb19038..000000000 --- a/docs/features/data-settings-danger-zone-entry/spec.md +++ /dev/null @@ -1,19 +0,0 @@ -# Data Settings Danger Zone Entry - -## User Story - -用户在数据设置页查看常规数据操作时,Danger Zone 不应以三个高权重红色按钮长期占据页面注意力;只有主动进入重置流程后,才需要看到具体的破坏性重置类型。 - -## Acceptance Criteria - -- Danger Zone 默认只显示一个低噪声的重置入口。 -- 重置入口使用 outline/destructive text 风格,不使用大面积红色背景。 -- 具体重置类型仍在确认弹窗中选择,包含聊天、知识库、配置和完全重置。 -- 默认重置类型为聊天数据。 -- 现有重置调用语义保持不变。 - -## Non-goals - -- 不改变任何重置数据语义。 -- 不新增 IPC、Presenter 方法或持久化格式。 -- 不调整 YoBrowser、数据库修复、模型配置更新等相邻操作。 diff --git a/docs/features/data-settings-danger-zone-entry/tasks.md b/docs/features/data-settings-danger-zone-entry/tasks.md deleted file mode 100644 index 555a39c5d..000000000 --- a/docs/features/data-settings-danger-zone-entry/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Replace the Danger Zone outer button group with one reset entry. -- [x] Keep dialog reset choices visible and testable. -- [x] Update renderer coverage for layout and reset behavior. -- [x] Run validation commands. diff --git a/docs/features/data-sync-card-header/plan.md b/docs/features/data-sync-card-header/plan.md deleted file mode 100644 index 449e2b0db..000000000 --- a/docs/features/data-sync-card-header/plan.md +++ /dev/null @@ -1,12 +0,0 @@ -# Implementation Plan - -## Change - -- Remove the redundant Backup & Sync section header from the data settings sync card. -- Keep the sync controls, backup actions, and import dialog unchanged. -- Remove the redundant Data Operations section header from the maintenance card. -- Keep the maintenance and reset actions unchanged. - -## Validation - -- Run format, i18n, and lint. diff --git a/docs/features/data-sync-card-header/spec.md b/docs/features/data-sync-card-header/spec.md deleted file mode 100644 index d2377a8fc..000000000 --- a/docs/features/data-sync-card-header/spec.md +++ /dev/null @@ -1,16 +0,0 @@ -# Data Sync Card Header - -## User Story - -As a settings user, I want data settings cards to avoid repeating their titles, so the cards read cleaner and the row labels carry the structure. - -## Acceptance Criteria - -- The Backup & Sync header inside the data sync card is removed. -- Enable data sync, sync folder, last sync time, backup, and import controls remain unchanged. -- The Data Operations header inside the maintenance card is removed. -- Database repair, model config update, danger zone, and YoBrowser controls remain unchanged. - -## Non-goals - -- No changes to data sync, backup, import, or repair behavior. diff --git a/docs/features/data-sync-card-header/tasks.md b/docs/features/data-sync-card-header/tasks.md deleted file mode 100644 index 1275a59f3..000000000 --- a/docs/features/data-sync-card-header/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Remove the redundant data sync card header. -- [x] Remove the redundant data operations card header. -- [x] Run validation commands. diff --git a/docs/features/deepchat-data-import-skill/plan.md b/docs/features/deepchat-data-import-skill/plan.md deleted file mode 100644 index 96481b3a3..000000000 --- a/docs/features/deepchat-data-import-skill/plan.md +++ /dev/null @@ -1,35 +0,0 @@ -# DeepChat Data Import Skill Plan - -## Skill Shape - -- Create `.agents/skills/deepchat-data-import/`. -- Keep `SKILL.md` as the trigger and workflow guide. -- Put detailed data and encryption documentation in one-level `references/` files: - - `data-locations.md` - - `sqlite-access.md` - - `schema-reference.md` - - `import-recipes.md` - -## Source Of Truth - -Derive the documented schema from: - -- `src/main/presenter/sqlitePresenter/index.ts` -- `src/main/presenter/sqlitePresenter/connectionConfig.ts` -- `src/main/presenter/databaseSecurityPresenter/index.ts` -- `src/main/presenter/sqlitePresenter/tables/*.ts` -- `src/main/presenter/agentRuntimePresenter/messageStore.ts` -- `src/main/presenter/agentRuntimePresenter/sessionStore.ts` -- `src/main/presenter/configPresenter/**` - -## Compatibility - -- Treat `agent.db` as the primary database. -- Mention legacy `chat.db` and `conversations/messages` only as compatibility paths. -- Avoid promising that internal table schemas are permanent; tell developers to inspect - `schema_versions` and `sqlite_master`. - -## Validation - -- Run the skill creator quick validator. -- Run repository formatting, i18n check, and lint after editing. diff --git a/docs/features/deepchat-data-import-skill/spec.md b/docs/features/deepchat-data-import-skill/spec.md deleted file mode 100644 index 9556249a9..000000000 --- a/docs/features/deepchat-data-import-skill/spec.md +++ /dev/null @@ -1,38 +0,0 @@ -# DeepChat Data Import Skill Specification - -## Goal - -Create a repository-local Codex skill that helps third-party developers import DeepChat data safely -and accurately. - -## User Stories - -- As an integration developer, I can locate DeepChat's SQLite database and configuration metadata - across supported platforms. -- As an integration developer, I can read provider, model, session, and message data from the - current `agent.db` schema. -- As an integration developer, I can handle both unencrypted SQLite databases and SQLCipher - encrypted databases. -- As an integration developer, I can decide whether to use Electron safeStorage, a user-provided - SQLite password, or a platform-native credential path. - -## Acceptance Criteria - -- The skill is stored under `.agents/skills/` with valid `SKILL.md` frontmatter and - `agents/openai.yaml` metadata. -- The skill links to focused reference files instead of embedding all schema and platform details in - `SKILL.md`. -- The references cover provider config, model config, MCP/app settings, current session/message - tables, legacy compatibility tables, and optional database encryption. -- The security guidance explains Electron, Tauri, and native macOS/Windows/Linux import options. -- The guidance favors read-only import, user consent, WAL-safe copies, and redaction of secrets. - -## Non-Goals - -- Do not add runtime import/export code to DeepChat in this change. -- Do not create sample applications for every framework. -- Do not define a stable public API beyond documenting the current database contract. - -## Open Questions - -None. diff --git a/docs/features/deepchat-data-import-skill/tasks.md b/docs/features/deepchat-data-import-skill/tasks.md deleted file mode 100644 index ac0f5881b..000000000 --- a/docs/features/deepchat-data-import-skill/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# DeepChat Data Import Skill Tasks - -- [x] Inspect current DeepChat schema, config storage, and encryption implementation. -- [x] Create SDD artifacts for the skill. -- [x] Initialize `.agents/skills/deepchat-data-import`. -- [x] Write the skill workflow and focused references. -- [x] Generate or verify `agents/openai.yaml`. -- [x] Validate the skill folder. -- [x] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/edit-file-tool/plan.md b/docs/features/edit-file-tool/plan.md deleted file mode 100644 index b89b5305e..000000000 --- a/docs/features/edit-file-tool/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# edit_file Tool Specification Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/edit-file-tool/spec.md b/docs/features/edit-file-tool/spec.md deleted file mode 100644 index 4caf4c8ad..000000000 --- a/docs/features/edit-file-tool/spec.md +++ /dev/null @@ -1,77 +0,0 @@ -# edit_file Tool Specification - -## Overview - -Add a new `edit_file` tool to the agent filesystem tools that enables AI agents to make precise text-based edits to files. This tool provides a simplified interface for exact string replacement, complementing the existing `edit_text` tool which supports regex and line-based operations. - -## User Stories - -### Primary User Story - -As an AI agent interacting with DeepChat, I want to edit specific portions of a file using exact text matching so that I can make precise modifications without worrying about regex escaping or complex operations. - -### Use Cases - -1. **Simple Text Replacement** - Replace a specific function implementation with an updated version -2. **Configuration Updates** - Modify specific configuration values in config files -3. **Bug Fixes** - Replace buggy code snippets with corrected versions -4. **Refactoring** - Update variable names or function calls across files - -## Acceptance Criteria - -### Functional Requirements - -- [ ] Tool accepts `path` (or `file_path`) parameter for target file -- [ ] Tool accepts `oldText` (or `old_string`) parameter for text to find -- [ ] Tool accepts `newText` (or `new_string`) parameter for replacement text -- [ ] Tool optionally accepts `base_directory` for resolving relative paths -- [ ] Tool performs exact string matching (case-sensitive) -- [ ] Tool replaces ALL occurrences of oldText when multiple matches exist -- [ ] Tool returns a JSON response with diff preview (original vs updated) -- [ ] Tool returns clear error message when oldText is not found -- [ ] Tool requires write permission before modifying files -- [ ] Tool validates paths are within allowed directories - -### Non-Functional Requirements - -- [ ] Tool follows existing filesystem tool patterns (schema, handler, registration) -- [ ] Tool response format is consistent with other filesystem tools -- [ ] Error messages are user-friendly and actionable -- [ ] Tool is registered under `agent-filesystem` server namespace - -## Non-Goals - -- Support for regex patterns (use `edit_text` or `text_replace` instead) -- Support for partial/line-based matching (use `edit_text` with `edit_lines` instead) -- Support for dry-run mode (can be added in future if needed) -- Support for multiple file editing in single call -- Support for backup creation (handled at system level if needed) - -## Constraints - -- Maximum text length for `oldText` and `newText` should be reasonable (suggest 10,000 chars) -- Path resolution follows existing `base_directory` pattern -- Must integrate with existing permission system for write operations - -## Open Questions - -None. All clarifications resolved: - -- **Parameter naming**: Support both camelCase (`path`, `oldText`, `newText`) and snake_case (`file_path`, `old_string`, `new_string`) variants for LLM compatibility -- **Multiple matches**: Replace all occurrences (not just first) to match common editing expectations -- **Case sensitivity**: Exact matching is case-sensitive (consistent with `edit_text` behavior) - -## UI/UX Considerations - -- Tool icon should match other filesystem tools (📁) -- Tool description: "Make precise edits to files by replacing exact text strings" -- Error display should highlight what text was not found - -## Related Tools - -| Tool | Purpose | When to Use | -|------|---------|-------------| -| `write_file` | Overwrite entire file | Creating new files or full rewrites | -| `edit_text` | Pattern/line-based editing | Regex replacement or complex edits | -| `text_replace` | Regex-based replacement | Pattern-based text replacement | -| `edit_file` | Exact string replacement | Simple, precise text modifications | diff --git a/docs/features/edit-file-tool/tasks.md b/docs/features/edit-file-tool/tasks.md deleted file mode 100644 index 876e866f0..000000000 --- a/docs/features/edit-file-tool/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# edit_file Tool Specification Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/electron-vite-5-upgrade/plan.md b/docs/features/electron-vite-5-upgrade/plan.md deleted file mode 100644 index 4ecb3ade4..000000000 --- a/docs/features/electron-vite-5-upgrade/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Electron Vite 5 Upgrade Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/electron-vite-5-upgrade/spec.md b/docs/features/electron-vite-5-upgrade/spec.md deleted file mode 100644 index a627d321c..000000000 --- a/docs/features/electron-vite-5-upgrade/spec.md +++ /dev/null @@ -1,32 +0,0 @@ -# Electron Vite 5 Upgrade - -## Summary - -Upgrade `electron-vite` from `^4.0.1` to `5.0.0` using the official migration guide at -`https://cn.electron-vite.org/guide/migration`. - -## Goals - -- keep current development and build scripts working with `electron-vite@5.0.0` -- remove deprecated v4-only config usage from `electron.vite.config.ts` -- preserve the existing dependency bundling behavior for `main` and `preload` - -## Acceptance Criteria - -- `package.json` depends on `electron-vite@5.0.0` -- `electron.vite.config.ts` no longer imports or uses `externalizeDepsPlugin` -- the current `main` config still bundles `mermaid` instead of externalizing it -- `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` pass -- typecheck/build validation still passes after the migration - -## Non-Goals - -- no renderer architecture changes -- no Electron version upgrade -- no bytecode or isolated build adoption as part of this migration - -## Migration Notes - -- `externalizeDepsPlugin()` is deprecated in v5 and replaced by `build.externalizeDeps` -- this repository does not use `bytecodePlugin` -- this repository already uses static `main`, `preload`, and `renderer` config objects diff --git a/docs/features/electron-vite-5-upgrade/tasks.md b/docs/features/electron-vite-5-upgrade/tasks.md deleted file mode 100644 index c00c0f3dc..000000000 --- a/docs/features/electron-vite-5-upgrade/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Electron Vite 5 Upgrade Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/file-attachment-support/plan.md b/docs/features/file-attachment-support/plan.md deleted file mode 100644 index f7183978d..000000000 --- a/docs/features/file-attachment-support/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Chat Attachment File Support Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/file-attachment-support/spec.md b/docs/features/file-attachment-support/spec.md deleted file mode 100644 index a5a3b826a..000000000 --- a/docs/features/file-attachment-support/spec.md +++ /dev/null @@ -1,19 +0,0 @@ -# Chat Attachment File Support - -## User Story - -Users can attach common office, document, text, image, and audio files from the chat input. If a file cannot be processed, DeepChat reports the failure instead of silently ignoring it. - -## Acceptance Criteria - -- Word OOXML documents such as `.docx`, `.docm`, `.dotx`, and `.dotm` route to the document adapter. -- Spreadsheet files such as `.xls`, `.xlsx`, `.xlsm`, `.xlsb`, `.xltx`, `.xltm`, `.ods`, `.csv`, and `.tsv` route to spreadsheet or delimited text adapters. -- Presentation OOXML files such as `.pptx`, `.pptm`, `.ppsx`, `.ppsm`, `.potx`, and `.potm` route to the presentation adapter. -- Common text formats such as `.txt`, `.md`, `.markdown`, `.json`, `.yaml`, `.yml`, `.xml`, `.html`, `.css`, and source files remain supported. -- Common media formats such as `.pdf`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`, `.svg`, `.heic`, `.heif`, `.mp3`, `.wav`, and `.m4a` remain supported where the existing adapters can process them. -- Chat input selection, paste, and drop flows show a localized destructive toast when one or more files fail processing. - -## Non-Goals - -- Full parsing support for legacy binary `.doc` and `.ppt` files is not introduced in this increment. -- The file picker is not restricted to only known extensions; unsupported files should still fail with visible feedback. diff --git a/docs/features/file-attachment-support/tasks.md b/docs/features/file-attachment-support/tasks.md deleted file mode 100644 index c4e97d5f0..000000000 --- a/docs/features/file-attachment-support/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Chat Attachment File Support Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/floating-agent-widget/plan.md b/docs/features/floating-agent-widget/plan.md deleted file mode 100644 index e62a84568..000000000 --- a/docs/features/floating-agent-widget/plan.md +++ /dev/null @@ -1,91 +0,0 @@ -# 悬浮 Agent 任务总览小部件实施计划 - -## 设计决策 - -1. 保留现有独立悬浮窗口入口,但将其内容从“按钮 + 外部悬浮聊天窗入口”收敛为“任务小部件”。 -2. 小部件数据由主进程聚合,renderer 只负责展示和交互。 -3. 仅复用 `agentSessionPresenter.getSessionList()` 与 `AgentRuntimePresenter` 的状态事件,不新增独立数据库表。 -4. 点击会话时不再打开旧的 `FloatingChatWindow`,而是唤起主窗口并激活对应会话。 - -## 数据模型 - -新增共享类型: - -- `FloatingWidgetSessionStatus` -- `FloatingWidgetSessionItem` -- `FloatingWidgetSnapshot` - -状态来源: - -- 会话列表:`agentSessionPresenter.getSessionList()` -- 会话状态:`SessionWithState.status` -- 语言:`configPresenter.getLanguage()` - -本地映射: - -- `generating` -> `in_progress` -- `idle` -> `done` -- `error` -> `error` - -## 事件流 - -### 初始化 - -1. 主进程创建悬浮窗口 -2. 悬浮 renderer 通过 preload 拉取初始 snapshot + language -3. 主进程将当前 snapshot 推送给悬浮 renderer - -### 会话同步 - -以下场景统一触发 `floatingButtonPresenter.refreshWidgetState()`: - -- `agentSessionPresenter` 发出列表更新时 -- `agentRuntimePresenter` 会话状态变化时 -- 标题自动生成完成时 - -主进程刷新后向悬浮 renderer 发送最新 snapshot。 - -### 会话跳转 - -1. 悬浮 renderer 发送 `open-session(sessionId)` -2. 主进程选择目标 chat 窗口;若不存在则创建 -3. 主进程调用 `agentSessionPresenter.activateSession(targetWebContentsId, sessionId)` -4. 主进程显示并聚焦目标窗口 -5. 目标 renderer 收到 `SESSION_EVENTS.ACTIVATED` 后切换到对应 chat route - -### 展开与折叠 - -1. 悬浮 renderer 发送 `set-expanded(boolean)` -2. 主进程更新悬浮窗口尺寸 -3. 如果当前吸附在右侧,扩展时保持右边缘不动;左侧同理 - -### 拖拽与吸附 - -1. renderer 继续通过 preload 发送拖拽开始/移动/结束 -2. 主进程按屏幕坐标移动窗口 -3. 拖拽结束时取 `screen.getDisplayMatching(bounds)` 获取目标显示器 -4. 将 X 吸附到该显示器工作区左右边缘之一,Y 仅做边界裁剪 - -## 测试策略 - -1. 主进程纯函数测试: - - 会话排序 / 快照构建 - - 吸附与尺寸重定位 -2. renderer store 测试: - - `SESSION_EVENTS.ACTIVATED` 时主界面 route 同步到 chat -3. 手工验证: - - 折叠态图标与计数切换 - - 展开态列表 - - 点击会话唤起主窗口 - - 多显示器拖拽吸附 - -## 风险与缓解 - -1. 风险:悬浮窗口不是常规 renderer 入口,没有现成全局 i18n - - 缓解:在 floating entry 单独初始化 `vue-i18n`,语言由 preload 提供 -2. 风险:动态尺寸与持久化窗口状态冲突 - - 缓解:仅复用持久化位置,启动时始终使用折叠尺寸 -3. 风险:不同 agent 的会话混在一起后,用户可能不易快速辨识 - - 缓解:先保持统一排序和会话级状态,总览问题优先解决;必要时再补 agent 标识 -4. 风险:主进程激活会话后页面未切换到 chat - - 缓解:补齐 `sessionStore` 对外部 `ACTIVATED` 事件的 route 同步 diff --git a/docs/features/floating-agent-widget/spec.md b/docs/features/floating-agent-widget/spec.md deleted file mode 100644 index f2284347a..000000000 --- a/docs/features/floating-agent-widget/spec.md +++ /dev/null @@ -1,52 +0,0 @@ -# 悬浮 Agent 任务总览小部件规格 - -## 背景 - -当前悬浮按钮仅提供简单入口,无法直接体现各类 agent 的运行状态,也无法在桌面侧快速切回正在执行的会话。 - -本次改造将悬浮按钮升级为“可展开的任务总览小部件”,展示所有常规 agent 会话,并覆盖 ACP 会话。 - -## 用户故事 - -1. 作为用户,我希望在桌面上看到当前所有 agent 是否有任务正在执行。 -2. 作为用户,我希望点击悬浮框后,直接看到所有 agent 会话列表和每个会话的任务状态。 -3. 作为用户,我希望点击列表项后,主窗口被唤起并跳转到对应会话。 -4. 作为用户,我希望拖拽悬浮框后它会自动吸附到当前显示器的左侧或右侧边缘,并且不会超出屏幕。 - -## 验收标准 - -### 折叠态 - -- [ ] 统计所有常规 agent 会话,包括 ACP 会话,不展示 subagent 会话。 -- [ ] 当存在进行中的 agent 会话时,折叠态显示进行中数量和 loading 效果。 -- [ ] 当不存在进行中的 agent 会话时,折叠态显示应用 logo。 -- [ ] 折叠态视觉需与当前应用的玻璃感 / 暗色圆角风格一致。 - -### 展开态 - -- [ ] 点击折叠态后,悬浮框展开为会话列表面板。 -- [ ] 面板展示所有 agent 的常规会话列表,按“进行中优先,其次最近更新优先”排序。 -- [ ] 每个会话只展示一行标题文字。 -- [ ] 每个会话显示任务状态标识,至少覆盖 `in progress` 与 `done`。 -- [ ] 若会话状态为错误,可显示独立错误态,不影响 `in progress / done` 主流程。 -- [ ] 无会话时展示空状态。 - -### 跳转与同步 - -- [ ] 点击会话项后,显示主窗口并聚焦。 -- [ ] 点击会话项后,主窗口切换到目标会话。 -- [ ] 任意 agent 会话列表变更、标题变更、状态变更后,悬浮框能及时刷新。 -- [ ] 主进程与悬浮框之间的通讯沿用当前项目的 preload + IPC 风格。 - -### 拖拽与吸附 - -- [ ] 悬浮框支持拖拽移动。 -- [ ] 拖拽结束后,按当前位置自动吸附到当前显示器左侧或右侧边缘。 -- [ ] Y 轴位置保持当前值,但需限制在工作区内。 -- [ ] 支持跨显示器拖拽,拖拽结束后吸附到目标显示器边缘。 - -## 非目标 - -- [ ] 不在悬浮框内直接展示完整聊天内容。 -- [ ] 不在悬浮框内直接提供 ACP 专属控制面板。 -- [ ] 不实现会话内更细粒度任务步骤树,仅展示会话级状态。 diff --git a/docs/features/floating-agent-widget/tasks.md b/docs/features/floating-agent-widget/tasks.md deleted file mode 100644 index 15e7ae38a..000000000 --- a/docs/features/floating-agent-widget/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# 悬浮 Agent 任务总览小部件任务清单 - -- [x] 编写 spec / plan / tasks 文档 -- [ ] 新增悬浮小部件共享类型与主进程布局辅助函数 -- [ ] 改造 floating preload API,支持 snapshot / language / expand / open-session -- [ ] 重写 floating renderer UI,支持折叠态、展开态、会话状态标识与空状态 -- [ ] 改造主进程 floatingButtonPresenter,接入 snapshot 同步、主窗口跳转、拖拽吸附 -- [ ] 将所有 agent / NewAgent / Config 的变更事件同步到 floating widget -- [ ] 补充测试并完成 format、i18n、lint 校验 diff --git a/docs/features/high-priority-i18n-languages/plan.md b/docs/features/high-priority-i18n-languages/plan.md deleted file mode 100644 index 686642c2e..000000000 --- a/docs/features/high-priority-i18n-languages/plan.md +++ /dev/null @@ -1,27 +0,0 @@ -# High Priority i18n Languages Plan - -## Scope - -Implement the requested locales by following the existing static i18n bundle pattern. The source of truth for required keys is `src/renderer/src/i18n/zh-CN`, with `en-US` as a secondary reference for shorter Latin-script phrasing. - -## Implementation - -- Create one locale directory per target locale under `src/renderer/src/i18n/`. -- Reuse the existing locale `index.ts` import/export shape for each new locale. -- Register new locale modules in `src/renderer/src/i18n/index.ts`. -- Add language options in `src/renderer/settings/components/DisplaySettings.vue`. -- Add new locale codes to `ConfigPresenter.getSystemLanguage()`, `ChatLanguage`, and the DeepChat settings Agent tool schema. -- Extend shared context-menu and error-message translations in `src/shared/i18n.ts`. -- Keep RTL handling unchanged because all requested locales are LTR. - -## Validation - -- Run a structural comparison against `zh-CN` for all target locale JSON files. -- Run `pnpm run format`. -- Run `pnpm run i18n`. -- Run `pnpm run lint`. - -## Risks - -- The largest risk is incomplete or malformed JSON translation files. Mitigation: validate parseability and exact key parity. -- Some UI strings may become long in German, Polish, Turkish, or Vietnamese. Mitigation: prefer natural but concise desktop UI wording and use English as a length reference where appropriate. diff --git a/docs/features/high-priority-i18n-languages/spec.md b/docs/features/high-priority-i18n-languages/spec.md deleted file mode 100644 index 506b1a719..000000000 --- a/docs/features/high-priority-i18n-languages/spec.md +++ /dev/null @@ -1,29 +0,0 @@ -# High Priority i18n Languages - -## User Stories - -- As a DeepChat desktop user in Spain, Germany, Turkey, Indonesia, Malaysia, Italy, Poland, or Vietnam, I can select my language in Display settings and use the app with readable local UI text. -- As a user whose system locale is one of the supported locales, DeepChat can resolve the matching app language when language is set to System. -- As an Agent Skills user, product and technical names such as DeepChat, Agent, Skills, MCP, Dify, and model/provider names remain recognizable and are not mistranslated. - -## Acceptance Criteria - -- Add full renderer i18n bundles for `es-ES`, `de-DE`, `tr-TR`, `id-ID`, `ms-MY`, `it-IT`, `pl-PL`, and `vi-VN`. -- Each new locale has the same JSON files and key structure as `zh-CN`. -- New locales are registered in renderer, settings renderer, floating renderer, language selector options, system-locale matching, shared chat settings types, and DeepChat settings Agent tool language schema. -- Shared native menu and error-label translations support the new locales where the shared i18n helper is used. -- Translation wording follows the Chinese source meaning, with English used as length/reference for Latin-script languages. -- Product and domain terms stay untranslated where requested: DeepChat, Agent, Skills, MCP, Dify, model/provider brand names, API, JSON, URL, token, prompt, and similar technical identifiers. -- `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` pass. - -## Non-Goals - -- No UI layout redesign. -- No new runtime language-loading architecture. -- No locale-specific date, number, or plural-rule behavior beyond existing vue-i18n support unless needed by validation. - -## Constraints - -- Preserve existing keys, placeholders, interpolation variables, markdown, and JSON syntax. -- Keep translations clear for desktop application users; avoid jargon-heavy or literal machine-style phrasing. -- Resolve implementation without `[NEEDS CLARIFICATION]` markers. diff --git a/docs/features/high-priority-i18n-languages/tasks.md b/docs/features/high-priority-i18n-languages/tasks.md deleted file mode 100644 index 2a14ad85e..000000000 --- a/docs/features/high-priority-i18n-languages/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# High Priority i18n Languages Tasks - -- [x] Create SDD artifacts for the i18n expansion. -- [x] Add translated locale bundles for `es-ES`, `de-DE`, `tr-TR`, `id-ID`, `ms-MY`, `it-IT`, `pl-PL`, and `vi-VN`. -- [x] Register all new locales in renderer and main-process language support. -- [x] Extend shared menu/error translations for the new locale codes. -- [x] Validate locale key parity and JSON parseability. -- [x] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/hooks-notifications/plan.md b/docs/features/hooks-notifications/plan.md deleted file mode 100644 index 6848ca7b0..000000000 --- a/docs/features/hooks-notifications/plan.md +++ /dev/null @@ -1,43 +0,0 @@ -# Hooks Commands V2 Plan - -## Summary - -- 删除旧 Hooks 的 Telegram / Discord / Confirmo 通道能力 -- 重建为纯 command hooks -- 支持多组 hooks 配置与单组测试 -- 拆除 Telegram remote 对 hooks 配置的依赖 - -## Implementation - -### Shared / Main - -- 更新 `src/shared/hooksNotifications.ts` -- 将 hooks 设置模型改为 `hooks: HookCommandItem[]` -- 重写 `src/main/presenter/hooksNotifications/config.ts` -- 重写 `src/main/presenter/hooksNotifications/index.ts` -- 删除 Confirmo、Telegram、Discord 的 hooks 运行时逻辑 -- `testHookCommand` 改为按 `hookId` 执行 - -### Remote - -- 更新 `TelegramRemoteRuntimeConfig`,将 `botToken` 写回 remote-control 自己的配置 -- 删除 `TelegramRemoteSettings.hookNotifications` -- 删除 `RemoteControlPresenterDeps` 中的 hooks 依赖 -- 清理 `RemoteSettings` 中 Telegram hooks UI - -### Renderer - -- 重写 `NotificationsHooksSettings.vue` -- 改为多组 hook 列表 + 新建 / 删除 / 测试 -- 更新 i18n 文案,移除旧 webhook / confirmo 方向描述 - -## Validation - -- `typecheck:node` -- `typecheck:web` -- `test/main/presenter/hooksNotifications.test.ts` -- `test/main/presenter/remoteControlPresenter/remoteControlPresenter.test.ts` -- `test/renderer/components/RemoteSettings.test.ts` -- `format` -- `i18n` -- `lint` diff --git a/docs/features/hooks-notifications/spec.md b/docs/features/hooks-notifications/spec.md deleted file mode 100644 index 99bc4e1c4..000000000 --- a/docs/features/hooks-notifications/spec.md +++ /dev/null @@ -1,175 +0,0 @@ -# Hooks Commands V2 - -## 背景 - -旧版 Hooks 同时承担了三类职责: - -- Telegram / Discord webhook 通知 -- Confirmo 本地 hook -- 单事件单命令的 command hooks - -当前产品方向已经变化: - -- Telegram 和 Discord 都已经转向 full-duplex remote control -- Confirmo 不再保留 -- Hooks 只需要承担“在生命周期事件上执行自定义命令”这一件事 - -因此,本规格将 Hooks 重构为单一能力的 command hooks。 - -## 目标 - -- `Hooks` 页面只保留 `Hooks Commands` -- 支持配置多组 hooks -- 每组 hook 独立配置: - - `name` - - `enabled` - - `command` - - `events` -- 每组 hook 都可单独测试 -- 运行时按事件异步触发命令,不阻断主流程 - -## 非目标 - -- 不保留 Telegram hooks 通知 -- 不保留 Discord hooks 通知 -- 不保留 Confirmo -- 不做模板系统 -- 不做旧 hooks 配置迁移 -- 不提供阻断、改写、审批式 hooks - -## 配置模型 - -```ts -interface HookCommandItem { - id: string - name: string - enabled: boolean - command: string - events: HookEventName[] -} - -interface HooksNotificationsSettings { - hooks: HookCommandItem[] -} -``` - -约束: - -- `id` 为稳定主键 -- `command` 为空时视为未配置 -- `events` 为空时,不会在真实事件流里触发 -- 测试时若 `events` 为空,默认使用 `SessionStart` - -## 生命周期事件 - -保留原有事件集合: - -- `SessionStart` -- `UserPromptSubmit` -- `PreToolUse` -- `PostToolUse` -- `PostToolUseFailure` -- `PermissionRequest` -- `Stop` -- `SessionEnd` - -## 执行契约 - -### 输入 - -触发时将 payload JSON 写入 stdin,一次性写入并关闭 stdin。 - -```jsonc -{ - "payloadVersion": 1, - "event": "PreToolUse", - "time": "2026-04-13T12:00:00.000Z", - "isTest": false, - "app": { - "version": "1.0.3-beta.1", - "platform": "win32" - }, - "session": { - "conversationId": "conv_xxx", - "agentId": "agent_xxx", - "workdir": "C:\\repo\\project", - "providerId": "deepchat", - "modelId": "gpt-5.4" - } -} -``` - -### 执行规则 - -- 使用 `child_process.spawn` -- `shell: true` -- `cwd` 优先取当前会话 `workdir`,否则回退到 `process.cwd()` -- 写入环境变量(当前实现与 `src/main/presenter/hooksNotifications/index.ts` 保持一致): - - `DEEPCHAT_HOOK_EVENT` - - `DEEPCHAT_HOOK_TIME` - - `DEEPCHAT_HOOK_IS_TEST` - - `DEEPCHAT_CONVERSATION_ID` - - `DEEPCHAT_WORKDIR` - - `DEEPCHAT_AGENT_ID` - - `DEEPCHAT_PROVIDER_ID` - - `DEEPCHAT_MODEL_ID` - - `DEEPCHAT_MESSAGE_ID` - - `DEEPCHAT_TOOL_NAME` - - `DEEPCHAT_TOOL_CALL_ID` - - 如未来实现调整注入变量,以 `src/main/presenter/hooksNotifications/index.ts` 为准 -- 固定超时 30 秒 - -### 输出 - -- 记录并展示: - - `success` - - `durationMs` - - `exitCode` - - `stdout` - - `stderr` - - `error` -- `stdout/stderr` 只展示截断后的摘要 -- 结果只用于诊断,不影响主流程 - -## Settings 行为 - -页面结构: - -1. 页面标题与说明 -2. Hooks Commands 卡片 -3. 多组 hook 列表 -4. `New Hook` 按钮 - -每组 hook 提供: - -- 名称输入框 -- 命令输入框 -- 启用开关 -- 事件勾选 -- Test 按钮 -- Delete 按钮 - -默认新建值: - -- `name = Hook N` -- `enabled = false` -- `command = ''` -- `events = DEFAULT_IMPORTANT_HOOK_EVENTS` - -## 兼容策略 - -- 读取到旧版 `telegram / discord / confirmo / commands` 结构时,直接重置为新版默认值: - -```ts -{ hooks: [] } -``` - -- 不迁移旧配置 -- 不保留隐藏兼容逻辑 - -## Remote 侧约束 - -- Telegram remote 与 hooks 完全解耦 -- Discord remote 与 hooks 完全解耦 -- Telegram / Discord 只保留 remote control 能力 -- Telegram bot token 只存于 remote-control 配置,不再借道 hooks 配置 diff --git a/docs/features/hooks-notifications/tasks.md b/docs/features/hooks-notifications/tasks.md deleted file mode 100644 index 2600dc07d..000000000 --- a/docs/features/hooks-notifications/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Hooks Commands V2 Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/manual-compaction-command/plan.md b/docs/features/manual-compaction-command/plan.md deleted file mode 100644 index f3a8b130c..000000000 --- a/docs/features/manual-compaction-command/plan.md +++ /dev/null @@ -1,28 +0,0 @@ -# Manual Compaction Command Plan - -## Implementation - -- Add a typed `sessions.compact` route and renderer client method that returns whether a new summary - was written plus the current compaction state. -- Extend the agent session presenter and DeepChat agent implementation with `compactSession`. -- Add a manual compaction preparation path in `CompactionService` that force-prepares an intent while - ignoring `autoCompactionEnabled`, threshold, and retain-recent-pairs checks. -- Execute manual compaction only for idle DeepChat runtime sessions; reuse the existing - `applyCompactionIntent` flow for persisted indicator messages and compaction events. -- Add a local DeepChat slash command item for idle existing sessions. Filter only this item during - generation. -- Intercept exact `/compact` submissions in `ChatPage` and call the session route instead of - `chat.sendMessage`. - -## Compatibility - -- No database schema migration is required. -- Existing summaries remain valid and are updated through the existing compare-and-set summary path. -- ACP sessions keep their existing command discovery and prompt forwarding behavior. - -## Tests - -- Unit-test manual intent creation in `CompactionService`. -- Unit-test `AgentRuntimePresenter.compactSession` success and unsupported states. -- Cover the typed route contract and route dispatcher. -- Cover renderer suggestion visibility and ChatPage command interception. diff --git a/docs/features/manual-compaction-command/spec.md b/docs/features/manual-compaction-command/spec.md deleted file mode 100644 index 0db2596b5..000000000 --- a/docs/features/manual-compaction-command/spec.md +++ /dev/null @@ -1,39 +0,0 @@ -# Manual Compaction Command Spec - -> Status: Draft -> Date: 2026-05-12 - -## Background - -DeepChat Agent sessions already auto-compact old conversation history when context pressure reaches -the configured threshold. Users sometimes know a session should be compacted before the automatic -threshold is reached, especially before switching tasks or continuing a long-running agent thread. - -ACP agents manage their own command and context policy, so this feature is DeepChat Agent only. - -## Goals - -- Add a DeepChat Agent-only `/compact` slash command. -- Let users trigger the same compaction behavior as auto compaction without checking the current - threshold. -- Keep manual compaction available even when auto compaction is disabled. -- Hide `/compact` while a DeepChat session is generating, without disabling other slash suggestions. - -## Acceptance Criteria - -- In an idle DeepChat session, `/compact` appears in slash suggestions and triggers compaction. -- The command does not create a user message and is not sent to the model. -- Manual compaction reuses existing compaction messages, summary state updates, and renderer events. -- Manual compaction ignores `autoCompactionEnabled`, trigger threshold, and retain-recent-pairs - settings; those settings only affect automatic compaction. -- Manual compaction summarizes all eligible history after the current summary cursor, and only - returns no-op when there is no new persisted history to summarize. -- ACP sessions and new-thread drafts do not show or execute `/compact`. -- During generation, `/compact` is hidden and exact `/compact` submission is ignored; skills, - prompts, and tools remain available in slash suggestions. - -## Non-Goals - -- Add manual compaction controls outside the slash command. -- Change ACP command handling. -- Change the automatic compaction threshold behavior. diff --git a/docs/features/manual-compaction-command/tasks.md b/docs/features/manual-compaction-command/tasks.md deleted file mode 100644 index c9eaa7cea..000000000 --- a/docs/features/manual-compaction-command/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Manual Compaction Command Tasks - -- [x] Add SDD artifacts. -- [x] Add shared route/schema/client support. -- [x] Add DeepChat runtime manual compaction. -- [x] Add renderer slash command and submit interception. -- [x] Update compaction copy and regression tests. -- [x] Run format, i18n, lint, and focused tests. diff --git a/docs/features/mcp-footer-alignment/plan.md b/docs/features/mcp-footer-alignment/plan.md deleted file mode 100644 index e9d27b6e8..000000000 --- a/docs/features/mcp-footer-alignment/plan.md +++ /dev/null @@ -1,12 +0,0 @@ -# Implementation Plan - -## Change - -- Adjust the MCP server footer wrapper spacing to match the page header inset. -- Expose the MCP Add server dialog trigger so the settings header can host the Add action. -- Hide the footer Add action from the settings page usage. -- Keep the MCP page wrapper from becoming the scroll container so the server grid scrolls independently between the static header and footer. - -## Validation - -- Run format, i18n, lint, and focused MCP server tests. diff --git a/docs/features/mcp-footer-alignment/spec.md b/docs/features/mcp-footer-alignment/spec.md deleted file mode 100644 index f8ef11e09..000000000 --- a/docs/features/mcp-footer-alignment/spec.md +++ /dev/null @@ -1,19 +0,0 @@ -# MCP Footer Alignment - -## User Story - -As a settings user, I want the MCP footer status bar to align with the page title area, so the bottom controls feel like part of the same layout. - -## Acceptance Criteria - -- The MCP footer status bar remains pinned to the bottom of the MCP server list. -- The MCP footer status bar remains visible while the server grid scrolls, matching the static page-frame behavior of the MCP Center title area. -- Footer content uses the same horizontal inset as the MCP page title area. -- The Add server action appears in the MCP page title action area. -- The footer no longer shows the Add server action when used on the settings page. -- The NPM registry action uses the shorter NPM Source label. -- Existing footer status values and actions remain unchanged. - -## Non-goals - -- No changes to MCP server filtering, status calculation, or registry behavior. diff --git a/docs/features/mcp-footer-alignment/tasks.md b/docs/features/mcp-footer-alignment/tasks.md deleted file mode 100644 index 757741d3a..000000000 --- a/docs/features/mcp-footer-alignment/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Align MCP footer spacing with the title area. -- [x] Move the Add server action to the MCP title area. -- [x] Keep the MCP footer visible while the server grid scrolls. -- [x] Run validation commands. diff --git a/docs/features/mcp-settings-status-bar/plan.md b/docs/features/mcp-settings-status-bar/plan.md deleted file mode 100644 index b5fb97bb8..000000000 --- a/docs/features/mcp-settings-status-bar/plan.md +++ /dev/null @@ -1,13 +0,0 @@ -# Implementation Plan - -## UI - -- Remove the top card grid from `McpSettings`. -- Add a `status-bar` slot to `McpServers` so the page shell can inject compact MCP stats and NPM mirror controls into the existing footer. -- Remove the duplicate MCP market action from the server-list footer because the header already owns that entry. -- Restrict `McpServers` filter options to `all`, `running`, and `stopped`. - -## Validation - -- Update focused renderer tests that assert footer actions. -- Run focused tests plus required format, i18n, lint, and web typecheck. diff --git a/docs/features/mcp-settings-status-bar/spec.md b/docs/features/mcp-settings-status-bar/spec.md deleted file mode 100644 index 438087c29..000000000 --- a/docs/features/mcp-settings-status-bar/spec.md +++ /dev/null @@ -1,18 +0,0 @@ -# MCP Settings Status Bar - -## User Story - -As a user managing MCP servers, I want the MCP settings page to spend less space on summary cards and filters, so the server list remains the main focus. - -## Acceptance Criteria - -- The top MCP summary cards are removed. -- Running, built-in, custom, and total server counts are displayed in a compact bottom status bar. -- The NPM mirror/source can be viewed and edited from the far side of the bottom status bar. -- Server filters are limited to All, Running, and Stopped. -- The top MCP market entry and global MCP toggle remain available. - -## Non-goals - -- No changes to MCP server lifecycle behavior. -- No changes to NPM mirror persistence or validation behavior. diff --git a/docs/features/mcp-settings-status-bar/tasks.md b/docs/features/mcp-settings-status-bar/tasks.md deleted file mode 100644 index e71562754..000000000 --- a/docs/features/mcp-settings-status-bar/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Move MCP summary and NPM mirror into a compact bottom status bar. -- [x] Reduce MCP filters to All, Running, and Stopped. -- [x] Update tests. -- [x] Run validation commands. diff --git a/docs/features/message-toolbar-actions/plan.md b/docs/features/message-toolbar-actions/plan.md deleted file mode 100644 index 3525e0062..000000000 --- a/docs/features/message-toolbar-actions/plan.md +++ /dev/null @@ -1,90 +0,0 @@ -# MessageToolbar 功能补齐实施计划(不含 Trace 落库) - -## 1. 关键决策 - -1. 新页面消息操作以 `agentSessionPresenter` 为唯一目标接口。 -2. 新架构下线 variants,不再做版本切换状态维护。 -3. 交付拆两阶段: - - Phase 1:前端接线、显隐与禁用策略。 - - Phase 2:补齐后端 API 并完成动作闭环。 -4. Trace 设计独立推进,不阻塞 toolbar 主流程。 - -## 2. 组件与状态改造范围 - -### 渲染层 - -1. `src/renderer/src/components/chat/MessageList.vue` - - 补齐 toolbar 事件透传与 action handler。 -2. `src/renderer/src/components/message/MessageToolbar.vue` - - 引入统一 capabilities/disabled 输入,移除 variants UI。 -3. `src/renderer/src/components/message/MessageItemUser.vue` - - 从旧 `sessionPresenter` 编辑链路切到新消息操作接口(Phase 2)。 -4. `src/renderer/src/components/message/MessageItemAssistant.vue` - - 从旧 `chatStore` 动作切到新消息操作接口(Phase 2)。 -5. `src/renderer/src/pages/ChatPage.vue` - - 汇总生成态/待交互态,向 MessageList 提供禁用控制。 - -### 状态层 - -1. `src/renderer/src/stores/ui/message.ts` - - 增加消息操作后的本地刷新策略(optimistic + reload 边界)。 -2. `src/renderer/src/stores/ui/session.ts` - - Fork 后会话激活与页面切换策略。 - -## 3. 新 API 设计(Phase 2) - -在 `IAgentSessionPresenter` 增加: - -1. `editUserMessage(sessionId: string, messageId: string, newText: string): Promise` -2. `retryFromUserMessage(sessionId: string, messageId: string): Promise` -3. `retryFromAssistantMessage(sessionId: string, messageId: string): Promise` -4. `deleteMessage(sessionId: string, messageId: string): Promise` -5. `forkSessionFromMessage(sessionId: string, messageId: string): Promise` - -后端语义: - -1. `editUserMessage`:更新该用户消息文本,删除其后消息,自动触发生成。 -2. `retryFromUserMessage`:删除该消息后的全部消息,触发重新生成。 -3. `retryFromAssistantMessage`:定位关联上下文边界,删除其后消息并重生成。 -4. `deleteMessage`:仅删除当前消息,不级联删除其他消息。 -5. `forkSessionFromMessage`:复制到目标 assistant(含本条)并返回新 `sessionId`。 - -## 4. 交互与禁用规则 - -1. 当会话 `isGenerating` 或有 pending tool interaction 时: - - 禁用:`edit/retry/delete/fork` - - 可用:`copy/copyImage` -2. 用户编辑模式下: - - `save` 与 `cancel` 保持可用。 - - 其他动作隐藏或禁用,防止状态竞争。 -3. 复制图片语义不变:短按当前组,长按从顶部到当前。 - -## 5. 数据兼容与迁移策略 - -1. 新接口落地前,Phase 1 中所有未闭环按钮必须降级处理(隐藏或 disabled),禁止 no-op。 -2. 不改历史消息存储结构,仅新增消息操作入口。 -3. 不引入 variants 兼容层,避免双语义并存。 - -## 6. 测试策略 - -### Main - -1. `editUserMessage`:截断范围、重生触发、异常回滚。 -2. `retryFromUserMessage` / `retryFromAssistantMessage`:边界与顺序正确。 -3. `deleteMessage`:仅当前消息删除,不影响相邻消息。 -4. `forkSessionFromMessage`:复制到目标点(含本条)并保持新旧会话隔离。 - -### Renderer - -1. toolbar 在生成中禁用策略正确。 -2. 用户编辑流程(进入/保存/取消)状态正确。 -3. user/assistant retry 触发正确 API。 -4. Fork 成功后页面切换到新会话。 - -## 7. 风险与回退 - -1. 风险:新旧链路并存导致行为漂移。 - - 缓解:新页面只读写新接口,Phase 1 明确禁用未闭环动作。 -2. 风险:消息截断边界错误导致上下文污染。 - - 缓解:Main 层单测覆盖多轮消息结构。 -3. 回退:保留旧组件分支,必要时通过 feature flag 关闭新 toolbar 动作映射。 diff --git a/docs/features/message-toolbar-actions/spec.md b/docs/features/message-toolbar-actions/spec.md deleted file mode 100644 index 353164dd0..000000000 --- a/docs/features/message-toolbar-actions/spec.md +++ /dev/null @@ -1,102 +0,0 @@ -# MessageToolbar 功能补齐规格(不含 Trace 落库) - -## 概述 - -本规格定义新架构会话页(`ChatPage + stores/ui/* + agentSessionPresenter`)下的 `MessageToolbar` 功能补齐范围,目标是让按钮行为与产品语义一致,避免“可见但不可用/行为不一致”。 - -本规格**不包含 Trace 数据落库设计**,Trace 由独立规格处理:`docs/features/message-trace-storage/`。 - -## 背景与目标 - -1. 新 UI 主链路已迁移到 `agentSessionPresenter + agentRuntimePresenter`,但 `MessageToolbar` 仍大量依赖旧 `chatStore/sessionPresenter` 语义。 -2. 当前按钮存在“新旧链路行为不一致”与“有 UI 无闭环”的问题。 -3. 需要明确分阶段交付:先修前端行为与禁用策略,再补后端消息操作 API。 - -## 用户故事 - -### US-1:用户消息可编辑并重生 - -作为用户,我希望编辑历史用户消息后,系统从该点重建后续回答,保证上下文一致。 - -### US-2:用户/助手重试一致 - -作为用户,我希望重试是“截断后重生成”,而不是 variants 或纯追加。 - -### US-3:删除是可预期的硬删除 - -作为用户,我希望删除仅影响当前消息本身,界面直接移除,不出现“已删除占位”。 - -### US-4:分支语义稳定 - -作为用户,我希望 Fork 从当前 assistant 消息(含本条)切分,并自动切换到新会话继续。 - -### US-5:运行中安全操作约束 - -作为用户,我希望生成中只能做安全操作(复制/截图),高风险操作自动禁用,避免状态混乱。 - -## 功能需求 - -### A. 架构范围 - -- [ ] 新会话页链路仅使用 `stores/ui/session.ts`、`stores/ui/message.ts`、`agentSessionPresenter`。 -- [ ] 不再依赖旧 `chatStore` 作为新页面消息操作入口。 -- [ ] 旧页面链路可保留兼容,但不作为新页面实现基线。 - -### B. 按钮与行为矩阵 - -#### 用户消息 - -- [ ] `copy`:复制当前用户消息文本。 -- [ ] `edit/save/cancel`:进入编辑、保存、取消。 -- [ ] `retry`:从该用户消息截断后重生成。 -- [ ] `delete`:仅删除当前消息(硬删除)。 - -#### Assistant 消息 - -- [ ] `copy`:复制 assistant 文本(含既有 CoT 开关策略)。 -- [ ] `copy image`: - - 短按:当前消息组截图。 - - 长按:从顶部到当前消息截图。 -- [ ] `retry`:从该 assistant 对应上下文截断后重生成。 -- [ ] `fork`:包含当前 assistant 消息并切换到新会话。 -- [ ] `delete`:仅删除当前消息(硬删除)。 - -### C. 语义约束 - -- [ ] variants 在新架构下线:不显示上一版/下一版按钮,不维护 selectedVariants。 -- [ ] 生成中或待交互中,禁用高风险动作:`edit/retry/delete/fork`。 -- [ ] 生成中保留低风险动作:`copy/copy image`。 - -### D. 分阶段交付 - -- [ ] Phase 1(前端先行): - - 补齐新页面 toolbar 事件接线。 - - 做可见性/禁用/降级策略,确保无空操作。 -- [ ] Phase 2(前后端闭环): - - 新增消息操作 IPC/API(编辑、重试、删除、fork)。 - - 新页面动作切换到新 API。 - -### E. 验收标准 - -- [ ] 新页面中不出现 variants UI。 -- [ ] 用户编辑保存后,后续消息按规则截断并自动重生成。 -- [ ] 用户/助手重试都执行“截断后重生”而非 variants。 -- [ ] 删除仅移除当前消息,不保留占位文案。 -- [ ] Fork 从当前 assistant(含本条)创建新会话并自动切换。 -- [ ] 生成中禁用高风险动作,复制和截图仍可用。 - -## 非目标 - -1. 本规格不定义 Trace 数据结构与 provider 采集实现。 -2. 本规格不恢复 variants 机制。 -3. 本规格不做 Message UI 视觉重设计。 - -## 约束 - -1. 维持 Presenter 架构,避免新链路反向依赖旧 `chatStore`。 -2. 用户可见文案必须走 i18n。 -3. 对话语义修改需保证历史数据兼容(不破坏既有消息读取)。 - -## 开放问题 - -无。 diff --git a/docs/features/message-toolbar-actions/tasks.md b/docs/features/message-toolbar-actions/tasks.md deleted file mode 100644 index cdcc3a348..000000000 --- a/docs/features/message-toolbar-actions/tasks.md +++ /dev/null @@ -1,41 +0,0 @@ -# MessageToolbar 功能补齐任务清单(不含 Trace 落库) - -## T0 规格文档 - -- [x] 创建 `docs/features/message-toolbar-actions/spec.md` -- [x] 创建 `docs/features/message-toolbar-actions/plan.md` -- [x] 创建 `docs/features/message-toolbar-actions/tasks.md` - -## T1 Phase 1:前端接线与降级 - -- [ ] `ChatPage/MessageList` 补齐 toolbar 事件透传 -- [ ] `MessageToolbar` 增加统一 `capabilities/disabled` 输入 -- [ ] 下线 variants 按钮与相关文案 -- [ ] 生成中高风险动作禁用策略落地 -- [ ] 未闭环动作在新页面做隐藏/禁用,杜绝 no-op - -## T2 Phase 2:新后端消息操作 API - -- [ ] `IAgentSessionPresenter` 增加消息操作接口定义 -- [ ] `agentSessionPresenter` 增加消息操作代理实现 -- [ ] `agentRuntimePresenter` 实现编辑/重试/删除/fork 语义 -- [ ] MessageStore/SQLite 层补齐对应读写方法 - -## T3 渲染层动作闭环 - -- [ ] `MessageItemUser` 切换到新 `edit/retry/delete` 接口 -- [ ] `MessageItemAssistant` 切换到新 `retry/delete/fork` 接口 -- [ ] 保留 `copy/copyImage` 既有语义与反馈 - -## T4 测试 - -- [ ] Main:消息操作语义单测(截断、重生、fork、删除边界) -- [ ] Renderer:toolbar 显隐/禁用与事件派发单测 -- [ ] Renderer:编辑流程与 retry/fork 流程单测 - -## T5 质量门禁 - -- [ ] `pnpm run format` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] 运行相关测试并记录结果 diff --git a/docs/features/message-trace-storage/plan.md b/docs/features/message-trace-storage/plan.md deleted file mode 100644 index f539e295f..000000000 --- a/docs/features/message-trace-storage/plan.md +++ /dev/null @@ -1,112 +0,0 @@ -# Message Trace 独立落库实施计划 - -## 1. 关键决策 - -1. Trace 从“历史反推”改为“provider 请求侧采集”。 -2. Trace 与 Toolbar 主功能拆分为独立发布项。 -3. 同一消息允许多条 trace,默认展示最新。 -4. 仅存脱敏数据;mask 策略保留敏感值尾 4 位。 - -## 2. 数据层设计 - -### 2.1 新表 - -新增 `deepchat_message_traces`: - -1. 主键:`id` -2. 关联字段:`message_id`, `session_id` -3. 请求标识:`request_seq` -4. 请求信息:`provider_id`, `model_id`, `endpoint`, `headers_json`, `body_json` -5. 控制字段:`truncated` -6. 时间字段:`created_at` - -索引: - -1. `idx_trace_message_seq (message_id, request_seq DESC)` -2. `idx_trace_session_time (session_id, created_at DESC)` - -### 2.2 迁移 - -1. 增加新 Table 类并纳入 `SQLitePresenter` 初始化。 -2. 迁移版本提升至新版本(高于当前最大版本)。 -3. 删除会话/消息时同步清理 trace(应用层或 FK 级联二选一,默认应用层显式删除)。 - -## 3. 采集链路设计 - -### 3.1 Trace 上下文 - -在 `agentRuntimePresenter.runStreamForMessage` 构建 trace context: - -1. `sessionId` -2. `messageId` -3. `providerId` -4. `modelId` -5. `traceEnabled`(读取 `traceDebugEnabled`) -6. `persistTrace(payload)` 回调 - -### 3.2 Provider 接入 - -1. 为 provider 请求流程增加统一 trace hook(在最终请求参数生成后、网络请求发出前)。 -2. wrapper provider(如 `dashscope/doubao/siliconcloud`)复用父类 hook 并透传上下文。 -3. 非 OpenAI 系 provider(Anthropic/Gemini/Ollama/Vertex/AWSBedrock/Copilot/VoiceAI/ACP)分别在其请求构造点调用 hook。 - -### 3.3 写库顺序 - -1. provider 构建最终 `endpoint/headers/body`。 -2. 调用脱敏器生成可落库 payload。 -3. 计算大小并按 512KB 限制截断。 -4. 写入 `deepchat_message_traces` 并分配 `request_seq`。 - -## 4. 安全与脱敏 - -1. 扩展 `src/main/lib/redact.ts`: - - 新增“尾 4 位保留”掩码能力。 - - Header/Body 的敏感键统一进入掩码策略。 -2. 数据库只接收脱敏后对象。 -3. 日志中禁止输出未脱敏请求体。 - -## 5. 查询与 UI 接口 - -### 5.1 IPC / Presenter - -在 `IAgentSessionPresenter` 增加: - -1. `listMessageTraces(messageId: string): Promise` -2. `getMessageTraceCount(messageId: string): Promise`(可选;也可在消息查询时聚合) - -### 5.2 消息列表可见性 - -1. `getMessages(sessionId)` 返回 `traceCount`(推荐),避免逐条查询。 -2. 渲染层 `MessageToolbar` 根据 `traceCount > 0` 控制 Trace 按钮显隐。 - -### 5.3 TraceDialog - -1. 打开时获取该消息 trace 列表。 -2. 默认选中 `request_seq` 最大的一条。 -3. 提供历史 trace 轮次切换。 - -## 6. 测试策略 - -### Main - -1. 表结构与迁移测试(建表、索引、版本升级)。 -2. 脱敏测试(尾 4 位保留、生效键覆盖、无明文泄露)。 -3. provider trace hook 触发测试(开启/关闭 traceDebugEnabled)。 -4. 大 payload 截断测试(`truncated=true`)。 -5. 消息/会话删除时 trace 清理测试。 - -### Renderer - -1. Trace 按钮显隐测试(有/无 traceCount)。 -2. TraceDialog 默认最新条目与历史切换测试。 -3. 接口错误态(空数据、查询失败)展示测试。 - -## 7. 风险与回退 - -1. 风险:多 provider 接入点分散,易漏采。 - - 缓解:建立 provider 接入清单并加覆盖测试。 -2. 风险:trace 体积膨胀影响 DB。 - - 缓解:512KB 上限 + 截断标记 + 索引控制。 -3. 风险:敏感数据泄漏。 - - 缓解:统一脱敏模块、落库前强制调用、测试校验。 -4. 回退:可通过 `traceDebugEnabled` 全局关闭采集,UI 自动隐藏按钮。 diff --git a/docs/features/message-trace-storage/spec.md b/docs/features/message-trace-storage/spec.md deleted file mode 100644 index f99dd296f..000000000 --- a/docs/features/message-trace-storage/spec.md +++ /dev/null @@ -1,96 +0,0 @@ -# Message Trace 独立落库规格 - -## 概述 - -新增 Message Trace 独立存储链路: - -1. 不再使用“从历史消息反推请求”的 trace 方式。 -2. 改为在各 provider 发起真实请求前,直接采集最终请求数据(endpoint/headers/body)。 -3. trace 按消息维度持久化,消息上 `Trace` 按钮仅在存在 trace 数据时展示。 - -## 背景与目标 - -1. 旧 preview 逻辑基于历史重建,可能与实际提交给服务端的数据不一致。 -2. 需要可审计、可复现的真实请求快照,同时满足安全脱敏要求。 -3. 需要把 Trace 与 MessageToolbar 主功能拆开,独立迭代、独立上线。 - -## 用户故事 - -### US-1:真实请求可追踪 - -作为开发/调试用户,我希望看到真正发往 provider 的请求内容,而不是推断结果。 - -### US-2:消息级 Trace 可见性 - -作为用户,我希望只有有 trace 的消息显示 Trace 按钮,避免空弹窗。 - -### US-3:安全可用 - -作为用户,我希望 trace 可调试但不泄露密钥,token 需做 mask。 - -## 功能需求 - -### A. 采集时机与来源 - -- [ ] 采集点位于 provider 内部“最终请求参数已构建、实际发请求之前”。 -- [ ] 仅当 `traceDebugEnabled = true` 时采集并落库。 -- [ ] 每次真实请求都记录一条 trace(同一消息可多条)。 - -### B. 数据模型 - -- [ ] 新增 `deepchat_message_traces`(独立表)。 -- [ ] 一条 assistant 消息允许多条 trace,按 `request_seq` 递增。 -- [ ] 存储字段至少包含: - - `message_id` - - `session_id` - - `provider_id` - - `model_id` - - `request_seq` - - `endpoint` - - `headers_json` - - `body_json` - - `truncated` - - `created_at` - -### C. 脱敏与体积控制 - -- [ ] 仅存脱敏结果,不落明文敏感值。 -- [ ] token/key 掩码策略:保留尾部 4 位(其余掩码)。 -- [ ] 单条 trace 上限 512KB;超出截断并写 `truncated = true`。 - -### D. 查询与展示 - -- [ ] 新接口支持按 `messageId` 查询 trace 列表(按 `request_seq DESC`)。 -- [ ] 消息列表返回 `traceCount`(或等价可判断字段)。 -- [ ] Toolbar 显示规则:`traceDebugEnabled && traceCount > 0`。 -- [ ] TraceDialog 默认展示最新 trace,并支持切换历史 trace。 - -### E. 生命周期 - -- [ ] 删除消息时级联删除对应 trace。 -- [ ] 删除会话时级联删除该会话 trace。 -- [ ] 不做历史数据回填,仅覆盖新请求。 - -### F. 验收标准 - -- [ ] 同一消息多轮请求可看到多条 trace 记录。 -- [ ] Trace 内容与真实请求参数一致(不依赖历史重建)。 -- [ ] 数据库存储中不存在明文 API Key/Token。 -- [ ] 大 payload 被截断并带 `truncated=true`。 -- [ ] 无 trace 的消息不显示 Trace 按钮。 - -## 非目标 - -1. 不回填旧历史消息 trace。 -2. 不提供 trace 导出/分享能力。 -3. 不改动非 trace 场景的 MessageToolbar 交互。 - -## 约束 - -1. 保持现有 Presenter + SQLite 架构。 -2. 采集必须是 provider 内真实请求路径,不使用“重建推断”。 -3. 性能优先:trace 关闭时不引入额外重型逻辑。 - -## 开放问题 - -无。 diff --git a/docs/features/message-trace-storage/tasks.md b/docs/features/message-trace-storage/tasks.md deleted file mode 100644 index 34cc68569..000000000 --- a/docs/features/message-trace-storage/tasks.md +++ /dev/null @@ -1,55 +0,0 @@ -# Message Trace 独立落库任务清单 - -## T0 规格文档 - -- [x] 创建 `docs/features/message-trace-storage/spec.md` -- [x] 创建 `docs/features/message-trace-storage/plan.md` -- [x] 创建 `docs/features/message-trace-storage/tasks.md` - -## T1 数据层 - -- [x] 新增 `deepchat_message_traces` Table 类 -- [x] 在 `SQLitePresenter` 注册新表与迁移版本 -- [x] 增加按消息查询、插入、删除方法 -- [x] 增加消息/会话删除时 trace 清理逻辑 - -## T2 类型与接口 - -- [x] 新增 `MessageTraceRecord` 共享类型 -- [x] `IAgentSessionPresenter` 增加 trace 查询接口 -- [x] `agentSessionPresenter` 增加 trace 查询代理实现 - -## T3 脱敏与截断 - -- [x] 扩展 `redact.ts` 支持“尾 4 位保留”策略 -- [x] 对 headers/body 统一应用脱敏 -- [x] 实现 512KB 截断与 `truncated` 标记 - -## T4 Provider 采集接入 - -- [x] 定义统一 trace hook/context -- [x] OpenAICompatible/OpenAIResponses 接入真实请求采集 -- [x] Anthropic/Gemini/Ollama/Vertex/AWSBedrock/Copilot/VoiceAI/ACP 接入采集 -- [x] 包装型 provider 透传上下文并复用父类采集 -- [x] trace 开关关闭时零写库 - -## T5 渲染层展示 - -- [x] 消息查询补充 `traceCount`(或等价字段) -- [x] `MessageToolbar` 仅在 `traceCount>0 && traceDebugEnabled` 显示 Trace -- [x] `TraceDialog` 改为按消息查询 trace 列表 -- [x] 默认展示最新条并支持历史切换 - -## T6 测试 - -- [x] Main:建表迁移、写入、查询、删除级联 -- [x] Main:脱敏与截断测试 -- [x] Main:各 provider 采集触发测试 -- [x] Renderer:按钮显隐与 TraceDialog 切换测试 - -## T7 质量门禁 - -- [x] `pnpm run format` -- [x] `pnpm run lint` -- [x] `pnpm run typecheck` -- [x] 运行相关测试并记录结果 diff --git a/docs/features/mistral-provider-support/plan.md b/docs/features/mistral-provider-support/plan.md deleted file mode 100644 index b20502e1d..000000000 --- a/docs/features/mistral-provider-support/plan.md +++ /dev/null @@ -1,19 +0,0 @@ -# Mistral Provider Support Plan - -## Runtime - -- Add `mistral` to `DEFAULT_PROVIDERS` with `apiType: "mistral"`, default base URL `https://api.mistral.ai/v1`, Mistral website links, and disabled default state. -- Register `mistral` in `providerRegistry` as an OpenAI-compatible AI SDK provider. -- Use provider DB model metadata for model list refreshes and use `generate-text` verification with `mistral-small-latest`. - -## Renderer And Deeplinks - -- Add `mistral` to provider DB-backed refresh hints. -- Add `mistral` to provider install custom types and the manual deeplink playground. -- Wire `ModelIcon.vue` to the existing Mistral color SVG. -- Expose Mistral AI in the custom provider API type select. - -## Compatibility - -- Existing users keep their stored provider settings. The provider helper appends the new default if it is missing. -- Existing custom providers with id `mistral` are not overwritten by migration code. diff --git a/docs/features/mistral-provider-support/spec.md b/docs/features/mistral-provider-support/spec.md deleted file mode 100644 index 0f402fd02..000000000 --- a/docs/features/mistral-provider-support/spec.md +++ /dev/null @@ -1,20 +0,0 @@ -# Mistral Provider Support Spec - -## User Story - -Users can enable Mistral AI from the built-in Model Providers list, enter a Mistral API key, refresh models, verify the provider, and use Mistral chat and vision-capable models without editing provider files or creating a custom OpenAI-compatible provider. - -## Acceptance Criteria - -- A disabled built-in provider with id `mistral` appears in default provider settings. -- The provider uses `https://api.mistral.ai/v1` as its default base URL and Bearer API key authentication. -- Mistral uses the existing OpenAI-compatible runtime with no new SDK dependency. -- Refreshing models maps existing provider DB metadata for Mistral, including vision, tool call, reasoning, context, and output limits. -- Provider verification sends a small generate-text request to `mistral-small-latest`. -- Provider install deeplinks support built-in `id: "mistral"` and custom `type: "mistral"`. - -## Non-Goals - -- Add a dedicated Mistral SDK package. -- Add new IPC routes or renderer APIs. -- Change existing custom provider behavior beyond allowing `mistral` as a supported type. diff --git a/docs/features/mistral-provider-support/tasks.md b/docs/features/mistral-provider-support/tasks.md deleted file mode 100644 index 0c9addb58..000000000 --- a/docs/features/mistral-provider-support/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mistral Provider Support Tasks - -- [x] Add SDD spec, plan, and task documents. -- [x] Add built-in Mistral provider metadata. -- [x] Register Mistral in the AI SDK provider registry. -- [x] Add renderer, deeplink, icon, and manual playground wiring. -- [x] Add targeted tests for provider metadata, runtime mapping, verification, and icon/deeplink support. -- [x] Run formatting, i18n, lint, typecheck, and targeted tests. diff --git a/docs/features/model-top-p-settings/plan.md b/docs/features/model-top-p-settings/plan.md deleted file mode 100644 index 393230587..000000000 --- a/docs/features/model-top-p-settings/plan.md +++ /dev/null @@ -1,38 +0,0 @@ -# Implementation Plan - -## Approach - -Implement `topP` as an optional generation setting, mirroring the existing session generation settings pipeline while avoiding a forced default. - -## Affected Interfaces - -- `src/shared/types/agent-interface.d.ts`: add optional `topP` to `SessionGenerationSettings`. -- `src/shared/contracts/common.ts`: add optional `topP` to route schemas. -- `src/shared/utils/generationSettingsValidation.ts`: validate `topP` as a finite number in `[0.1, 1]`. -- `src/main/presenter/sqlitePresenter/tables/deepchatSessions.ts`: add `top_p` storage and migration. -- `src/main/presenter/agentRuntimePresenter/index.ts`: sanitize, persist, and pass `topP` through runtime model config. -- `src/main/presenter/llmProviderPresenter/aiSdk/runtime.ts`: include `topP` in AI SDK `generateText` and `streamText` calls and request traces when defined. -- `src/renderer/src/stores/ui/draft.ts`: carry draft `topP` overrides for new sessions. -- `src/renderer/src/components/chat/ChatStatusBar.vue`: show and persist the compact `topP` control. -- i18n `chat.json` and `settings.json` files: add `topP` label, hover description, and validation. -- `topP` number inputs use min `0.1`, max `1`, and step `0.1` so values align with common AI SDK/provider constraints. - -## Data Flow - -1. User edits `topP` in ChatStatusBar. -2. Draft sessions store it in Pinia; active sessions call `sessions.updateGenerationSettings`. -3. Main process validates and stores `topP` as part of session generation settings. -4. Agent runtime adds `topP` to runtime `ModelConfig`. -5. AI SDK runtime includes `topP` only when defined. - -## Compatibility - -- Existing databases migrate by adding nullable `top_p`. -- Existing sessions return no `topP` unless previously set. -- Requests without `topP` preserve current behavior. - -## Test Strategy - -- Run formatting and i18n generation required by repository guidelines. -- Run lint to catch type/schema/template issues. -- Prefer focused type/lint validation over provider integration tests because this is a pass-through parameter. diff --git a/docs/features/model-top-p-settings/spec.md b/docs/features/model-top-p-settings/spec.md deleted file mode 100644 index 006540853..000000000 --- a/docs/features/model-top-p-settings/spec.md +++ /dev/null @@ -1,36 +0,0 @@ -# Model Top P Settings - -## User Need - -Users need to adjust `top_p` for chat models because many upstream model APIs support nucleus sampling and expose it as a generation parameter. - -## Goal - -Add an optional per-session `topP` generation setting for text chat requests and pass it through to AI SDK text generation when the user explicitly sets it. - -## Acceptance Criteria - -- Users can set `topP` from the chat model advanced settings panel for regular text chat models. -- `topP` accepts values greater than or equal to 0.1 and less than or equal to 1. -- The Top P UI uses the plain label "Top P" and moves explanatory copy into a hover help icon. -- Existing conversations and new sessions continue to work when no `topP` is set. -- `topP` persists with DeepChat session generation settings and survives app restart. -- Text `generateText` and streaming requests pass `topP` to AI SDK only when it is defined. -- Existing Voice.ai TTS `topP` configuration remains independent. - -## Constraints - -- Follow current typed route/contracts and presenter boundaries. -- Use `topP` internally and let SDK/provider layers map provider payload details. -- Do not default-send `topP: 1`; omit the parameter when unset to preserve provider defaults. -- Use i18n keys for all user-facing strings. -- SQLite schema migration must be backward compatible. - -## Non-Goals - -- Provider-specific `top_p` compatibility matrices. -- Building a full Provider DB `top_p` capability matrix. - -## Open Questions - -- None. diff --git a/docs/features/model-top-p-settings/tasks.md b/docs/features/model-top-p-settings/tasks.md deleted file mode 100644 index af489579a..000000000 --- a/docs/features/model-top-p-settings/tasks.md +++ /dev/null @@ -1,10 +0,0 @@ -# Tasks - -- [x] Create SDD artifacts for optional model `topP` setting. -- [x] Add shared types, contracts, and validation support for `topP`. -- [x] Add SQLite session persistence and migration for `topP`. -- [x] Propagate `topP` through runtime model config and AI SDK calls. -- [x] Add renderer draft state and advanced settings UI for `topP`. -- [x] Add i18n strings for `topP` controls and validation. -- [x] Refine Top P UI to use a help tooltip and `[0.1, 1]` number input range. -- [x] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/ollama-model-selection/plan.md b/docs/features/ollama-model-selection/plan.md deleted file mode 100644 index 1f935638c..000000000 --- a/docs/features/ollama-model-selection/plan.md +++ /dev/null @@ -1,48 +0,0 @@ -# Ollama 模型可选性与跨窗口同步实施计划 - -## 1. 关键决策 - -1. 真源统一到 main/config 持久层,renderer `ollamaStore` 只保留 UI 状态。 -2. Ollama provider 刷新时同时采集 `listModels()` 与 `listRunningModels()` 并合并。 -3. 新发现模型通过 `ensureModelStatus(..., true)` 默认启用,但不覆盖已有显式状态。 -4. 本次只做 SDK 审计,不升级 `ollama` 依赖版本。 - -## 2. main/config 设计 - -### 2.1 状态语义 - -1. 在 `ModelStatusHelper` 增加 `ensureModelStatus`。 -2. 仅当状态尚未存在时写入默认值。 -3. 直接写存储并更新 cache,不发送 `MODEL_STATUS_CHANGED`。 - -### 2.2 Ollama provider 列表构建 - -1. `fetchProviderModels()` 并行获取本地模型与运行中模型。 -2. 使用 `model.name` 合并,优先保留本地模型主体字段。 -3. 合并 `capabilities`、`model_info` 和已有缓存能力元数据。 -4. 生成 `MODEL_META` 后写入 config provider models。 - -## 3. renderer 刷新链路 - -1. `ollamaStore.refreshOllamaModels(providerId)` 先更新设置页本地/运行中列表。 -2. 随后调用 `llmP.refreshModels(providerId)` 让 main 重建持久化目录。 -3. 当前窗口再调用 `modelStore.refreshProviderModels(providerId)` 收敛显示。 -4. pull 成功事件复用同一刷新链路。 - -## 4. 回归点 - -1. 删除 `modelStore.updateModelStatus()` 中对 Ollama 的提前返回,确保显式启停会落到 config。 -2. 聊天侧继续依赖 `modelStore.enabledModels`,不增加临时兼容分支。 - -## 5. 测试策略 - -### Main - -1. `ModelStatusHelper.ensureModelStatus` 不覆盖显式关闭状态。 -2. `OllamaProvider.fetchModels()` 合并本地与运行中模型并保留能力元数据。 - -### Renderer - -1. `ollamaStore.refreshOllamaModels()` 会调用 `llmP.refreshModels()` 与 `modelStore.refreshProviderModels()`。 -2. pull 成功事件会触发同样的刷新链路。 -3. `ChatStatusBar`、`ModelSelect`、`ModelChooser` 显示 Ollama chat 模型并过滤 Ollama embedding 模型。 diff --git a/docs/features/ollama-model-selection/spec.md b/docs/features/ollama-model-selection/spec.md deleted file mode 100644 index 8e1e64dc3..000000000 --- a/docs/features/ollama-model-selection/spec.md +++ /dev/null @@ -1,71 +0,0 @@ -# Ollama 模型可选性与跨窗口同步规格 - -## 概述 - -修复 Ollama 模型在设置页可见但在聊天状态栏、模型选择器等入口不可选的问题,并统一模型目录真源到 main/config 持久层。 - -## 背景与目标 - -1. 当前设置窗口通过 `ollamaStore` 的本地临时状态直接拼装可选模型,和主聊天窗口依赖的 `modelStore`/config 真源不一致。 -2. 运行中的 Ollama 模型没有稳定进入持久化目录,导致跨窗口、刷新后、重启后行为不一致。 -3. 新发现的本地/运行中模型需要默认可选,但必须保留用户显式关闭的结果。 - -## 用户故事 - -### US-1:聊天入口可选 - -作为用户,我希望在设置页刷新出 Ollama 模型后,不用重启聊天窗口就能在 `ChatStatusBar` 和其他模型选择器中选中它们。 - -### US-2:运行中模型可见 - -作为用户,我希望仅存在于“运行中模型”列表里的 Ollama 模型也能进入可选目录,而不是只认本地模型列表。 - -### US-3:显式关闭可保留 - -作为用户,我希望手动关闭某个 Ollama 模型后,后续刷新不会把它重新强制打开。 - -## 功能需求 - -### A. 真源统一 - -- [ ] Ollama 可选模型目录以 main/config 持久层为准。 -- [ ] renderer `ollamaStore` 只维护设置页 UI 状态,不再直接改写全局模型真源。 - -### B. 模型合并 - -- [ ] Ollama provider 刷新时合并 `本地模型 ∪ 运行中模型`。 -- [ ] 以 `model.name` 去重。 -- [ ] 本地模型和运行中模型同名时,以本地模型为主,补齐运行中模型额外信息。 -- [ ] 运行中-only 模型也进入持久化 provider model 列表。 - -### C. 元数据保真 - -- [ ] 合并结果保留 `type/contextLength/vision/functionCall/reasoning` 等关键能力字段。 -- [ ] 嵌入模型继续标记为 `embedding`,聊天选择器仍应过滤掉它们。 - -### D. 默认启用语义 - -- [ ] 新发现的 Ollama 本地/运行中模型默认设为可选。 -- [ ] 已有显式状态时不得覆盖,尤其是用户已关闭的模型。 -- [ ] 该默认启用写入不发送逐模型状态变更事件。 - -### E. 刷新链路 - -- [ ] 设置页手动刷新、初始化、pull 成功后都走同一条刷新链路: - 1. 拉取本地/运行中模型更新设置页 UI。 - 2. 调 main `refreshModels(providerId)` 重建持久化目录。 - 3. 当前窗口刷新 `modelStore.refreshProviderModels(providerId)`。 - -## 非目标 - -1. 本次不升级 `ollama` SDK。 -2. 不改 Ollama 设置页“运行中的模型 / 本地模型”分区 UI。 -3. 不新增用户可见配置项。 - -## 验收标准 - -- [ ] 设置页刷新 Ollama 后,聊天窗口模型选择器立即能看到新的 chat 模型。 -- [ ] 运行中-only 的 Ollama 模型能被选中发起会话。 -- [ ] Ollama embedding 模型不会出现在聊天模型列表中。 -- [ ] 用户关闭某个 Ollama 模型后,刷新不会重新启用它。 -- [ ] `package.json` 中 `ollama` 版本保持不变。 diff --git a/docs/features/ollama-model-selection/tasks.md b/docs/features/ollama-model-selection/tasks.md deleted file mode 100644 index 0d6bb371e..000000000 --- a/docs/features/ollama-model-selection/tasks.md +++ /dev/null @@ -1,11 +0,0 @@ -# Ollama 模型可选性与跨窗口同步任务拆分 - -- [x] 为 config/model status 增加 `ensureModelStatus` 语义。 -- [x] 调整 Ollama provider 的模型抓取逻辑,合并本地与运行中模型。 -- [x] 调整 renderer `ollamaStore`,改为 UI 状态 + 主进程刷新链路。 -- [x] 移除 Ollama 模型状态更新的本地短路逻辑,保证显式关闭可持久化。 -- [x] 补充 main/store/component 回归测试。 -- [x] 运行 `pnpm run format` -- [x] 运行 `pnpm run i18n` -- [x] 运行 `pnpm run lint` -- [x] 运行相关测试并确认通过 diff --git a/docs/features/openai-compatible-video-generation/plan.md b/docs/features/openai-compatible-video-generation/plan.md deleted file mode 100644 index 37b2c5b4f..000000000 --- a/docs/features/openai-compatible-video-generation/plan.md +++ /dev/null @@ -1,42 +0,0 @@ -# Plan - -## Approach -Treat video generation as a first-class model capability parallel to image generation and TTS: -- Extend shared model/type enums and model-db parsing to include `videoGeneration`. -- Add a shared video compatibility helper that can recover video intent from model metadata, endpoint hints, modalities, or known model ID patterns when upstream data is incomplete. -- Add an OpenAI-compatible video runtime path that sends requests to `/v1/videos`, normalizes provider responses, and emits media output into the assistant stream. -- Reuse the current assistant media block transport by carrying video payloads through the existing message block structure with video MIME detection on the renderer side. - -## Affected Areas -- Shared types/contracts: - - `src/shared/model.ts` - - `src/shared/types/model-db.ts` - - `src/shared/types/presenters/llmprovider.presenter.d.ts` - - `src/shared/types/presenters/legacy.presenters.d.ts` - - `src/shared/videoGenerationSettings.ts` (new) -- Main runtime/provider: - - `src/main/presenter/configPresenter/index.ts` - - `src/main/presenter/configPresenter/modelConfig.ts` - - `src/main/presenter/llmProviderPresenter/index.ts` - - `src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts` - - `src/main/presenter/llmProviderPresenter/aiSdk/runtime.ts` -- Renderer: - - `src/renderer/src/composables/useModelTypeDetection.ts` - - `src/renderer/src/components/chat/messageListItems.ts` - - `src/renderer/src/components/message/MessageItemAssistant.vue` - - `src/renderer/src/components/message/MessageBlockVideo.vue` (new) - - `src/renderer/settings/components/ProviderModelList.vue` -- Model DB: - - `resources/model-db/providers.json` - -## Compatibility -- Existing text, image, and TTS paths remain unchanged. -- Existing assistant block persistence remains compatible by reusing the current media payload field rather than changing the storage shape. -- Future video models can plug in through shared detection helpers or explicit `videoGeneration` metadata. - -## Verification Strategy -Run: -- `pnpm run typecheck` -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` diff --git a/docs/features/openai-compatible-video-generation/spec.md b/docs/features/openai-compatible-video-generation/spec.md deleted file mode 100644 index 66550c902..000000000 --- a/docs/features/openai-compatible-video-generation/spec.md +++ /dev/null @@ -1,32 +0,0 @@ -# OpenAI-Compatible Video Generation - -## User Need -Users need DeepChat to recognize and run video generation models such as `doubao-seedance-2-0-fast-260128` through the same model-driven provider flow used by text and audio generation, without hardcoding one-off provider logic for each future video model. - -## Goal -Enable first-class video generation routing in DeepChat for OpenAI-compatible providers, starting with AIHubMix Seedance models and leaving a compatibility layer for future video models. - -## Acceptance Criteria -1. Shared model/type contracts support `videoGeneration` and preserve compatibility with existing model metadata. -2. DeepChat can recognize `doubao-seedance-2-0-fast-260128` as a video generation model even when upstream metadata is incomplete or still marked as `chat`. -3. Main runtime can route video generation requests through an OpenAI-compatible `/v1/videos` flow. -4. Video generation responses are normalized into a stable internal result shape that future providers/models can reuse. -5. Generated video output reaches the existing assistant message pipeline and renders in the chat UI. -6. Validation commands pass: -- `pnpm run typecheck` -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` - -## Constraints -- Keep the provider integration generic for OpenAI-compatible video endpoints. -- Reuse the current assistant media block pipeline where practical instead of introducing a parallel storage format. -- Do not scope in advanced video editing controls or provider-specific parameter UIs for this change. - -## Non-Goals -- Dedicated video generation settings panels. -- Agent-level video generation tool configuration. -- Non-OpenAI-compatible video provider protocols. - -## Open Questions -- None for current scope. diff --git a/docs/features/openai-compatible-video-generation/tasks.md b/docs/features/openai-compatible-video-generation/tasks.md deleted file mode 100644 index d27f8ab8f..000000000 --- a/docs/features/openai-compatible-video-generation/tasks.md +++ /dev/null @@ -1,25 +0,0 @@ -# Tasks - -## Shared Types + Detection -- [x] Add `ModelType.VideoGeneration` and extend model-db parsing/schema for `videoGeneration`. -- [x] Add shared video detection/compatibility helpers for endpoint hints, modalities, and known model IDs. -- [x] Update model config inference to classify video models consistently in main and renderer flows. -- [x] Extend session generation settings/contracts and draft state to carry `videoGeneration` options. - -## Runtime + Provider -- [x] Add `generateVideoStandalone` presenter contracts and implementation. -- [x] Add OpenAI-compatible `/v1/videos` request/response normalization in the AI SDK runtime/provider path. -- [x] Persist and sanitize session-level video generation settings through agent runtime and sqlite storage. -- [ ] Mark Seedance built-in model metadata as `videoGeneration` where available. - -## Renderer -- [x] Expose video model detection for UI behavior alignment. -- [x] Add assistant message rendering for generated video media. -- [x] Update model list/type display for video generation models. -- [x] Expose video generation settings in chat status bar and model config dialog flows. - -## Validation -- [x] Run `pnpm run typecheck`. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. diff --git a/docs/features/openai-image-generation-settings/plan.md b/docs/features/openai-image-generation-settings/plan.md deleted file mode 100644 index 26b8a3ace..000000000 --- a/docs/features/openai-image-generation-settings/plan.md +++ /dev/null @@ -1,28 +0,0 @@ -# OpenAI Image Generation Settings Plan - -## Architecture - -- Keep the shared `ImageGenerationOptions` type and contracts. -- Rename gpt-image-2-specific helpers, constants, validators, and UI component names to OpenAI image generation settings names. -- Store session image settings as JSON in `deepchat_sessions`; keep the existing v27 migration. -- Keep model-level settings in the existing model config JSON store. - -## Data Flow - -- Settings dialog writes model-level `imageGeneration` when `supportsOpenAIImageGenerationSettings(...)` is true. -- Chat status bar writes session-level `imageGeneration` under the same capability check. -- Agent runtime merges effective session settings into `ModelConfig`. -- AI SDK runtime passes `size` at the `generateImage()` top level and OpenAI provider options through `providerOptions`. - -## Compatibility - -- Existing sessions without image settings behave exactly as before. -- Empty or invalid stored image settings are treated as unset. -- Existing chat-model settings remain unchanged for models outside the OpenAI image settings capability. -- The `deepchat_sessions` migration stays at version 27 because the global schema version is already 26. - -## Tests - -- Runtime tests use `gpt-image-2` for empty options and option forwarding. -- Contract and SQLite tests verify config round trips. -- Renderer component tests use `gpt-image-2` as the positive image settings fixture and existing chat model ids as generic fallbacks. diff --git a/docs/features/openai-image-generation-settings/spec.md b/docs/features/openai-image-generation-settings/spec.md deleted file mode 100644 index b59baaa13..000000000 --- a/docs/features/openai-image-generation-settings/spec.md +++ /dev/null @@ -1,27 +0,0 @@ -# OpenAI Image Generation Settings Spec - -## User Story - -Users who select an OpenAI or OpenAI-compatible image generation model can configure image generation parameters without seeing chat-only settings that do not affect image generation. - -## Acceptance Criteria - -- OpenAI image-generation routes, image endpoints, imageGeneration model types, and the current `gpt-image-2` fallback use the image-specific settings UI. -- Default UI choices do not persist or send image generation parameters. -- Model-level image settings define defaults for new sessions. -- Session-level image settings can override model-level settings. -- Runtime forwards only valid OpenAI image options to AI SDK image generation. -- Invalid custom sizes cannot be saved from the UI. - -## Non-goals - -- Do not add support for `n`, `partial_images`, streaming partial images, `input_fidelity`, `style`, or `user`. -- Do not add transparent background support. -- Do not test future or unconfirmed model ids. - -## Constraints - -- Public config fields remain under `imageGeneration`. -- Unset options must remain `undefined` so OpenAI defaults apply. -- Supported stored fields are `size`, `quality`, `outputFormat`, `outputCompression`, `background`, and `moderation`. -- Custom sizes must use `{width}x{height}`, both dimensions must be multiples of 16, each side must be at most 3840, aspect ratio must be at most 3:1, and total pixels must be between 655360 and 8294400. diff --git a/docs/features/openai-image-generation-settings/tasks.md b/docs/features/openai-image-generation-settings/tasks.md deleted file mode 100644 index f49cf88d9..000000000 --- a/docs/features/openai-image-generation-settings/tasks.md +++ /dev/null @@ -1,10 +0,0 @@ -# OpenAI Image Generation Settings Tasks - -- [x] Define SDD artifacts. -- [x] Add shared image option types, validation, and contracts. -- [x] Persist session image settings. -- [x] Forward image settings in AI SDK runtime. -- [x] Add image-generation-specific settings UI. -- [x] Generalize naming from gpt-image-2 to OpenAI image generation settings. -- [x] Keep tests focused on `gpt-image-2` and existing non-image models. -- [x] Run format, i18n, lint, typecheck, and focused tests. diff --git a/docs/features/privacy-mode/plan.md b/docs/features/privacy-mode/plan.md deleted file mode 100644 index f6aa6743e..000000000 --- a/docs/features/privacy-mode/plan.md +++ /dev/null @@ -1,26 +0,0 @@ -# Privacy Mode Plan - -## Settings Flow - -- Add `privacyModeEnabled` to the persisted app settings defaults. -- Expose typed getter/setter access through `ConfigPresenter`, `UiSettingsHelper`, settings route contracts, route adapter, and renderer `uiSettingsStore`. -- Publish typed settings change payloads so the renderer reacts immediately. - -## Automatic Request Gating - -- `UpgradePresenter`: skip the app-focus auto-check path while Privacy Mode is on. -- `ProviderDbLoader`: skip automatic startup/background refresh while keeping cached or built-in snapshots active. -- `AcpRegistryService`: skip automatic manifest refresh and icon sync while keeping cached or built-in snapshots active. -- `McpPresenter` and `ServerManager`: skip automatic npm registry probing and delayed background refresh while keeping cached/custom registry state active. - -## UI - -- Add `PrivacySettingsSection` to Data settings. -- Show a switch, a short explanation, and an inline audit list of the covered automatic outbound paths. -- Keep the copy explicit about manual actions and configured integrations staying available. - -## Validation - -- Update typed settings route tests and renderer store tests. -- Add presenter/service tests for automatic skip and manual refresh behavior. -- Add Common settings UI coverage for the new section. diff --git a/docs/features/privacy-mode/spec.md b/docs/features/privacy-mode/spec.md deleted file mode 100644 index 46c7c6c8e..000000000 --- a/docs/features/privacy-mode/spec.md +++ /dev/null @@ -1,51 +0,0 @@ -# Privacy Mode - -## Summary - -Add a global `privacyModeEnabled` toggle in Data settings. Privacy mode disables DeepChat-owned automatic outbound requests while keeping model APIs, manual refresh/check actions, and configured third-party integrations available. - -## User Stories - -### US-1: Enable privacy mode per device -- As a privacy-sensitive user, I can enable Privacy Mode from Data settings. -- Acceptance: - - The setting persists per device. - - The default value is `false`. - - Future automatic tasks respect the setting immediately without restart. - -### US-2: Keep manual actions available -- As a user, I can still run manual update checks and manual refresh actions while Privacy Mode is on. -- Acceptance: - - Manual app update checks and manual download/install flows keep current behavior. - - Manual provider DB refresh, ACP Registry refresh, and npm registry refresh keep current behavior. - -### US-3: Stop DeepChat-owned automatic outbound requests -- As a security-conscious operator, I can keep the app on cached or built-in data sources while Privacy Mode is on. -- Acceptance: - - Automatic app update checks stop. - - Automatic provider/model metadata refresh stops. - - Automatic ACP Registry refresh and icon sync stop. - - Automatic MCP npm registry probing stops. - -### US-4: Preserve configured integrations -- As an administrator, I can keep approved external integrations active while Privacy Mode is on. -- Acceptance: - - Model API traffic keeps current behavior. - - Remote control channels, enabled remote MCP HTTP/SSE servers, and other user-enabled integrations keep current behavior. - -## Automatic Outbound Audit - -- App update checks through `electron-updater`, currently using GitHub Releases feed in this repo build. -- Provider/model metadata refresh from `https://raw.githubusercontent.com/ThinkInAIXYZ/PublicProviderConf/refs/heads/dev/dist/all.json`. -- ACP Registry manifest refresh from `https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json`. -- ACP Registry icon sync from `https://cdn.agentclientprotocol.com/registry/`. -- MCP npm registry auto-detect against: - - `https://registry.npmmirror.com/` - - `https://registry.npmjs.org/` - - `https://r.cnpmjs.org/` - -## Non-Goals - -- Blocking model API traffic. -- Blocking manual OAuth, manual sync, or manual download flows. -- Blocking configured third-party integrations. diff --git a/docs/features/privacy-mode/tasks.md b/docs/features/privacy-mode/tasks.md deleted file mode 100644 index f1e7a5587..000000000 --- a/docs/features/privacy-mode/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Privacy Mode Tasks - -1. Add `privacyModeEnabled` to persisted settings defaults, presenter accessors, typed settings contracts, adapter, and renderer store. -2. Gate automatic update checks in `UpgradePresenter`. -3. Gate automatic provider DB refresh in `ProviderDbLoader`. -4. Gate automatic ACP Registry refresh and icon sync in `AcpRegistryService`. -5. Gate automatic MCP npm registry probing in `McpPresenter` and `ServerManager`. -6. Add the Data settings privacy section with outbound audit copy. -7. Add or update tests for settings flow, automatic skip paths, manual refresh paths, and Common settings rendering. diff --git a/docs/features/process-tool/plan.md b/docs/features/process-tool/plan.md deleted file mode 100644 index 88ad78216..000000000 --- a/docs/features/process-tool/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Process Tool Specification Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/process-tool/spec.md b/docs/features/process-tool/spec.md deleted file mode 100644 index db4e7d2ce..000000000 --- a/docs/features/process-tool/spec.md +++ /dev/null @@ -1,109 +0,0 @@ -# Process Tool Specification - -## Overview - -Add a new agent tool `process` for managing background exec sessions. This tool allows agents to run long-running commands in the background and interact with them asynchronously. - -## User Stories - -### US-1: Start Background Command -As an AI agent, I want to start a command in the background so that I can run long-running tasks without blocking the conversation. - -**Acceptance Criteria:** -- Agent can execute `execute_command` with `background: true` parameter -- Command returns immediately with a `sessionId` and `status: "running"` -- Process continues running after tool returns - -### US-1.1: Foreground Yield To Background -As an AI agent, I want a foreground `exec` call to yield into a background session when it runs too long, so that the loop can continue without restarting the command. - -**Acceptance Criteria:** -- Foreground `exec` waits only until `yieldMs` (or the default yield window) -- If the command finishes within that window, it returns the normal foreground result -- If the command is still running after that window, the same process is kept alive and `exec` returns `status: "running"` with a `sessionId` -- The yielded session is manageable through `process` - -### US-2: Monitor Background Output -As an AI agent, I want to poll the output of a background command so that I can monitor its progress. - -**Acceptance Criteria:** -- `process` tool with `action: "poll"` returns recent output -- Output is truncated to last N characters (configurable, default 500) -- Returns current status: "running", "done", "error", or "killed" - -### US-3: Read Full Output -As an AI agent, I want to read the full output of a completed command so that I can analyze the results. - -**Acceptance Criteria:** -- `process` tool with `action: "log"` supports pagination via `offset` and `limit` -- Large outputs are automatically offloaded to files -- Agent can use file tools to read offloaded content - -### US-4: Send Input to Background Process -As an AI agent, I want to send input to a running background process so that I can interact with interactive commands. - -**Acceptance Criteria:** -- `process` tool with `action: "write"` sends data to stdin -- Optional `eof: true` closes stdin - -### US-5: Manage Background Sessions -As an AI agent, I want to list, kill, and clean up background sessions so that I can manage resources. - -**Acceptance Criteria:** -- `action: "list"` shows all sessions for current agent -- `action: "kill"` terminates a running session -- `action: "clear"` clears output buffer/file -- `action: "remove"` completely removes a session - -## Constraints - -### Security -- Sessions are isolated by `conversationId` (agent-scoped) -- Session IDs are cryptographically random (nanoid) -- No cross-agent session access - -### Resource Management -- Sessions are in-memory only (lost on restart) -- Automatic TTL cleanup (default 30 minutes inactivity) -- Maximum runtime limit (default 30 minutes) -- Large outputs offloaded to files (>10KB threshold) - -### Configuration -```typescript -interface ExecToolsConfig { - backgroundMs: number // Default yield window (10000) - timeoutSec: number // Max runtime (1800) - cleanupMs: number // Session TTL (1800000) - maxOutputChars: number // Poll output limit (500) -} -``` - -Environment variables: -- `PI_BASH_YIELD_MS` -- `PI_BASH_MAX_OUTPUT_CHARS` -- `OPENCLAW_BASH_PENDING_MAX_OUTPUT_CHARS` (compatibility) -- `PI_BASH_JOB_TTL_MS` - -## Non-Goals - -- PTY/terminal emulation (not needed - pipe mode is sufficient) -- Cross-agent session sharing -- Persistent sessions across restarts -- Real-time streaming output (poll-based only) - -## Open Questions - -| Question | Decision | -|----------|----------| -| PTY or pipe mode? | Pipe mode (agent-controlled) | -| Offload large outputs? | Yes, >10KB threshold | -| Always show process tool? | Yes, always visible | -| Default poll output size? | 500 characters | - -## Business Value - -Enables agents to: -1. Run long-running builds/tests without blocking -2. Monitor server processes -3. Handle interactive CLI tools -4. Better resource management for complex workflows diff --git a/docs/features/process-tool/tasks.md b/docs/features/process-tool/tasks.md deleted file mode 100644 index 3a73c59e8..000000000 --- a/docs/features/process-tool/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Process Tool Specification Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/provider-config-import/plan.md b/docs/features/provider-config-import/plan.md deleted file mode 100644 index 3c004f3e8..000000000 --- a/docs/features/provider-config-import/plan.md +++ /dev/null @@ -1,89 +0,0 @@ -# Provider Config Import Plan - -## Architecture - -- Add provider import route contracts in `providers.routes.ts`: - - `providers.import.scan` returns a scan session, source statuses, provider previews, mappings, default selections, and warnings. - - `providers.import.apply` takes the scan session and selected source/provider IDs, then returns an import result summary. -- Add a main-process `ProviderImportService` owned by the provider routes area. -- Wire service into `MainKernelRouteRuntime` and `dispatchProviderRoute`. -- Add `ProviderImportClient` methods to `ProviderClient` for renderer use. -- Keep scan sessions in memory and short lived. Scan responses only include masked API keys. - -## Source Readers - -Default path discovery is platform-aware: - -| Source | macOS / Linux | Windows | -| --- | --- | --- | -| Alma | `~/Library/Application Support/alma/chat_threads.db` | `%APPDATA%\alma\chat_threads.db` | -| Cherry Studio | `~/Library/Application Support/CherryStudio/Local Storage/leveldb` | `%APPDATA%\CherryStudio\Local Storage\leveldb` | -| Hermes | `~/.hermes/config.yaml` | `%USERPROFILE%\.hermes\config.yaml` | -| OpenClaw | `~/.openclaw/gateway.yaml` | `%USERPROFILE%\.openclaw\gateway.yaml` | - -- Alma: - - Path: see platform table above. - - Query: `providers` table, skip `type='acp'` - - Use read-only `better-sqlite3-multiple-ciphers`. -- Cherry Studio: - - Path: see platform table above. - - Read `persist:cherry-studio`, parse nested `llm.providers` - - Use `level` dependency. - - Copy a temporary LevelDB snapshot and skip `LOCK` before reading, so import can work while Cherry Studio is running. -- Hermes: - - Path: see platform table above. - - Parse `llm.providers` with `yaml`. -- OpenClaw: - - Path: see platform table above. - - Parse `providers` with `yaml`. - -## Mapping - -Fixed processing order: Alma, Cherry Studio, Hermes, OpenClaw. - -| Source type / id / api_format | DeepChat target | -| --- | --- | -| `openai`, `openai-chat`, `openai-compatible`, Alma `openai-chat` | built-in `openai` if source id/name is OpenAI, otherwise custom provider with `apiType='openai-completions'` | -| `openai-response`, `openai-responses` | built-in `openai-responses` | -| `anthropic` | built-in `anthropic` | -| `gemini` | built-in `gemini` | -| `ollama` | built-in `ollama` | -| `new-api` | built-in `new-api` | -| `silicon`, `siliconflow`, `siliconcloud` | built-in `silicon` | -| `deepseek` | built-in `deepseek` | -| `ppio`, `ppinfra`, base URL containing `ppinfra.com` | built-in `ppio` | -| `volcengine`, `ark` | built-in `doubao` | -| known built-in ids | matching built-in provider id | -| unknown provider with API key and `http(s)` base URL | custom provider, source name retained, `apiType='openai-completions'` | -| custom/openai-compatible provider with base URL but no API key | custom provider row, not selected by default; user can switch API type to `ollama` before importing | -| unsupported credential-only shapes without a usable base URL | visible as unsupported and not selectable | - -Configured DeepChat providers default to unchecked. A forced checked row updates the matching provider. - -## Write Behavior - -- Built-in target: - - Update `apiKey`, `baseUrl`, `enable`. - - Preserve existing provider metadata and websites. - - Replace or upsert custom model entries imported from that row. -- Custom target: - - Create stable id from source prefix and source provider id, with suffix on collision. - - Set `custom: true`, `enable: true`, user-selected `apiType`, `apiKey`, `baseUrl`, and name. - - When updating an existing custom provider matched by fingerprint, preserve unrelated provider metadata such as rate limits, custom models, websites, and capability ids. - - Add source models as custom models and enable them. -- Apply selected rows in fixed source order. If multiple selected rows map to the same target provider, later rows overwrite earlier rows and earlier result rows are marked overwritten. - -## UI Integration - -- Add `ProviderConfigImportDialog.vue` under settings components. -- Add a Data Settings row and section target id `provider-import`. -- Add welcome guide import action beside the Select a Provider step title; it opens settings route `settings-database` with section `provider-import` and resumes the onboarding guide on the next provider setup step without completing setup until import succeeds. -- Keep scan source rows text-first and compact, without per-agent icons. -- Add an API type selector for custom provider rows; default to the mapping result and submit the override with the provider selection. -- On successful import during onboarding, complete provider setup steps that are satisfied by imported config. - -## Test Strategy - -- Main tests for readers, mapping, default selection, scan session, apply overwrite order, and model writes. -- Renderer tests for Data Settings entry, section auto-open, dialog flow, empty/error states, and welcome button navigation. -- Verify with local real scans on installed Alma and Cherry Studio after tests pass. diff --git a/docs/features/provider-config-import/spec.md b/docs/features/provider-config-import/spec.md deleted file mode 100644 index 2df26bd5e..000000000 --- a/docs/features/provider-config-import/spec.md +++ /dev/null @@ -1,99 +0,0 @@ -# Provider Config Import - -## Goal - -Help new and existing DeepChat users import model provider configuration from other local Agent clients so they can start using DeepChat without manually retyping API keys, base URLs, and model names. - -## User Stories - -- As a new user, I can choose "Import from other agents" from the welcome provider setup and continue in the settings import guide. -- As an existing user, I can open Data Settings and start an "Import from other agents" guide. -- As a user with multiple Agent clients installed, I can select one or more detected sources and review provider rows before importing. -- As a user with existing DeepChat provider config, I can see which rows would overwrite existing config before I opt in. -- As a user, I can see a final import summary with a scrollable list of imported, updated, overwritten, and skipped providers. - -## Acceptance Criteria - -- The import entry appears in Data Settings and opens a step-by-step dialog. -- The welcome guide title presents "Select a Provider or Import from other agents"; the import action is visually clickable in the coachmark while the provider grid remains focused on provider choices. -- Clicking the welcome import action resumes the guide on the next provider setup step after settings opens; setup steps are marked complete only after an import succeeds. -- Scan checks macOS, Linux, and Windows default paths for Alma, Cherry Studio, Hermes, and OpenClaw. -- Source order is fixed: Alma, Cherry Studio, Hermes, OpenClaw. -- Users can multi-select detected sources; selected sources are processed in fixed source order. -- Each selected source screen lists detected provider config, the DeepChat mapping, masked API key, base URL, model count, and default selection state. -- Providers already configured in DeepChat are not selected by default and show an overwrite warning. -- Unknown provider types with an API key and `http(s)` base URL import as custom providers, preserving the source name and using `apiType='openai-completions'`. -- Custom provider rows allow the user to override the DeepChat API type before import, because automatic protocol matching can be wrong. -- Custom rows with a base URL but no API key can be imported after selecting an API type that does not require an API key, such as Ollama; non-Ollama custom imports require both API key and endpoint. -- If later selected sources map to the same target provider, later sources overwrite earlier selected sources. -- Import reads source data only and never modifies other Agent application files. -- Renderer never receives raw API keys during scan; raw keys stay in the main process scan session until import is applied. - -## Non-Goals - -- Importing conversations, messages, attachments, MCP config, prompts, skills, or Agent definitions. -- Custom source path selection or manual browsing in this first increment. -- Exporting DeepChat provider config to other clients. -- Network validation of imported API keys. - -## UX Notes - -Keep the interface clean and operational: - -```text -Data & Privacy -──────────────────────────────────────── -Provider Config Import -Import API keys, base URLs, and models from other Agent clients. - [Import from other agents] -──────────────────────────────────────── -Database Repair [Check and repair] -Provider DB [Refresh provider DB] -``` - -```text -┌─ Import Provider Configs ───────────────────────────────┐ -│ Scan → Alma → Cherry Studio → Hermes → OpenClaw → Done │ -│ │ -│ Select sources │ -│ Alma Found 1 provider ~/Library/... [x] │ -│ Cherry Studio Found 1 configured ~/Library/... [x] │ -│ Hermes Not found [ ] │ -│ OpenClaw Invalid config [ ] │ -│ │ -│ [Cancel] [Rescan] [Next] │ -└──────────────────────────────────────────────────────────┘ -``` - -```text -┌─ Alma: choose providers ────────────────────────────────┐ -│ [x] nextapi → Custom / openai-completions │ -│ API type: [OpenAI Chat Completions v] │ -│ sk-1234...abcd · 8 models · https://.../v1 │ -│ [ ] OpenAI → OpenAI │ -│ Already configured in DeepChat; not selected │ -│ │ -│ [Back] [Next source] │ -└──────────────────────────────────────────────────────────┘ -``` - -```text -┌─ Import Complete ───────────────────────────────────────┐ -│ Imported 4 providers · 2 updated · 1 skipped │ -│ │ -│ Scrollable results │ -│ ✓ Alma / nextapi Created custom provider │ -│ ✓ Cherry / new-api Updated New API │ -│ ↷ Hermes / PPInfra Overwritten by OpenClaw │ -│ ! OpenClaw / broken Skipped: missing API key │ -│ │ -│ [Done] │ -└──────────────────────────────────────────────────────────┘ -``` - -## Constraints - -- Use DeepChat typed route / typed event architecture for renderer-main communication. -- Use i18n keys for user-facing strings. -- Apply provider writes through `ConfigPresenter` and existing provider/model events, not direct `app-settings.json` edits. -- Use existing dependencies where possible; Cherry Studio LevelDB reading may add `level`. diff --git a/docs/features/provider-config-import/tasks.md b/docs/features/provider-config-import/tasks.md deleted file mode 100644 index 0c4e1d5ab..000000000 --- a/docs/features/provider-config-import/tasks.md +++ /dev/null @@ -1,14 +0,0 @@ -# Provider Config Import Tasks - -- [x] Add SDD docs and dependency decision for LevelDB. -- [x] Add shared route schemas and provider import types. -- [x] Implement `ProviderImportService` scan, mapping, apply, and reader helpers. -- [x] Add macOS, Linux, and Windows default path discovery. -- [x] Wire routes into provider route handler, main runtime, route catalog, and provider client. -- [x] Build settings import dialog and Data Settings entry. -- [x] Add welcome guide title import entry, settings section navigation, and guide step advancement. -- [x] Add i18n keys for English and Chinese, then run i18n sync/check. -- [x] Add main and renderer tests for critical paths. -- [x] Run local mock imports and real scans against installed Alma/Cherry Studio. -- [x] Run format, i18n, lint, typecheck, and focused tests. -- [x] Start a separate review subagent and address findings. diff --git a/docs/features/provider-deeplink-import/plan.md b/docs/features/provider-deeplink-import/plan.md deleted file mode 100644 index 170d4a237..000000000 --- a/docs/features/provider-deeplink-import/plan.md +++ /dev/null @@ -1,180 +0,0 @@ -# Provider Deeplink Import 实施计划 - -## 1. 当前实现基线 - -### 1.1 Deeplink 现状 - -1. `src/main/presenter/deeplinkPresenter/index.ts` 已支持 `deepchat://start` 和 `deepchat://mcp/install`。 -2. 设置窗口已经支持通过 `SETTINGS_EVENTS.NAVIGATE` 进行页面跳转。 -3. 设置 App 已有 MCP deeplink 的初始化处理,可复用设置窗口 ready 后接收事件的模式。 - -### 1.2 Provider 设置页现状 - -1. Provider 列表与配置由 `providerStore` 驱动。 -2. Provider 详情页可基于路由参数 `providerId` 切换目标 provider。 -3. 自定义 provider 已有手动新增流程,可复用新增后的选中逻辑。 - -## 2. 设计决策 - -### 2.1 Payload 与共享类型 - -新增共享模块 `src/shared/providerDeeplink.ts`: - -1. 常量: - - `PROVIDER_INSTALL_ROUTE` - - `PROVIDER_INSTALL_VERSION` -2. 类型: - - `ProviderInstallDeeplinkPayload` - - `ProviderInstallPreview` -3. 工具函数: - - `maskApiKey` - - custom type 校验 - -### 2.2 主进程事件流 - -入口:`deepchat://provider/install?v=1&data=...` - -处理顺序: - -1. `DeeplinkPresenter.handleDeepLink` 识别 `provider/install` -2. Base64 解码 + JSON 解析 + 字段校验 -3. built-in: - - 校验 `id` - - 拒绝 `acp` -4. custom: - - 校验 `name/type` - - 校验 `type` 在允许列表中 - - 拒绝 `acp` -5. 创建/聚焦设置窗口 -6. 发送: - - `SETTINGS_EVENTS.NAVIGATE -> settings-provider` - - `SETTINGS_EVENTS.PROVIDER_INSTALL -> preview` - -错误策略: - -1. 解析失败或 payload 不合法时,发 `NOTIFICATION_EVENTS.SHOW_ERROR` -2. 失败时不写任何 provider 配置 - -### 2.3 渲染进程事件流 - -`src/renderer/settings/App.vue`: - -1. 监听 `SETTINGS_EVENTS.PROVIDER_INSTALL` -2. 确保 provider store 已初始化 -3. built-in 导入时切到 `settings-provider/:providerId` -4. custom 导入时切到 `settings-provider` -5. 把 preview 放入新的 pending import store - -`src/renderer/src/stores/providerDeeplinkImport.ts`: - -1. 只维护当前 pending preview -2. 对话框开关由 preview 是否存在推导 - -### 2.4 对话框与落库行为 - -`ProviderDeeplinkImportDialog` 只负责展示解析结果,不自行写配置。 - -展示规则: - -1. built-in:`icon + id` -2. custom:`icon + name`,并额外显示 `type` -3. 两类都展示 `baseUrl` -4. 两类都展示脱敏 `apiKey` -5. built-in 额外显示覆盖 warning - -确认逻辑放在 `ModelProviderSettings.vue`: - -1. built-in: - - 更新 `baseUrl/apiKey` - - 若未启用则自动启用 - - 刷新该 provider 模型 - - 切换到对应 provider 页面 -2. custom: - - 生成新 `id` - - 创建 `custom: true` provider - - 默认 `enable: true` - - 刷新新 provider 模型 - - 切换到新 provider 页面 -3. cancel: - - 清空 pending preview - - 不写配置 - -### 2.5 Provider 兼容策略 - -1. built-in provider 以 `id` 作为唯一匹配键,因此导入是覆盖语义。 -2. custom provider 以 `type/apiType` 校验,但确认后总是新增实例,因此是追加语义。 -3. `vertex`、`aws-bedrock`、`github-copilot` 等允许部分导入,即使后续仍需补专属字段,也不阻塞 `baseUrl/apiKey` 导入。 -4. `acp` 独立于本流程,不进入 `settings-provider` 导入链路。 - -## 3. Manual Playground - -新增: - -- `test/manual/deeplink-playground.html` - -页面结构: - -1. `start` -2. `mcp/install` -3. `provider/install` -4. `provider/install builder` - -规则: - -1. built-in 列出当前所有默认 provider `id`,排除 `acp` -2. custom 列出当前所有允许导入的 `apiType`,排除 `acp` -3. 每项展示: - - label - - raw JSON - - deeplink - - `Open` - - `Copy` -4. 示例数据全部使用假地址和假 key - -## 4. 测试策略 - -### 4.1 Main - -1. built-in payload 成功时: - - 打开设置窗 - - 发送 `NAVIGATE` - - 发送 `PROVIDER_INSTALL` -2. custom payload 成功时: - - 发送 custom preview -3. 非法 payload: - - 不发送导入事件 - - 发送错误通知 - -### 4.2 Renderer - -1. `App.vue` 收到 `PROVIDER_INSTALL` 后正确导航并写入 preview store -2. `ModelProviderSettings.vue`: - - built-in confirm 覆盖并启用 provider - - custom confirm 新增并选中新 provider - - cancel 不写配置 -3. `ProviderDeeplinkImportDialog.vue` 正确展示 built-in/custom 解析结果 - -### 4.3 Manual - -1. playground 中三类 deeplink 都能生成合法协议链接 -2. built-in/custom 列表覆盖范围正确 -3. builder 输出格式与应用解析格式一致 - -## 5. 风险与缓解 - -1. 风险:设置窗口创建后事件发送早于页面监听注册。 -缓解:复用现有 settings 事件通道,并在 App 侧做独立导航兜底。 - -2. 风险:部分 provider 启用后仍缺专属字段,模型刷新可能失败。 -缓解:允许部分导入;模型刷新失败只记录日志,不回滚导入。 - -3. 风险:手工验证页 provider 列表与真实支持集合漂移。 -缓解:built-in 与 custom 列表以当前代码中的 provider 集合为准,变更时同步更新此页。 - -## 6. 质量门槛 - -1. `pnpm run format` -2. `pnpm run i18n` -3. `pnpm run lint` -4. `pnpm run typecheck` -5. 关键 main/renderer 测试通过 diff --git a/docs/features/provider-deeplink-import/spec.md b/docs/features/provider-deeplink-import/spec.md deleted file mode 100644 index 4f383bb5d..000000000 --- a/docs/features/provider-deeplink-import/spec.md +++ /dev/null @@ -1,136 +0,0 @@ -# Provider Deeplink Import 规格 - -## 概述 - -新增 provider 导入 deeplink: - -- `deepchat://provider/install?v=1&data=` - -其中 `data` 只接受两种结构,且 `id` 与 `type` 必须二选一: - -1. `{ id, baseUrl, apiKey }` -2. `{ name, type, baseUrl, apiKey }` - -导入后统一进入 Provider Settings,先展示确认对话框;用户确认后才写入配置,取消则直接丢弃。 - -## 背景与动机 - -1. 用户经常需要在多个 built-in provider 与 custom provider 之间切换配置。 -2. 当前 provider 配置主要依赖手动录入,分享和一键导入成本高。 -3. DeepLink 已经用于 `start` 和 `mcp/install`,provider 导入应沿用同一套唤起能力。 -4. 需要一个独立的手工验证页,降低联调和回归验证成本。 - -## 用户故事 - -### US-1:一键导入内置 Provider - -作为用户,我希望点击一个 deeplink 后直接进入对应 provider 设置,并在确认后覆盖它的 `baseUrl` 与 `apiKey`。 - -### US-2:一键新增 Custom Provider - -作为用户,我希望通过 deeplink 快速新增一个 custom provider,而不是手动新建并逐项填写。 - -### US-3:导入前确认 - -作为用户,我希望在真正写入前看到解析结果,避免误覆盖现有配置。 - -### US-4:手工验证入口 - -作为开发者或测试者,我希望仓库里有一个静态网页,能集中打开所有支持的 deeplink。 - -## 功能需求 - -### A. Provider Deeplink 协议 - -- [ ] 新增 `deepchat://provider/install?v=1&data=` -- [ ] `v=1` 是当前唯一支持版本 -- [ ] `data` Base64 解码后必须是 JSON object -- [ ] payload 只允许两种结构: - - [ ] `{ id, baseUrl, apiKey }` - - [ ] `{ name, type, baseUrl, apiKey }` -- [ ] `id` 与 `type` 同时存在或同时缺失时,必须拒绝 - -### B. 内置 Provider 导入 - -- [ ] 当 payload 包含 `id` 时,按内置 provider id 匹配 -- [ ] `id='acp'` 必须拒绝 -- [ ] unknown `id` 必须拒绝 -- [ ] 确认后覆盖目标 provider 的 `baseUrl` 与 `apiKey` -- [ ] 若目标 provider 当前未启用,确认后自动启用 -- [ ] 完成后停留在对应 provider 设置页 -- [ ] 若是 `vertex`、`aws-bedrock`、`github-copilot` 等仍需额外字段的 provider,允许部分导入,不阻塞确认 - -### C. Custom Provider 导入 - -- [ ] 当 payload 包含 `type` 时,按 provider `apiType` 匹配 -- [ ] `type='acp'` 必须拒绝 -- [ ] unknown `type` 必须拒绝 -- [ ] custom payload 必须包含 `name` -- [ ] 确认后总是新增一条 custom provider,不复用旧条目 -- [ ] 新 provider 默认 `enable=true` -- [ ] 完成后停留在新 provider 设置页 - -### D. 设置页行为 - -- [ ] deeplink 唤起后自动进入 `settings-provider` -- [ ] 在真正写入前弹出 `Import Provider` 对话框 -- [ ] built-in 对话框只展示: - - [ ] `id + icon` - - [ ] `baseUrl` - - [ ] 脱敏 `apiKey` -- [ ] custom 对话框只展示: - - [ ] `name + icon` - - [ ] `type` - - [ ] `baseUrl` - - [ ] 脱敏 `apiKey` -- [ ] built-in 导入需要展示“将覆盖当前配置”的提示 -- [ ] 取消后不写入任何 provider 配置 - -### E. 错误处理 - -- [ ] 非法 Base64、非法 JSON、非法版本、缺字段、unknown `id/type` 均必须拒绝 -- [ ] 非法 deeplink 需要有可见错误提示 -- [ ] 拒绝场景不得写入 provider 配置 - -### F. Manual Playground - -- [ ] 新增 `test/manual/deeplink-playground.html` -- [ ] 页面覆盖三类 deeplink: - - [ ] `start` - - [ ] `mcp/install` - - [ ] `provider/install` -- [ ] `provider/install` 区块必须列出: - - [ ] 所有 built-in provider `id`,排除 `acp` - - [ ] 所有允许的 custom `apiType`,排除 `acp` -- [ ] 每项都提供 `Open` 和 `Copy` -- [ ] 每项都展示原始 JSON 与最终 deeplink -- [ ] 页面提供一个可编辑 builder,用于临时生成 deeplink -- [ ] 所有示例数据必须为 fake data - -## 验收标准 - -- [ ] 打开 built-in provider deeplink 时,设置窗进入对应 provider,并弹出确认对话框 -- [ ] 确认 built-in provider 导入后,`baseUrl/apiKey` 被覆盖,provider 被自动启用 -- [ ] 打开 custom provider deeplink 时,设置窗进入 provider 设置页,并弹出确认对话框 -- [ ] 确认 custom provider 导入后,会新增一条启用中的 custom provider -- [ ] 取消导入时,不产生任何配置写入 -- [ ] 非法 payload 只显示错误,不进入确认流程 -- [ ] 手工验证页可直接生成并打开三类 deeplink - -## 非目标 - -1. 不扩展 provider deeplink 的版本协商机制,本次仅支持 `v=1`。 -2. 不新增 provider 专属迁移脚本或持久化 schema。 -3. 不为 `acp` provider 引入导入能力。 -4. 不修改现有 `start` 与 `mcp/install` 的协议格式。 - -## 约束 - -1. 保持现有 Presenter + EventBus 架构。 -2. 所有用户可见文案必须走 i18n。 -3. 不破坏现有 provider 配置存储结构。 -4. Manual playground 不打包进应用,仅作为仓库内测试辅助页。 - -## 开放问题 - -无。 diff --git a/docs/features/provider-deeplink-import/tasks.md b/docs/features/provider-deeplink-import/tasks.md deleted file mode 100644 index ca53fb43e..000000000 --- a/docs/features/provider-deeplink-import/tasks.md +++ /dev/null @@ -1,59 +0,0 @@ -# Provider Deeplink Import Tasks - -## T0 规格与设计 - -- [x] 完成 `spec.md` -- [x] 完成 `plan.md` -- [x] 完成 `tasks.md` - -## T1 共享协议与事件 - -- [x] 新增 provider deeplink 共享类型与协议常量 -- [x] 新增 `SETTINGS_EVENTS.PROVIDER_INSTALL` -- [x] 补共享类型导出 - -## T2 主进程解析与分发 - -- [x] 在 `deeplinkPresenter` 新增 `provider/install` 入口 -- [x] 校验 `v=1` -- [x] 校验 Base64 / JSON / 字段结构 -- [x] built-in 导入按 `id` 匹配 -- [x] custom 导入按 `type` 校验 -- [x] 拒绝 `acp` -- [x] 发送设置页导航与 preview 事件 -- [x] 非法 payload 显示错误通知 - -## T3 设置页预览与确认 - -- [x] 新增 pending import store -- [x] `App.vue` 监听 `PROVIDER_INSTALL` -- [x] built-in 导航到目标 provider -- [x] 新增 `ProviderDeeplinkImportDialog` -- [x] built-in confirm 覆盖 `baseUrl/apiKey` 并自动启用 -- [x] custom confirm 新增启用中的 custom provider -- [x] cancel 只清空 pending preview - -## T4 i18n 与测试 - -- [x] 补齐 provider import 对话框文案 -- [x] 新增 main deeplink 测试 -- [x] 新增 settings app 事件处理测试 -- [x] 新增 provider settings confirm 测试 - -## T5 Manual Playground - -- [x] 新增 `test/manual/deeplink-playground.html` -- [x] 覆盖 `start` -- [x] 覆盖 `mcp/install` -- [x] 覆盖 built-in provider import,排除 `acp` -- [x] 覆盖 custom provider import,排除 `acp` -- [x] 提供 builder、Open、Copy、raw JSON、deeplink 展示 -- [x] 更新 `test/README.md` - -## T6 质量检查 - -- [ ] `pnpm run format` -- [ ] `pnpm run i18n` -- [ ] `pnpm run lint` -- [ ] `pnpm run typecheck` -- [ ] 运行相关测试并记录结果 diff --git a/docs/features/provider-detail-simplification/plan.md b/docs/features/provider-detail-simplification/plan.md deleted file mode 100644 index da8a8b51a..000000000 --- a/docs/features/provider-detail-simplification/plan.md +++ /dev/null @@ -1,18 +0,0 @@ -# Implementation Plan - -## UI - -- Remove status badges from the provider navigation rows and provider detail header. -- Keep the enabled model count badge in the provider detail header. -- Remove the outer rounded/shadow treatment from `ProviderModelManager`. -- Replace the four-tab provider detail layout with three tabs. -- Render `ProviderRateLimitConfig` inside Advanced above provider-specific settings. - -## Compatibility - -The change is presentation-only. Existing provider, model, and rate limit data flows remain unchanged. - -## Validation - -- Run repository-required `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. -- Run `pnpm run typecheck:web` for Vue template safety. diff --git a/docs/features/provider-detail-simplification/spec.md b/docs/features/provider-detail-simplification/spec.md deleted file mode 100644 index df19166c0..000000000 --- a/docs/features/provider-detail-simplification/spec.md +++ /dev/null @@ -1,18 +0,0 @@ -# Provider Detail Simplification - -## User Story - -As a user configuring providers, I want the provider detail page to avoid repeated status tags and heavy model-list card styling, so the page is easier to scan. - -## Acceptance Criteria - -- The repeated Connected status tag is removed from the provider list and provider detail header. -- The provider detail header still shows the enabled model count. -- The Models tab content no longer uses a card-like border or shadow container. -- Limits is merged into Advanced, leaving the visible tabs as Connect, Models, and Advanced. -- Advanced contains rate limit controls first, followed by provider-specific advanced controls when available. - -## Non-goals - -- No changes to provider connection, model enablement, or rate limit persistence behavior. -- No changes to Ollama or Bedrock provider architecture beyond the shared model manager styling. diff --git a/docs/features/provider-detail-simplification/tasks.md b/docs/features/provider-detail-simplification/tasks.md deleted file mode 100644 index fa40041da..000000000 --- a/docs/features/provider-detail-simplification/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Remove Connected/status badges from provider navigation and detail header. -- [x] Simplify Models tab container styling. -- [x] Merge Limits into Advanced. -- [x] Run format, i18n, lint, and web typecheck. diff --git a/docs/features/remote-acp-control/plan.md b/docs/features/remote-acp-control/plan.md deleted file mode 100644 index 06d4b2657..000000000 --- a/docs/features/remote-acp-control/plan.md +++ /dev/null @@ -1,106 +0,0 @@ -# Remote ACP Control Plan - -## Summary - -Implement ACP-aware remote control inside the existing Electron main remote-control flow. The change stays within the current presenter and runner architecture: `RemoteControlPresenter` handles config normalization and sanitization, `RemoteConversationRunner` handles ACP session creation and workdir resolution, command routers expose ACP-specific behavior, and `RemoteSettings.vue` exposes the new configuration surface. - -## Goals - -- Allow Telegram and Feishu to use any enabled agent as the default remote agent. -- Require and resolve a workdir when that default agent is ACP. -- Preserve current DeepChat remote behavior. -- Keep the change additive and compatible with existing bindings and settings. - -## Readiness - -- No open clarification markers remain. -- Scope is intentionally limited to channel-level defaults and ACP-aware session creation. - -## Implementation Decisions - -### 1. Default Agent Sanitization - -- Extend remote default-agent sanitization from “enabled DeepChat only” to “any enabled agent”. -- Fallback order stays deterministic: - - configured candidate - - `deepchat` - - first enabled agent - - literal `deepchat` - -### 2. Workdir Storage And Resolution - -- Add `defaultWorkdir` to both Telegram and Feishu remote settings/runtime config. -- Normalize missing or blank values to `''`. -- Resolve ACP workdir with this chain: - - channel `defaultWorkdir` - - global `configPresenter.getDefaultProjectPath()` - - explicit runtime error - -### 3. ACP Session Creation Path - -- `RemoteConversationRunner.createNewSession(...)` checks the resolved default agent type. -- DeepChat path remains unchanged. -- ACP path calls `createDetachedSession(...)` with: - - `agentId` - - `providerId: 'acp'` - - `modelId: agentId` - - `projectDir: resolvedWorkdir` -- If no workdir is available, the runner throws: - - `ACP agent requires a workdir. Set a Remote default directory or global default directory first.` - -### 4. Router Behavior - -- `/new` continues to create a session through the runner and therefore inherits ACP-aware behavior. -- `/sessions` still scopes to the current bound session agent when present, else the channel default agent. -- `/model` checks whether the current session model is locked by ACP. If locked, it returns: - - `ACP sessions lock the model. Change the channel default agent instead.` -- `/status` includes: - - `Default workdir` - - `Current workdir` - -### 5. Settings UI - -- The default-agent selector shows all enabled agents. -- ACP agents display with a ` (ACP)` suffix. -- Telegram and Feishu settings each expose a `Default directory` input. -- Helper text explains that the field is used only for ACP and falls back to the global default project path. - -## Dependencies - -- `RemoteControlPresenter` -- `RemoteConversationRunner` -- `RemoteCommandRouter` -- `FeishuCommandRouter` -- `RemoteSettings.vue` -- Shared remote-control presenter types - -## Migration And Compatibility - -- No database migration is required. -- Electron Store config is additive and normalized on read/write. -- Existing bindings are not rebound when the default agent or workdir changes. -- Existing remote tool-interaction handling remains attached to the bound session flow. - -## Risks And Mitigations - -- Missing ACP workdir causes remote session creation failure - - Mitigation: explicit error message and `/status` visibility for default/current workdir -- Invalid configured default agent - - Mitigation: sanitize through enabled-agent fallback order -- UI confusion between DeepChat and ACP agents - - Mitigation: ACP label suffix in selector and helper text for default directory - -## Test Strategy - -- Main-process tests - - remote default-agent sanitization accepts enabled ACP agents - - ACP session creation passes provider/model/projectDir - - global default project path is used as fallback - - missing workdir throws the documented error - - ACP `/model` path returns the locked-model message - - `/status` includes default/current workdir -- Renderer tests - - enabled ACP agents appear in the selector - - default workdir persists from the settings form -- Regression tests - - DeepChat remote flows continue to pass unchanged diff --git a/docs/features/remote-acp-control/spec.md b/docs/features/remote-acp-control/spec.md deleted file mode 100644 index fc8fcdca4..000000000 --- a/docs/features/remote-acp-control/spec.md +++ /dev/null @@ -1,50 +0,0 @@ -# Remote ACP Control - -## Summary - -Extend remote control so Telegram and Feishu can create and continue ACP-backed sessions for coding-style tasks. Each channel keeps a single configured default agent. When that default agent is ACP, remote session creation must resolve a workdir before creating the detached session. - -This increment builds on [telegram-remote-control](../../archives/telegram-remote-control/spec.md) and [remote-tool-interactions](../remote-tool-interactions/spec.md) without replacing their scope. - -## User Stories - -- As a remote-control user, I can set an ACP agent as the default remote agent for Telegram or Feishu. -- As a remote-control user, I can configure a per-channel default workdir for ACP sessions. -- As a paired remote user, my first `/new` or plain-text conversation turn can create an ACP session that already knows which workdir to use. -- As a paired remote user, I can inspect the current and default workdir from `/status`. -- As a paired remote user on an ACP-backed session, I get a clear response that `/model` cannot change the session model remotely. - -## Acceptance Criteria - -- The remote settings UI lists all enabled agents in the default-agent selector, including ACP agents. -- ACP agents are visually labeled in the selector so they are distinguishable from DeepChat agents. -- Telegram and Feishu remote settings both persist a `defaultWorkdir` string in Electron Store. -- New remote sessions still use the configured default agent for the channel. -- When the default agent is DeepChat, remote session creation behavior remains unchanged. -- When the default agent is ACP, remote session creation passes `providerId: 'acp'`, `modelId: `, and `projectDir: ` to detached session creation. -- ACP workdir resolution follows `channel.defaultWorkdir -> global default project path -> explicit error`. -- If neither the channel default workdir nor the global default project path is configured, ACP remote session creation is rejected with an actionable error message. -- `/status` for Telegram and Feishu includes both the default workdir and the current session workdir when available. -- `/model` on an ACP-backed bound session returns a locked-model message instead of opening provider/model selection. -- Existing bound sessions remain bound after changing the channel default agent or default workdir. -- Existing DeepChat remote flows, including `/sessions`, `/use`, `/open`, and remote tool interactions, continue to work. - -## Constraints - -- Each remote channel keeps one configured default agent; this feature does not add per-message agent switching. -- ACP remote session creation requires a workdir. -- Remote bot text remains English. -- Config changes stay in Electron Store; no SQLite migration is introduced. - -## Non-Goals - -- No `/agent` remote command. -- No `/workdir` remote command. -- No multi-workspace picker or per-session workspace selection flow. -- No remote provider/model switching for ACP sessions. - -## Compatibility - -- Existing remote bindings remain valid. -- Existing remote settings load even when `defaultWorkdir` is absent and normalize to an empty string. -- DeepChat remains a valid fallback default agent when an invalid configured agent is encountered. diff --git a/docs/features/remote-acp-control/tasks.md b/docs/features/remote-acp-control/tasks.md deleted file mode 100644 index 749715586..000000000 --- a/docs/features/remote-acp-control/tasks.md +++ /dev/null @@ -1,67 +0,0 @@ -# Remote ACP Control Tasks - -## Readiness - -- No open clarification items remain. -- All tasks below map back to the acceptance criteria in [spec.md](./spec.md). - -## T0 Spec Artifacts - -- [x] Create `spec.md`, `plan.md`, and `tasks.md` -- Owner: Remote control maintainer -- Acceptance Criteria: - - The ACP remote-control scope, compatibility, and non-goals are documented. - - Reviewers can trace implementation decisions without reading every changed file. - -## T1 Remote Config And Type Support - -- [x] Add `defaultWorkdir` to Telegram and Feishu remote settings/runtime types -- [x] Normalize and persist `defaultWorkdir` through `RemoteControlPresenter` -- [x] Allow enabled ACP agents to survive remote default-agent sanitization -- Owner: Electron main -- Acceptance Criteria: - - Settings load without migrations. - - ACP is selectable as a valid default remote agent. - -## T2 ACP Session Creation - -- [x] Detect ACP default agents during remote session creation -- [x] Resolve ACP workdir from channel config or global default project path -- [x] Create detached ACP sessions with `providerId`, `modelId`, and `projectDir` -- [x] Reject ACP session creation when no workdir is configured -- Owner: Electron main -- Acceptance Criteria: - - New ACP remote sessions are created with the expected runtime fields. - - Missing workdir produces the documented error. - -## T3 Remote Command Behavior - -- [x] Keep `/sessions` agent scoping based on the bound session or default agent -- [x] Add ACP model-lock handling to `/model` -- [x] Add default/current workdir lines to `/status` -- Owner: Remote routers -- Acceptance Criteria: - - ACP sessions do not open model-selection flows. - - `/status` exposes enough context to debug remote workspace selection. - -## T4 Settings UI And i18n - -- [x] Show all enabled agents in the default-agent selector -- [x] Label ACP agents with `(ACP)` -- [x] Add Telegram and Feishu default-directory inputs -- [x] Add i18n strings for the new settings fields -- Owner: Renderer settings -- Acceptance Criteria: - - Users can configure ACP defaults from the existing Remote settings page. - - The UI still behaves correctly for legacy settings payloads. - -## T5 Tests And Validation - -- [x] Add main-process tests for ACP sanitization, workdir fallback, and ACP session creation -- [x] Add router tests for ACP `/model` locking and `/status` output -- [x] Add renderer tests for ACP agent visibility and default-workdir persistence -- [x] Run targeted main and renderer Vitest suites -- Owner: QA + Remote control maintainer -- Acceptance Criteria: - - Test coverage maps to the new ACP remote-control behavior. - - DeepChat regression paths remain green in the targeted suites. diff --git a/docs/features/remote-block-streaming/plan.md b/docs/features/remote-block-streaming/plan.md deleted file mode 100644 index d79319039..000000000 --- a/docs/features/remote-block-streaming/plan.md +++ /dev/null @@ -1,65 +0,0 @@ -# Remote Block Streaming Plan - -## Summary - -Reuse the shared remote block renderer to derive two remote outputs: - -- `statusText` for the temporary status message -- `text` / `finalText` for the streamed answer message - -Telegram and Feishu both switch from “status message becomes final answer” to “temporary status + persistent streamed answer”. - -## Key Decisions - -- Keep `RemoteConversationSnapshot` fields: - - `statusText` - - `text` - - `finalText` - - compatibility fields: `draftText`, `renderBlocks`, `fullText` -- Redefine remote runtime use of `text`: - - answer-content only - - may update throughout execution -- Continue to build `RemoteRenderableBlock` data for compatibility and local display, but stop using those blocks as the primary remote transport payload. -- Keep deterministic tool-result summarization and final fallback generation. - -## Data Flow - -- `DeepChat` stream accumulation still finalizes narrative/tool/search/error blocks as before. -- `RemoteConversationRunner` parses assistant blocks and builds: - - `statusText` - - `text` as answer-only streamed content - - `finalText` - - compatibility fields (`draftText`, `renderBlocks`, `fullText`) -- Telegram runtime: - - creates one temporary status message - - creates one streamed answer message when answer content first appears - - edits the status message as `statusText` changes - - edits the streamed answer message as `text` grows - - deletes the status message after syncing the streamed answer to `finalText` -- Feishu runtime: - - mirrors the same two-message lifecycle with text updates and message deletion -- Pending interactions: - - set the status message to waiting - - keep any already-streamed answer content visible - - continue using the existing prompt/card delivery - -## Risks And Mitigations - -- Long answers can exceed platform message limits mid-stream - - Mitigation: split answer text into chunks, keep earlier chunks fixed, and continue editing only the newest tail chunk -- A resumed pending interaction could accidentally create duplicate temporary status messages - - Mitigation: keep endpoint-scoped in-memory delivery state with `sourceMessageId`, `statusMessageId`, and `contentMessageIds` -- Status-message deletion may fail due to platform constraints - - Mitigation: treat deletion as best-effort and always clear local transient state - -## Test Strategy - -- Block renderer unit tests for answer-only `text`, status extraction, and final fallback behavior -- Runner tests for `text` / `statusText` / `finalText` generation across reasoning, writing, and waiting states -- Telegram runtime tests for: - - separate status and answer messages - - temporary status deletion - - streamed answer updates - - long-answer editable tail behavior - - pending prompt handling -- Feishu runtime tests for the same lifecycle plus card fallback behavior diff --git a/docs/features/remote-block-streaming/spec.md b/docs/features/remote-block-streaming/spec.md deleted file mode 100644 index 2ee18a823..000000000 --- a/docs/features/remote-block-streaming/spec.md +++ /dev/null @@ -1,41 +0,0 @@ -# Remote Block Streaming - -## Summary - -Keep the shared remote block renderer, but move Telegram and Feishu to a dual-track remote delivery model: - -- one temporary status message that only shows execution state -- one answer-text message that streams user-visible answer content - -The status message is deleted when the turn finishes. The answer-text message remains as the remote transcript for that turn. - -## User Stories - -- As a Telegram remote user, I can see answer text appear progressively without also receiving reasoning/tool/search transcript spam. -- As a Feishu remote user, I get the same streaming answer experience with a separate execution-status indicator. -- As a remote user, I can still receive pending permission/question prompts as separate actionable messages while preserving any answer text that has already streamed. -- As a remote user, I keep the final answer in chat history while the temporary status message disappears after the turn ends. - -## Acceptance Criteria - -- Remote snapshot generation continues to expose `statusText`, `text`, and `finalText`, while keeping `draftText`, `renderBlocks`, and `fullText` for compatibility. -- During execution, `text` contains only streamable answer content from `content` blocks; `reasoning_content`, tool, and search transcript text never enters the answer stream. -- `statusText` still reflects the current phase, such as thinking, calling a tool, reviewing search results, writing, or waiting for user input. -- Telegram creates a temporary status message and a separate streamed answer message for a normal assistant turn. -- Feishu creates a temporary status message and a separate streamed answer message for a normal assistant turn. -- The status message is updated in place during execution and deleted when the turn completes, errors, times out, or produces no response. -- The answer-text message is updated in place while it fits within the platform limit. When it grows beyond the limit, earlier chunks remain fixed and only the newest tail chunk stays editable. -- Pending interactions set the status message to a waiting state, preserve already-streamed answer text, and continue to use the existing Telegram prompt / Feishu card flow. -- `/status` no longer exposes Telegram stream mode information. - -## Constraints - -- No extra model call is allowed to summarize tool output or synthesize status text. -- Desktop-local message rendering and persistence remain unchanged. -- Generated image blocks are persisted into the session workspace and exposed to remote runtimes as local image assets. Channels that support image messages send the image first and fall back to a local path text reply when upload fails. - -## Compatibility - -- Existing remote command routing and pending interaction behavior remain unchanged. -- Old code paths that still read `draftText`, `renderBlocks`, or `fullText` continue to have fallback values. -- Legacy `TelegramStreamMode` config remains readable for compatibility, but remote delivery no longer changes behavior based on it. diff --git a/docs/features/remote-block-streaming/tasks.md b/docs/features/remote-block-streaming/tasks.md deleted file mode 100644 index e32bd0916..000000000 --- a/docs/features/remote-block-streaming/tasks.md +++ /dev/null @@ -1,37 +0,0 @@ -# Remote Block Streaming Tasks - -## T0 Spec - -- [x] Update the spec, plan, and tasks artifacts for dual-track remote delivery - -## T1 Snapshot Contract - -- [x] Keep `statusText`, `text`, and `finalText` in `RemoteConversationSnapshot` -- [x] Redefine `text` as answer-only streamed content for remote runtimes -- [x] Keep `draftText`, `renderBlocks`, and `fullText` as compatibility fields - -## T2 Compact Extraction - -- [x] Derive short status strings from assistant blocks -- [x] Derive streamed answer text from `content` blocks only -- [x] Keep `finalText` limited to final answer or terminal fallback - -## T3 Telegram Delivery - -- [x] Create separate temporary status and streamed answer messages -- [x] Delete the status message after final answer sync -- [x] Keep pending interaction prompts separate and preserve streamed answer text -- [x] Support long-answer chunk growth with an editable tail - -## T4 Feishu Delivery - -- [x] Create separate temporary status and streamed answer messages -- [x] Delete the status message after final answer sync -- [x] Keep pending interaction cards/fallback text separate and preserve streamed answer text -- [x] Support long-answer chunk growth with an editable tail - -## T5 Validation - -- [x] Add formatter, runner, binding-store, Telegram, and Feishu regression tests -- [x] Keep `/status` free of stream-mode output and retain legacy stream-mode config as a compatibility no-op -- [x] Run repo quality gates and capture residual issues diff --git a/docs/features/remote-discord-lark/plan.md b/docs/features/remote-discord-lark/plan.md deleted file mode 100644 index 308c25b14..000000000 --- a/docs/features/remote-discord-lark/plan.md +++ /dev/null @@ -1,63 +0,0 @@ -# Implementation Plan - -## Architecture - -- Shared presenter contracts extend `RemoteChannelId`, pairing snapshots, settings, and status unions with `discord`. -- Main-process config normalization extends `remoteControl` with: - - `discord` - - `feishu.brand` -- `RemoteBindingStore` remains the single source of truth for pair codes, paired principals, bindings, and delivery-state persistence. -- `RemoteConversationRunner` keeps the existing session/binding model and now resolves channel-specific ACP default workdirs for Telegram, Feishu/Lark, QQBot, Discord, and Weixin iLink. - -## Discord Runtime Design - -- Transport: - - REST for bot identity, slash command registration, replies, edits, typing, and interaction responses - - Gateway for `READY`, `RESUMED`, `MESSAGE_CREATE`, and `INTERACTION_CREATE` -- Flow: - - `DiscordGatewaySession` manages heartbeat, reconnect, and resume - - `DiscordParser` normalizes gateway payloads into DeepChat inbound messages - - `DiscordAuthGuard` enforces paired-channel authorization - - `DiscordCommandRouter` reuses the existing remote conversation runner and command surface - - `DiscordRuntime` handles delivery, slash command responses, and throttled message edits - -## Feishu / Lark Design - -- Keep the channel id as `feishu`. -- Add `brand: 'feishu' | 'lark'`. -- Pass the resolved domain into both: - - `Lark.Client` - - `Lark.WSClient` - -## Renderer Design - -- `RemoteSettings.vue` - - add a Discord tab - - add Feishu/Lark brand selection in the Feishu tab - - show per-channel default workdir fields where supported - - keep Telegram settings self-contained in the remote page - - keep Hooks controls out of the remote settings page -- `WindowSideBar.vue` - - aggregate remote channel status through a channel map instead of hardcoded branches - -## Data / Migration - -- Feishu configs without `brand` normalize to `feishu`. -- Legacy Discord root-level config is normalized into `remoteControl.discord`. -- Existing Feishu bindings and pairing data stay under `feishu`. -- Discord endpoint keys use: - - `discord:dm:` - - `discord:channel:` - -## Test Strategy - -- Main: - - config normalization and migration for Discord - - Discord parser, auth guard, adapter - - presenter separation between Discord remote settings and Hooks config - - Feishu `brand = lark` domain mapping - - remote conversation runner channel-specific workdir resolution -- Renderer: - - Discord tab and controls - - Feishu brand selector without separate Lark tab - - sidebar remote status aggregation including Discord diff --git a/docs/features/remote-discord-lark/spec.md b/docs/features/remote-discord-lark/spec.md deleted file mode 100644 index 6bba9384e..000000000 --- a/docs/features/remote-discord-lark/spec.md +++ /dev/null @@ -1,41 +0,0 @@ -# Remote Discord + Feishu/Lark - -## Summary - -Extend the existing remote-control system with a built-in `discord` channel and add `lark` brand compatibility to the existing `feishu` channel. This is an incremental extension of the current remote multi-channel design, not a redesign of the remote-control framework. - -## User Stories - -- As a DeepChat desktop user, I want to control DeepChat from Discord with a bot token, so I can use DMs, channel mentions, and slash commands instead of webhooks. -- As a DeepChat desktop user, I want Discord remote control settings to stay self-contained under remote-control config, so remote changes do not depend on Hooks state. -- As a DeepChat desktop user, I want to use either Feishu or Lark from the same remote-control tab, so I do not need a duplicate channel implementation. -- As a DeepChat desktop user, I want Discord, Feishu/Lark, QQBot, Telegram, and Weixin iLink to appear consistently in the remote settings UI and sidebar status aggregation. - -## Acceptance Criteria - -- `discord` is available as a built-in remote channel across shared types, presenter APIs, runtime config, renderer settings, and sidebar status. -- `remoteControl.discord` stores Discord bot token, enabled flag, default agent, default workdir, paired channel ids, bindings, pairing state, and fatal error state. -- Discord remote control only reads and writes `remoteControl.discord`. -- Discord remote control supports `/start`, `/help`, `/pair`, `/new`, `/sessions`, `/use`, `/stop`, `/open`, `/pending`, `/model`, and `/status` through both slash commands and text commands. -- Discord authorization follows the existing DeepChat pairing model: - - DMs and guild channels pair by `/pair ` - - unpaired channels can only use `/start`, `/help`, and `/pair` - - non-mentioned guild text messages do not enter the conversation flow -- The renderer shows a dedicated Discord tab in remote settings, and that tab does not expose Hooks configuration fields. -- The Feishu tab supports a `brand` selection of `feishu | lark` without adding a separate `lark` channel. -- Feishu/Lark bindings, pairing identifiers, endpoint keys, and status channel ids remain under `feishu`. -- Existing configs without Feishu `brand` default to `feishu`. -- Existing configs without Discord state normalize safely to disabled defaults. - -## Non-goals - -- No standalone `lark` remote channel entity. -- No custom Lark domain input. -- No Hooks migration for Discord remote control. -- No redesign of the remote binding or session-runner architecture. - -## Constraints - -- Reuse the current presenter/binding-store/runtime abstractions. -- Prefer compatibility with existing saved settings and bindings. -- Keep user-facing strings in i18n. diff --git a/docs/features/remote-discord-lark/tasks.md b/docs/features/remote-discord-lark/tasks.md deleted file mode 100644 index 6b552e555..000000000 --- a/docs/features/remote-discord-lark/tasks.md +++ /dev/null @@ -1,14 +0,0 @@ -# Tasks - -1. Extend shared remote-control types with Discord settings, status, pairing, and Feishu brand. -2. Extend main-process config normalization and binding helpers for Discord endpoint keys and pairing state. -3. Add Discord parser, auth guard, command router, gateway session, REST client, runtime, and adapter. -4. Wire Discord into `RemoteControlPresenter`, channel registry, status computation, and runtime rebuild flows. -5. Add Feishu/Lark brand handling in Feishu adapter/client setup. -6. Update `RemoteSettings.vue` with: - - Discord tab - - Feishu/Lark brand selector - - default workdir controls -7. Update `WindowSideBar.vue` remote status aggregation to be channel-map based. -8. Add or update tests for Discord main-process behavior, Feishu/Lark domain handling, renderer settings, and sidebar status. -9. Run format, i18n sync/checks, lint, and test suites. diff --git a/docs/features/remote-multi-channel/plan.md b/docs/features/remote-multi-channel/plan.md deleted file mode 100644 index 03f0585b6..000000000 --- a/docs/features/remote-multi-channel/plan.md +++ /dev/null @@ -1,255 +0,0 @@ -# Remote Multi-Channel Foundation Plan - -Feature: `remote-multi-channel` -Spec: [spec.md](./spec.md) - -## Summary - -This plan captures the implementation and rollout shape for the `remote-multi-channel` -foundation described in [spec.md](./spec.md). The feature keeps Telegram, Discord, and -Feishu/Lark on the shared remote adapter boundary while shipping QQBot and WeChat iLink as -built-in channels registered through the same `ChannelManager` path. - -## Goals - -- Expose a registry-driven multi-channel remote presenter contract that includes `qqbot` and - `weixin-ilink`. -- Keep existing Telegram, Discord, and Feishu/Lark behavior compatible while moving renderer - callers to descriptor-driven UI. -- Ship official-only QQBot and WeChat iLink transports for the first release scope in the spec. -- Preserve `RemoteBindingStore`, `RemoteConversationRunner`, and `remoteBlockRenderer` as the - source-of-truth pipeline for bindings, session orchestration, and rendered delivery text. - -## Readiness - -- Companion artifacts exist in `docs/features/remote-multi-channel/`: - - `spec.md` - - `plan.md` - - `tasks.md` -- No open `[NEEDS CLARIFICATION]` markers remain in [spec.md](./spec.md). -- Discord-specific runtime details continue to live in `docs/features/remote-discord-lark/`. - -## Official Constraints To Preserve - -- QQ official C2C `user_openid` and group `member_openid` stay separate identity spaces. -- QQ group authorization remains separate from C2C pairing and only reacts to - `GROUP_AT_MESSAGE_CREATE` after explicit `/pair`. -- QQBot uses official transport primitives only: - - `POST https://bots.qq.com/app/getAppAccessToken` - - `GET https://api.sgroup.qq.com/gateway` - - official gateway `identify` / `resume` / heartbeat flow - - official C2C and group send endpoints -- WeChat iLink stays on the official QR-login plus long-poll flow only. -- The first WeChat iLink delivery remains owner-account-only and does not add a collaborator - allowlist UI. - -## Implementation Decisions - -### 1. Shared Presenter Contract - -- Extend shared presenter types with: - - `RemoteChannelId` - - `RemoteChannelDescriptor` - - per-channel generic settings / status methods - - QQBot and WeChat iLink settings, status, and pairing/login types -- Add `listRemoteChannels()` to `IRemoteControlPresenter` and the preload bridge. -- Keep Telegram compatibility methods available during the registry migration so existing - renderer callers keep working. - -### 2. Channel Registration And Runtime Ownership - -- `ChannelManager` remains the single registration point for built-in channels. -- Register: - - `telegram` - - `discord` - - `feishu` - - `qqbot` - - `weixin-ilink` -- `RemoteControlPresenter` rebuilds channel runtimes through descriptors and registered - adapters instead of adding new presenter-owned branches for QQBot or WeChat iLink. - -### 3. Shared Persistence, Session, And Rendering Pipeline - -- `RemoteBindingStore` remains the source of truth for: - - pair codes - - paired principals - - endpoint bindings - - delivery state - - WeChat iLink per-account runtime credentials and bindings -- QQBot settings persist under `remoteControl.qqbot`. -- WeChat iLink settings persist under `remoteControl.weixinIlink` with per-account runtime data - instead of flattening everything into a generic `channels` map. -- `RemoteConversationRunner` remains the only path that creates, reuses, and binds remote - sessions for QQBot and WeChat iLink. -- `remoteBlockRenderer` remains the only renderer for draft, final, trace, status, and block - text that outbound adapters deliver. - -### 4. QQBot Runtime Delivery - -- Implement `QQBotAdapter` on top of the shared adapter surface. -- Add official QQBot modules for: - - access-token acquisition - - gateway session management - - parser - - auth guard - - command router - - runtime -- First-release scope stays limited to: - - C2C direct messages - - group `@bot` messages - - text-only passive replies -- Group authorization is stored separately from paired C2C identities because the two QQ - identity spaces are not interchangeable. - -### 5. WeChat iLink Runtime Delivery - -- Implement `WeixinIlinkAdapter` on top of the shared adapter surface. -- Add official iLink modules for: - - QR-login initiation - - QR status polling with redirect-host handling - - `getupdates` long polling - - `sendmessage` - - `getconfig` - - `sendtyping` -- Run one adapter per connected account from day one. -- Persist `bot_token`, `baseUrl`, owner user id, and bindings per connected account. -- Enforce owner-account-only authorization in the first release. - -### 6. Renderer Migration - -- Remote settings overview cards and tab headers are driven from channel descriptors. -- Sidebar remote-status aggregation reads implemented built-in channel descriptors instead of a - hardcoded Telegram / Feishu pair. -- Built-in channel panels remain explicit for usability: - - Telegram - - Feishu/Lark - - QQBot - - WeChat iLink -- The shared registry contract also covers the shipped Discord runtime from - `remote-discord-lark`. - -## Milestones - -### M0 Readiness And Spec Hygiene - -- Confirm `spec.md`, `plan.md`, and `tasks.md` all exist under `remote-multi-channel`. -- Confirm no `[NEEDS CLARIFICATION]` markers remain in [spec.md](./spec.md). -- Align terminology across the feature to: - - `qqbot` - - `weixin-ilink` - - `remoteControl.qqbot` - - `remoteControl.weixinIlink` - -### M1 Shared Contract And Registry - -- Extend shared remote-control types and presenter methods. -- Add `listRemoteChannels()` and descriptor exposure. -- Register QQBot and WeChat iLink through `ChannelManager`. -- Preserve Telegram-only compatibility shims during migration. - -Exit criteria: - -- `IRemoteControlPresenter` exposes generic multi-channel APIs. -- `RemoteChannelId` includes `telegram`, `discord`, `feishu`, `qqbot`, and `weixin-ilink`. - -### M2 QQBot Runtime - -- Ship `QQBotAdapter` plus official HTTP/gateway/runtime modules. -- Persist QQBot config and pairing/group-authorization state under `remoteControl.qqbot`. -- Route QQBot replies through the shared session and rendering pipeline. - -Exit criteria: - -- C2C and group `@bot` messages can trigger text-only passive replies. -- QQ group authorization is independent from C2C pairing. - -### M3 WeChat iLink Runtime - -- Ship `WeixinIlinkAdapter` plus official QR-login and long-poll modules. -- Persist per-account WeChat iLink runtime credentials, bindings, and owner identity under - `remoteControl.weixinIlink`. -- Start one runtime per connected account. - -Exit criteria: - -- QR login works through the official flow. -- Owner-account-only direct replies work for connected accounts. - -### M4 Renderer And UX Migration - -- Convert overview cards, tab headers, and sidebar status aggregation to channel descriptors. -- Keep explicit built-in panels for QQBot and WeChat iLink settings. -- Preserve existing Telegram / Discord / Feishu UX and compatibility behavior. - -Exit criteria: - -- Renderer no longer depends on hardcoded Telegram / Feishu-only branching for overview and - sidebar status. -- New channels are configurable from the existing Remote settings page. - -### M5 QA, Compatibility, And Rollout - -- Run focused main-process and renderer regression suites. -- Verify config compatibility and saved-setting normalization. -- Validate official-constraint behavior for QQBot and WeChat iLink. - -Exit criteria: - -- Acceptance criteria in [spec.md](./spec.md) are covered by tests or targeted manual QA. -- Compatibility guarantees remain intact for legacy Telegram / Discord / Feishu callers. - -## Rollout Steps - -1. Land shared presenter and descriptor contracts first while keeping Telegram compatibility - methods callable. -2. Register QQBot and WeChat iLink through `ChannelManager` before switching renderer status - aggregation to the descriptor path. -3. Bring up QQBot runtime support with official token, gateway, auth, and passive-reply flows. -4. Bring up WeChat iLink QR login and multi-account runtime support with owner-only - authorization. -5. Switch Remote settings overview cards, tab headers, and sidebar aggregation to the - descriptor-driven implementation. -6. Run compatibility and regression validation before treating the feature as the preferred - multi-channel path. - -## QA And Compatibility Checks - -- Shared-contract checks - - `listRemoteChannels()` is exposed through the shared presenter bridge. - - `RemoteChannelId` includes `qqbot` and `weixin-ilink`. -- Compatibility checks - - Existing Telegram and Feishu saved settings still load without migration breakage. - - Existing Telegram hook test API remains valid. - - Existing renderer callers that still use Telegram-only compatibility methods continue to - work. - - Discord continues to participate in the shared registry surface without regressing the - dedicated runtime behavior tracked in `remote-discord-lark`. -- QQBot checks - - C2C pairing and group authorization are stored and tested separately. - - Only `GROUP_AT_MESSAGE_CREATE` group control is accepted. - - Passive replies stay text-only and use official send semantics. -- WeChat iLink checks - - QR login, QR status polling, and redirect-host handling follow the official flow. - - Owner-account-only authorization is enforced. - - Multi-account runtime separation keeps credentials and bindings isolated. -- Shared pipeline checks - - `RemoteBindingStore` reloads and persists QQBot and WeChat iLink data correctly. - - `RemoteConversationRunner` remains the only session-binding path. - - `remoteBlockRenderer` output stays the delivery source of truth for remote adapters. -- Quality gates - - `pnpm run format` - - `pnpm run i18n` - - `pnpm run lint` - - `pnpm run typecheck` - -## Risks And Mitigations - -- QQ identity-space mismatch can cause incorrect authorization assumptions. - - Mitigation: keep separate persisted state and separate router checks for C2C vs group - control. -- WeChat iLink multi-account state can leak across accounts if runtime ownership is not isolated. - - Mitigation: keep per-account credentials, bindings, and runtime instances under - `remoteControl.weixinIlink`. -- Renderer migration can accidentally regress legacy Telegram / Feishu / Discord surfaces. - - Mitigation: keep compatibility shims during rollout and verify descriptor-driven UI with - focused renderer regression tests. diff --git a/docs/features/remote-multi-channel/spec.md b/docs/features/remote-multi-channel/spec.md deleted file mode 100644 index 92447ed72..000000000 --- a/docs/features/remote-multi-channel/spec.md +++ /dev/null @@ -1,88 +0,0 @@ -# Remote Multi-Channel Foundation - -## Summary - -DeepChat remote control now has a built-in adapter framework and a registry-driven renderer contract. The shipped built-in channel surface now covers Telegram, Discord, Feishu/Lark, QQ Bot Open Platform, and WeChat iLink. This spec captures the shared multi-channel foundation plus the QQBot and WeChat iLink delivery, while the Discord-specific behavior is detailed separately in `remote-discord-lark`. - -The design basis for QQBot follows Tencent official documentation only: - -- access token and HTTP auth -- WebSocket gateway and intents -- official `C2C_MESSAGE_CREATE` and `GROUP_AT_MESSAGE_CREATE` events -- official `/v2/users/{openid}/messages` and `/v2/groups/{group_openid}/messages` send APIs - -The WeChat iLink design basis follows Tencent official package behavior only: - -- fixed QR base URL and official QR login flow -- `get_bot_qrcode` / `get_qrcode_status` polling with redirect-host handling -- `getupdates` long polling -- `sendmessage`, `getconfig`, and `sendtyping` -- multi-account runtime separation from day one - -## User Stories - -- As a desktop user, I can configure Telegram, Discord, Feishu/Lark, QQBot, and WeChat iLink remote control from one Remote settings page. -- As a maintainer, I can add future built-in channels by registering descriptors and adapters instead of hardcoding presenter branches. -- As a maintainer, I can keep Telegram, Discord, and Feishu/Lark behavior aligned through the same adapter boundary while extending the channel set with QQBot and WeChat iLink. -- As a WeChat iLink user, I can connect an official bot account by QR login and manage multiple connected accounts from the built-in settings page. - -## Acceptance Criteria - -- `IRemoteControlPresenter` exposes `listRemoteChannels()` and generic per-channel settings / status methods that include `qqbot` and `weixin-ilink`. -- `RemoteChannelId` includes: - - `telegram` - - `discord` - - `feishu` - - `qqbot` - - `weixin-ilink` -- Renderer remote UI is registry-driven for: - - overview cards - - tab headers - - sidebar remote status aggregation -- Existing Telegram and Feishu runtime behavior remains unchanged, and the same multi-channel adapter surface also supports the shipped Discord runtime described in `remote-discord-lark`. -- A built-in `QQBotAdapter` exists and is registered through `ChannelManager`. -- A built-in `WeixinIlinkAdapter` exists and is registered through `ChannelManager`. -- QQBot uses official transport primitives only: - - `POST https://bots.qq.com/app/getAppAccessToken` - - `GET https://api.sgroup.qq.com/gateway` - - official WebSocket `identify` / `resume` / heartbeat flow - - official C2C and group message send endpoints -- QQBot first-release scope is: - - C2C direct messages - - group `@bot` messages - - passive text replies plus generated image replies when rich media upload succeeds -- WeChat iLink first-release scope is: - - official QR login - - multi-account management in settings - - owner-account-only remote control - - direct text replies plus generated image replies when the iLink media item is accepted -- Built-in remotes download inbound files and images into the bound session workspace, then attach the local files to the agent message context. -- `RemoteBindingStore`, `RemoteConversationRunner`, and `RemoteBlockRenderer` stay the source of truth for bindings, sessions, and rendered delivery text. -- Remote settings persist QQBot data under `remoteControl.qqbot` without flattening everything into a generic `channels` map. -- Remote settings persist WeChat iLink data under `remoteControl.weixinIlink`, including per-account runtime credentials and bindings. - -## Official Constraints - -- QQ official C2C `user_openid` and group `member_openid` are different identity spaces, so a direct-message-paired user cannot be inferred from a group event. -- Because of that constraint, QQBot group authorization is handled separately from C2C user pairing. -- This iteration stores: - - paired C2C user ids - - internally authorized group ids -- Group control only reacts to `GROUP_AT_MESSAGE_CREATE` and only after explicit group authorization with `/pair`. -- WeChat iLink login and runtime follow the official QR + long-poll flow only; no personal WeChat bridge or unofficial protocol is used. -- This first WeChat iLink delivery authorizes only the owner account returned by QR login; it does not implement a secondary allowlist UI yet. - -## Non-Goals - -- No OneBot, go-cqhttp, unofficial QQ bridges, or personal-WeChat bridges. -- No Slack runtime in this iteration. -- No secondary WeChat collaborator allowlist or shared-account UI in this iteration. -- No third-party plugin execution or installation UI in this iteration. - -## Compatibility - -- Existing Telegram and Feishu saved settings remain valid. -- New WeChat iLink saved settings live beside existing remote settings without changing Telegram / Feishu / QQBot schema shapes. -- Existing Telegram hook test API remains valid. -- Existing renderer callers that use Telegram-only compatibility methods continue to work. -- New generic remote presenter methods become the preferred path for renderer code. diff --git a/docs/features/remote-multi-channel/tasks.md b/docs/features/remote-multi-channel/tasks.md deleted file mode 100644 index c0a71798b..000000000 --- a/docs/features/remote-multi-channel/tasks.md +++ /dev/null @@ -1,125 +0,0 @@ -# Remote Multi-Channel Foundation Tasks - -Feature: `remote-multi-channel` -Spec: [spec.md](./spec.md) -Plan: [plan.md](./plan.md) - -## Readiness - -- [x] `T0.1` Confirm `spec.md`, `plan.md`, and `tasks.md` exist in - `docs/features/remote-multi-channel/`. - Owner: Remote control maintainer - Effort: XS -- [x] `T0.2` Confirm no `[NEEDS CLARIFICATION]` markers remain in [spec.md](./spec.md). - Owner: Remote control maintainer - Effort: XS - -## Epic E1 Shared Contract And Registry - -- [x] `T1.1` Extend shared presenter types with `RemoteChannelId`, channel descriptors, and - generic per-channel settings / status methods that include `qqbot` and `weixin-ilink`. - Owner: Electron main - Effort: M -- [x] `T1.2` Add `listRemoteChannels()` to the remote presenter and preload/shared bridge - surface. - Owner: Electron main + preload - Effort: S -- [x] `T1.3` Register `telegram`, `discord`, `feishu`, `qqbot`, and `weixin-ilink` through - `ChannelManager` instead of adding hardcoded presenter-owned runtime branches. - Owner: Electron main - Effort: M -- [x] `T1.4` Keep Telegram-only compatibility methods callable during the registry migration. - Owner: Electron main + renderer - Effort: S - -## Epic E2 QQBot Runtime - -- [x] `T2.1` Implement `QQBotAdapter` and the official QQBot transport modules: - access-token client, gateway session manager, parser, auth guard, command router, and - runtime. - Owner: Electron main - Effort: L -- [x] `T2.2` Restrict the first-release QQBot scope to C2C direct messages, group `@bot` - messages, and text-only passive replies. - Owner: Electron main - Effort: M -- [x] `T2.3` Persist QQBot runtime data under `remoteControl.qqbot` and keep C2C pairing - separate from group authorization because QQ identity spaces differ. - Owner: Electron main - Effort: M - -## Epic E3 WeChat iLink Runtime - -- [x] `T3.1` Implement `WeixinIlinkAdapter` and the official QR-login plus long-poll runtime - modules. - Owner: Electron main - Effort: L -- [x] `T3.2` Persist WeChat iLink per-account `bot_token`, `baseUrl`, owner user id, - bindings, and runtime credentials under `remoteControl.weixinIlink`. - Owner: Electron main - Effort: M -- [x] `T3.3` Enforce owner-account-only remote control for the first WeChat iLink release while - keeping multi-account runtime separation from day one. - Owner: Electron main - Effort: M - -## Epic E4 Shared Persistence, Session, And Rendering Pipeline - -- [x] `T4.1` Keep `RemoteBindingStore` as the source of truth for QQBot and WeChat iLink pair - state, endpoint bindings, delivery state, and account-scoped persistence. - Owner: Electron main - Effort: M -- [x] `T4.2` Keep `RemoteConversationRunner` as the only session-creation and session-binding - path used by QQBot and WeChat iLink command routers. - Owner: Electron main - Effort: M -- [x] `T4.3` Keep `remoteBlockRenderer` as the source of truth for rendered delivery text, - including draft, final, trace, status, and block rendering consumed by new adapters. - Owner: Electron main - Effort: S -- [x] `T4.4` Ensure QQBot and WeChat iLink settings remain additive and do not collapse into a - generic `channels` map. - Owner: Electron main - Effort: S - -## Epic E5 Renderer And Settings - -- [x] `T5.1` Drive Remote settings overview cards and tab headers from channel descriptors. - Owner: Renderer - Effort: M -- [x] `T5.2` Update sidebar remote-status aggregation to use implemented built-in channel - descriptors instead of a hardcoded Telegram / Feishu pair. - Owner: Renderer - Effort: S -- [x] `T5.3` Add or maintain built-in settings panels for QQBot and WeChat iLink, including QR - login and account-management controls for WeChat iLink. - Owner: Renderer - Effort: L -- [x] `T5.4` Preserve existing Telegram, Discord, and Feishu/Lark behavior while the renderer - migrates to the descriptor-driven contract. - Owner: Renderer + Electron main - Effort: S - -## Epic E6 QA, Compatibility, And Rollout - -- [x] `T6.1` Add or maintain focused QQBot tests for parser, auth guard, command router, - adapter, and presenter registration. - Owner: QA + Remote control maintainer - Effort: M -- [x] `T6.2` Add or maintain focused WeChat iLink tests for QR login flow, per-account status - rendering, adapter behavior, and presenter persistence. - Owner: QA + Remote control maintainer - Effort: M -- [x] `T6.3` Update presenter and renderer regression coverage for descriptor-driven - multi-channel behavior, including sidebar aggregation and compatibility shims. - Owner: QA + Remote control maintainer - Effort: M -- [x] `T6.4` Verify compatibility promises from [spec.md](./spec.md): - Telegram and Feishu saved settings remain valid, the Telegram hook test API remains valid, - and legacy Telegram-only compatibility methods still work. - Owner: QA + Remote control maintainer - Effort: S -- [x] `T6.5` Run `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, and - `pnpm run typecheck`, plus focused main-process and renderer Vitest suites. - Owner: QA + Remote control maintainer - Effort: S diff --git a/docs/features/remote-process-log/plan.md b/docs/features/remote-process-log/plan.md deleted file mode 100644 index c3baa7bb5..000000000 --- a/docs/features/remote-process-log/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Remote Process Log Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/remote-process-log/spec.md b/docs/features/remote-process-log/spec.md deleted file mode 100644 index 73fe4c94f..000000000 --- a/docs/features/remote-process-log/spec.md +++ /dev/null @@ -1,31 +0,0 @@ -# Remote Process Log - -## Summary - -Replace the temporary remote status message with an ordered, persistent remote transcript for Telegram and Feishu. Each normal assistant turn keeps: - -- one ordered message sequence derived from assistant blocks -- persistent process-log segments for tool-call progress -- answer segments that stay in the same order as the desktop transcript - -Process segments survive after the turn completes and are not erased. New answer and process phases may create new remote messages instead of rewriting the earliest message in the turn. - -## Acceptance Criteria - -- `RemoteConversationSnapshot` exposes `deliverySegments`, ordered exactly as remote delivery should appear. -- `deliverySegments` contain only `process`, `answer`, and `terminal` segments. -- Process segments contain one line per completed tool-call argument payload using the format `EMOJI raw_tool_name: "preview"`. -- Remote trace preview reuses the desktop tool-summary extraction rule: parse JSON when possible, summarize the first meaningful value, flatten to one line, then truncate for remote delivery. -- Failed tool calls append an extra `❌ raw_tool_name: "error preview"` line inside the same process segment. -- Consecutive tool calls merge into one process segment until an answer block appears. -- Consecutive answer blocks merge into one answer segment, even if ignored remote-only block types appear between them. -- Telegram and Feishu stop depending on separate trace/content tracks for normal turns and instead sync ordered delivery segments by key. -- New later segments append after earlier ones; completion must not compact all answer text back into the first answer message. -- Tool-only turns keep only the process segment and do not append the generic no-response fallback when the process log already explains the turn. -- Timeout or terminal failure text appends as a trailing terminal segment only when existing answer/process segments do not already express that final state. - -## Non-Goals - -- Showing reasoning content in remote transcripts. -- Showing tool result bodies, search result bodies, or image payloads in the process log. -- Adding a user-facing settings toggle for remote transcript mode in this increment. diff --git a/docs/features/remote-process-log/tasks.md b/docs/features/remote-process-log/tasks.md deleted file mode 100644 index 71b49becd..000000000 --- a/docs/features/remote-process-log/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Remote Process Log Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/remote-system-group/plan.md b/docs/features/remote-system-group/plan.md deleted file mode 100644 index 23e113d1a..000000000 --- a/docs/features/remote-system-group/plan.md +++ /dev/null @@ -1,9 +0,0 @@ -# Implementation Plan - -## Change - -- Update the `settings-remote` navigation item group from `tools` to `system`. - -## Validation - -- Run format, i18n, lint, and web typecheck. diff --git a/docs/features/remote-system-group/spec.md b/docs/features/remote-system-group/spec.md deleted file mode 100644 index f2057bbb4..000000000 --- a/docs/features/remote-system-group/spec.md +++ /dev/null @@ -1,15 +0,0 @@ -# Remote System Group - -## User Story - -As a settings user, I want Remote settings to appear under the System group, so app-level remote control configuration is grouped with other system controls. - -## Acceptance Criteria - -- Remote settings appears in the System sidebar group. -- The Remote route, label, and page behavior remain unchanged. - -## Non-goals - -- No changes to remote integrations or runtime behavior. -- No changes to route paths or i18n keys. diff --git a/docs/features/remote-system-group/tasks.md b/docs/features/remote-system-group/tasks.md deleted file mode 100644 index af21d5050..000000000 --- a/docs/features/remote-system-group/tasks.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Move Remote settings to the System group. -- [x] Run validation commands. diff --git a/docs/features/remote-tool-interactions/plan.md b/docs/features/remote-tool-interactions/plan.md deleted file mode 100644 index 096970dd2..000000000 --- a/docs/features/remote-tool-interactions/plan.md +++ /dev/null @@ -1,125 +0,0 @@ -# Remote Tool Interactions Plan - -## Summary - -Implement a structured remote interaction loop for Telegram and Feishu so remote endpoints can resolve paused permission and question interactions without falling back to a generic desktop-only notice. The feature stays inside Electron main and reuses the existing `RemoteConversationRunner`, `RemoteCommandRouter`, `FeishuCommandRouter`, and `agentSessionPresenter.respondToolInteraction(...)` flow. - -## Goals - -- Expose `RemoteConversationSnapshot.pendingInteraction` as the canonical paused-interaction state for remote delivery. -- Preserve the current detached-session and bound-endpoint model without adding renderer IPC. -- Let Telegram resolve interactions with inline buttons plus text fallback. -- Let Feishu render interaction cards and fall back to complete plain-text prompts when card delivery fails. -- Keep command/session state safe while an interaction is unresolved. - -## Readiness - -- No open clarification items remain. -- The feature is ready for implementation and regression verification. - -## Rollout Steps - -1. Extend remote snapshot and runner contracts to surface `pendingInteraction`. -2. Parse assistant `tool_call_permission` and `question_request` blocks into a shared `RemotePendingInteraction` model. -3. Gate remote command routing around pending interactions and add `/pending`. -4. Add Telegram-specific rendering, callback token state, callback refresh, and text fallback. -5. Add Feishu-specific card rendering, text fallback, and inbound text parsing. -6. Add regression coverage for runner extraction, callback refresh, prompt resend, and channel-specific prompt delivery. -7. Update spec artifacts so acceptance, rollout, and compatibility are reviewable without tracing code. - -## Dependencies - -- `RemoteConversationSnapshot.pendingInteraction` in `RemoteConversationRunner` -- `agentSessionPresenter.respondToolInteraction(...)` -- Existing Telegram outbound edit/send flows in `TelegramPoller` -- Existing Feishu outbound text flow extended with card sending in `FeishuRuntime` -- In-memory callback/token state in `RemoteBindingStore` - -## Data And API Changes - -- `RemoteConversationSnapshot` - - Add `pendingInteraction: RemotePendingInteraction | null` - - Preserve `text` and `completed` semantics so remote delivery can send visible text plus a follow-up interaction prompt -- `RemoteRunnerStatus` - - Add `pendingInteraction` - - Suppress `isGenerating` while the assistant is explicitly waiting on user action -- `RemotePendingInteraction` - - Include `messageId`, `toolCallId`, `toolName`, `toolArgs` - - Include permission metadata for `tool_call_permission` - - Include question metadata for `question_request` -- `RemoteCommandRouteResult` / `FeishuCommandRouteResult` - - Allow outbound interaction prompt actions in addition to normal replies/conversation execution - -## Telegram Rendering Behavior - -- Permission interactions render a dedicated prompt with inline `Allow` / `Deny` buttons. -- Single-choice questions render inline option buttons and `Other` when custom text is allowed. -- `question.multiple === true` does not render fake multi-select buttons and instead instructs the user to reply in plain text. -- Text fallback accepts: - - `ALLOW` / `DENY` for permissions - - Exact numeric replies for question options - - Exact option labels for question options - - Custom text when allowed -- Expired callback tokens do not hard-fail if the interaction still exists; the router re-reads the current pending interaction and refreshes the prompt. -- After a button press, Telegram edits the original prompt into a resolved state immediately, then continues any deferred execution in the background. - -## Feishu Rendering Behavior - -- Pending interactions render as interactive-card style outbound messages when the card API succeeds. -- Card fallback uses the full plain-text prompt, not only a short reply hint, so the user still sees permission/question details. -- Feishu remains text-response only on the inbound side: - - `ALLOW` / `DENY` for permissions - - Exact numeric replies for question options - - Exact option labels for question options - - Custom text when allowed -- `question.multiple === true` always uses plain-text answers. - -## Command Gating While Waiting - -- Blocked commands while a pending interaction exists: - - `/new` - - `/use` - - `/model` - - Unrelated plain-text new-turn input -- Allowed commands while a pending interaction exists: - - `/help` - - `/status` - - `/open` - - `/pending` -- `/pending` re-sends the current prompt for the endpoint-bound session. - -## Migration And Compatibility - -- `RemoteConversationSnapshot.pendingInteraction` is additive and does not require a persisted config migration. -- Existing Telegram and Feishu bindings remain valid. -- Existing remote sessions continue to use detached session creation and the same runner/session binding path. -- Telegram keeps inline-button interaction handling; Feishu does not introduce public callback endpoints. -- The former generic "Desktop confirmation is required" message becomes a fallback path only, not the primary remote behavior. - -## Risks And Mitigations - -- Stale callback tokens - - Mitigation: rebind tokens to `endpointKey + messageId + toolCallId` and refresh prompts when the current interaction still matches. -- Session drift while waiting - - Mitigation: block `/new`, `/use`, `/model`, and unrelated plain-text turns until the interaction is resolved. -- Feishu card delivery failures - - Mitigation: fall back to the full plain-text prompt and keep inbound parsing text-only. -- Telegram callback latency - - Mitigation: edit the prompt immediately and run continuation work off the poll loop. - -## Test Strategy - -- Runner tests - - Extract `pendingInteraction` from assistant action blocks - - Resume after tool interaction response - - Handle chained interactions on the same assistant message -- Telegram tests - - Button callbacks and text fallback - - Expired callback token refresh - - `/pending` prompt resend - - Prompt edit timing and non-blocking deferred continuation -- Feishu tests - - Card prompt generation - - Plain-text fallback content - - Text parsing for permission/question answers - - Pending command gating and `/pending` diff --git a/docs/features/remote-tool-interactions/spec.md b/docs/features/remote-tool-interactions/spec.md deleted file mode 100644 index 864b29e86..000000000 --- a/docs/features/remote-tool-interactions/spec.md +++ /dev/null @@ -1,47 +0,0 @@ -# Remote Tool Interactions - -## Summary - -Extend remote control so Telegram and Feishu can surface structured pending tool interactions instead of collapsing them into a generic desktop-only notice. Remote users must be able to resolve permission requests and `user ask` style questions from the chat channel itself, while the desktop app keeps the existing agent execution and permission backends. - -## User Stories - -- As a Telegram remote user, I can approve or deny a tool permission request directly from inline buttons. -- As a Telegram remote user, I can answer a pending question by tapping an option or replying with text when custom input is allowed. -- As a Feishu remote user, I can see a clear card-style prompt for a pending permission or question and reply with a supported text answer. -- As a desktop user, I do not lose remote session continuity when a tool interaction pauses the assistant. -- As a paired remote user, I can ask the bot to re-show the current pending interaction without opening the desktop app. - -## Acceptance Criteria - -- `RemoteConversationSnapshot` includes `pendingInteraction` with structured `permission` or `question` data when the latest assistant message is waiting on user action. -- Remote delivery no longer relies on the generic "Desktop confirmation is required" path as the primary behavior. -- Telegram pending permission prompts render inline `Allow` and `Deny` buttons and also accept `ALLOW` / `DENY` text replies. -- Telegram single-choice question prompts render inline option buttons and an `Other` button when custom answers are allowed. -- Telegram multi-answer questions do not render fake multi-select buttons and instruct the user to reply with plain text. -- Expired Telegram interaction callback tokens refresh the prompt when the underlying pending interaction still exists. -- Feishu pending prompts render as interactive-card style outbound messages when possible and fall back to plain text when card delivery fails. -- Feishu accepts `ALLOW` / `DENY`, option numbers, exact option labels, and custom text according to the pending question shape. -- `/pending` re-sends the current prompt for both Telegram and Feishu. -- While a pending interaction exists, `/new`, `/use`, `/model`, and plain new-turn messages are blocked from creating unrelated session state changes. -- `/help`, `/status`, `/open`, and `/pending` remain available while a pending interaction exists. -- Existing remote pairing, binding, `/open`, `/status`, and normal non-interaction conversations continue to work. - -## Constraints - -- Keep all logic in Electron main; do not add a new renderer IPC surface for this feature. -- Telegram continues to use callback-query buttons; Feishu does not introduce a public HTTP callback service for card clicks. -- Remote bot copy remains English in this increment. -- Each endpoint only resolves the first pending interaction for its bound session at a time. - -## Non-Goals - -- Feishu clickable approval callbacks. -- Locale negotiation for remote bot messages. -- Arbitrary rich remote workflows beyond permission requests and question requests. - -## Compatibility - -- Existing Telegram and Feishu bindings remain valid. -- Existing remote sessions continue to use `RemoteConversationRunner` and detached session creation. -- Structured pending interaction handling is additive and only changes how remote channels render and answer paused assistant states. diff --git a/docs/features/remote-tool-interactions/tasks.md b/docs/features/remote-tool-interactions/tasks.md deleted file mode 100644 index 659613ab4..000000000 --- a/docs/features/remote-tool-interactions/tasks.md +++ /dev/null @@ -1,103 +0,0 @@ -# Remote Tool Interactions Tasks - -## Readiness - -- No open clarification items remain. -- All tasks below map back to the acceptance criteria in [spec.md](./spec.md). - -## T0 Spec Artifacts - -- [x] Create and align `spec.md`, `plan.md`, and `tasks.md` -- Owner: Remote control maintainer -- Estimate: 0.5d -- Acceptance Criteria: - - Spec acceptance criteria for `pendingInteraction`, channel rendering, `/pending`, and command gating are explicitly represented in the plan/tasks artifacts. - - No unresolved clarification markers remain before the work is marked ready. - -## T1 Remote Snapshot And API Changes - -- [x] Extend `RemoteConversationSnapshot` with `pendingInteraction` -- [x] Extend runner status to expose `pendingInteraction` -- [x] Parse assistant `tool_call_permission` and `question_request` blocks into `RemotePendingInteraction` -- Owner: Electron main -- Estimate: 1d -- Acceptance Criteria: - - Satisfies spec acceptance criteria for structured `pendingInteraction`. - - Remote delivery no longer depends on the generic desktop confirmation notice as the primary state. - -## T2 Electron Main Integration - -- [x] Add `RemoteConversationRunner.getPendingInteraction()` -- [x] Add `RemoteConversationRunner.respondToPendingInteraction()` -- [x] Continue polling the same assistant message after tool interaction responses -- Owner: Electron main -- Estimate: 1d -- Acceptance Criteria: - - Satisfies spec acceptance criteria for remote session continuity during paused interactions. - - Chained interactions can surface one at a time without losing the bound session. - -## T3 Telegram Buttons, Callback Handling, And Text Fallback - -- [x] Render permission prompts with `Allow` / `Deny` inline buttons -- [x] Render single-choice question prompts with option buttons and `Other` when custom input is allowed -- [x] Parse `ALLOW` / `DENY`, exact numeric replies, exact labels, and custom text as appropriate -- [x] Edit the original Telegram prompt into a resolved state immediately after button selection -- Owner: Telegram remote -- Estimate: 1.5d -- Acceptance Criteria: - - Satisfies spec acceptance criteria for Telegram permission buttons, single-choice buttons, and text fallback. - - `question.multiple === true` stays plain-text only. - -## T4 Feishu Card Rendering And Full Plain-Text Fallback - -- [x] Render pending interactions as Feishu card-style outbound actions -- [x] Fall back to the complete plain-text prompt when card delivery fails -- [x] Parse `ALLOW` / `DENY`, exact numeric replies, exact labels, and custom text as appropriate -- Owner: Feishu remote -- Estimate: 1d -- Acceptance Criteria: - - Satisfies spec acceptance criteria for Feishu card rendering and fallback behavior. - - Card failure still preserves permission/question details in the fallback message. - -## T5 Token Refresh And Expired Callback Recovery - -- [x] Store Telegram pending interaction callback tokens in `RemoteBindingStore` -- [x] Refresh the pending prompt when an expired callback token is used and the interaction still exists -- Owner: Telegram remote -- Estimate: 0.5d -- Acceptance Criteria: - - Satisfies spec acceptance criteria for expired Telegram callback token refresh. - - Prompt refresh only succeeds when `endpointKey`, `messageId`, and `toolCallId` still match. - -## T6 Pending Prompt Re-Send And Command Gating - -- [x] Add `/pending` for Telegram and Feishu -- [x] Block `/new`, `/use`, `/model`, and unrelated plain-text turns while waiting -- [x] Keep `/help`, `/status`, `/open`, and `/pending` available while waiting -- Owner: Remote router -- Estimate: 0.5d -- Acceptance Criteria: - - Satisfies spec acceptance criteria for `/pending`. - - Satisfies spec acceptance criteria for blocked and allowed commands while waiting. - -## T7 Tests - -- [x] Add runner tests for extraction and follow-up execution -- [x] Add Telegram tests for callback handling, `/pending`, prompt refresh, and non-blocking continuation -- [x] Add Feishu tests for text parsing and fallback behavior -- [x] Add binding/token lifecycle tests -- Owner: QA + Electron main -- Estimate: 1d -- Acceptance Criteria: - - Test coverage maps to the acceptance criteria in `spec.md`. - - Regressions in pairing, binding, `/open`, `/status`, and normal non-interaction flows are covered by targeted tests. - -## T8 Documentation And Review Notes - -- [x] Document compatibility, rollout behavior, and command gating in `plan.md` -- [x] Keep the feature scope explicit: Telegram buttons, Feishu cards, no Feishu callback endpoint -- Owner: Remote control maintainer -- Estimate: 0.5d -- Acceptance Criteria: - - Reviewers can understand rollout steps, dependencies, and compatibility notes without reading implementation files. - - The blocked commands list and allowed commands list match the implemented router behavior and `spec.md`. diff --git a/docs/features/right-sidepanel/plan.md b/docs/features/right-sidepanel/plan.md deleted file mode 100644 index 3caa8db89..000000000 --- a/docs/features/right-sidepanel/plan.md +++ /dev/null @@ -1,199 +0,0 @@ -# 右侧按需 Sidepanel 实施计划 - -## 1. 关键决策 - -1. 用新的 `Sidepanel` 替换 `ArtifactDialog`,不再维护独立 artifact drawer。 -2. sidepanel 分为两个一级 tab:`workspace`、`browser`。 -3. sidepanel 默认隐藏;开启状态仅由顶部按钮和 artifact 完成事件驱动。 -4. `workspace` 会话级状态与 `browser` 窗口级状态分离。 -5. 旧 workspace 的 `Plan / Terminal` 直接删除,不做兼容层。 -6. YoBrowser 主界面入口和独立窗口 UI 一并下线;底层先收敛到单嵌入实例。 - -## 2. 状态模型 - -### Window 级 - -新增 `sidepanel` store: - -1. `open: boolean` -2. `activeTab: 'workspace' | 'browser'` -3. `width: number` -4. `browserHostBounds: { x: number; y: number; width: number; height: number } | null` - -### Session 级 - -按 `sessionId` 保存: - -1. `selectedArtifactContext: { threadId: string; messageId: string; artifactId: string } | null` -2. `selectedFilePath: string | null` -3. `selectedDiffPath: string | null` -4. `viewMode: 'preview' | 'code'` -5. `sections: { artifacts: boolean; files: boolean; git: boolean }` - -## 3. 渲染层改造 - -### Chat 主布局 - -1. `ChatTabView.vue` - - 移除 `ArtifactDialog` - - 引入 `ChatSidePanel` - - 布局改为主内容 + 右侧滑出 panel - -2. `ChatTopBar.vue` - - 新增 `Workspace` 按钮 - - 接入 `sidepanelStore.toggleWorkspace()` - -### Workspace UI - -新增组件: - -1. `components/sidepanel/ChatSidePanel.vue` -2. `components/sidepanel/WorkspacePanel.vue` -3. `components/sidepanel/WorkspaceAccordionSection.vue` -4. `components/sidepanel/WorkspaceArtifactList.vue` -5. `components/sidepanel/WorkspaceFileTree.vue` -6. `components/sidepanel/WorkspaceGitChanges.vue` -7. `components/sidepanel/WorkspaceViewer.vue` -8. `components/sidepanel/BrowserPanel.vue` - -`WorkspaceViewer` 统一渲染: - -1. artifact preview/code -2. file preview/code -3. git diff - -能复用的现有实现: - -1. artifact preview 组件族 -2. `useArtifactCodeEditor` -3. `MarkdownRenderer` -4. `WorkspaceFileNode` 的树节点样式与操作 - -### Artifact 列表来源 - -1. 从 `messageStore.messages` 和 `streamingBlocks` 解析 artifact。 -2. 复用 `useArtifacts.ts` 的标签解析逻辑,抽出纯函数用于列表和消息卡片共享。 -3. 由 sidepanel store 负责“最新 artifact 完成后自动打开”的状态更新。 - -## 4. Main / Presenter 改造 - -### WorkspacePresenter - -删除: - -1. `getPlanEntries` -2. `updatePlanEntries` -3. `emitTerminalSnippet` -4. `terminateCommand` -5. `clearWorkspaceData` -6. `PlanStateManager` - -保留: - -1. `registerWorkspace` -2. `registerWorkdir` -3. `unregisterWorkspace` -4. `unregisterWorkdir` -5. `readDirectory` -6. `expandDirectory` -7. `revealFileInFolder` -8. `openFile` -9. `searchFiles` - -新增: - -1. `readFilePreview(filePath: string): Promise` -2. `getGitStatus(workspacePath: string): Promise` -3. `getGitDiff(workspacePath: string, filePath?: string): Promise` - -实现策略: - -1. `readFilePreview` - - 复用 `FilePresenter.prepareFileCompletely(absPath, undefined, 'origin')` - - 标准化 text / markdown / html / svg / image / binary -2. `getGitStatus` - - `git rev-parse --is-inside-work-tree` - - `git status --porcelain=v1 --branch` -3. `getGitDiff` - - `git diff -- file` - - `git diff --cached -- file` - - staged / unstaged 合并返回 - -### Agent Bash - -1. 删除向 `workspacePresenter.emitTerminalSnippet()` 的发送。 -2. 保留命令执行本身,不再产出 workspace terminal 片段。 - -## 5. YoBrowser 嵌入策略 - -### Presenter - -`YoBrowserPresenter` 增加嵌入式 host 生命周期: - -1. `ensureEmbeddedTarget(): Promise` -2. `attachEmbeddedToWindow(windowId: number): Promise` -3. `updateEmbeddedBounds(windowId: number, bounds: Rectangle, visible: boolean): Promise` -4. `detachEmbedded(): Promise` - -策略: - -1. 仅维护一个活动 browser target。 -2. 不再依赖多个 `BrowserWindowInfo` 列表驱动 UI。 -3. `getBrowserContext()` 最多返回一个活动窗口快照,保证 tool handler 兼容。 -4. 独立窗口创建接口不再由 renderer 调用;如仍被内部调用,重定向到同一活动 target。 - -### Renderer - -1. `BrowserPanel` 渲染 toolbar 和占位容器。 -2. 使用 `ResizeObserver` + `getBoundingClientRect()` 把容器 bounds 同步给 main。 -3. panel 关闭、切 tab、路由切换时隐藏嵌入内容。 - -## 6. 事件与类型清理 - -删除 workspace 事件: - -1. `WORKSPACE_EVENTS.PLAN_UPDATED` -2. `WORKSPACE_EVENTS.TERMINAL_OUTPUT` - -保留: - -1. `WORKSPACE_EVENTS.FILES_CHANGED` - -新增共享类型: - -1. `SidePanelTab` -2. `WorkspaceNavSection` -3. `WorkspaceFilePreview` -4. `WorkspaceGitFileChange` -5. `WorkspaceGitState` -6. `WorkspaceGitDiff` - -同步更新: - -1. `src/shared/types/presenters/workspace.d.ts` -2. `src/shared/types/presenters/index.d.ts` -3. `src/shared/types/presenters/legacy.presenters.d.ts` - -## 7. 测试策略 - -### Main - -1. `readFilePreview`:文本、markdown、html、svg、图片、二进制、非法路径 -2. `getGitStatus`:非仓库、clean repo、dirty repo -3. `getGitDiff`:staged、unstaged、stashed-free basic cases - -### Renderer - -1. `ChatTopBar` workspace 按钮显隐与切换 -2. artifact 完成后的自动滑出行为 -3. `Artifacts / Files / Git` 折叠区与 viewer 切换 -4. `BrowserPanel` bounds 同步与导航按钮行为 - -## 8. 风险与缓解 - -1. 风险:artifact 流式更新和 sidepanel 自动滑出状态竞争 - - 缓解:只在 artifact 从 loading 进入 loaded 且 panel 关闭时自动打开 -2. 风险:嵌入式 browser bounds 不准导致内容错位 - - 缓解:用容器真实 `getBoundingClientRect()`,并在 resize/tab 切换时强制同步 -3. 风险:删除旧 workspace 事件后存在死引用 - - 缓解:先全仓搜索,再删类型/事件/发送方/监听方 diff --git a/docs/features/right-sidepanel/spec.md b/docs/features/right-sidepanel/spec.md deleted file mode 100644 index fe50ba6a4..000000000 --- a/docs/features/right-sidepanel/spec.md +++ /dev/null @@ -1,133 +0,0 @@ -# 右侧按需 Sidepanel 规格 - -## 概述 - -本规格定义聊天页右侧统一 `Sidepanel` 的交互与范围。目标是替换当前错位的 `ArtifactDialog` 和残留的旧 `Workspace` 语义,把 `artifact / files / git / yo browser` 收敛到一个按需滑出的右侧面板中。 - -本规格明确以下产品方向: - -1. 右侧 panel 默认隐藏,不常驻。 -2. 顶部增加 `Workspace` 按钮作为主入口,位于分享按钮左侧。 -3. artifact 完成生成后,在 panel 隐藏时自动滑出并直接进入 preview。 -4. `Workspace` 左侧导航统一为 `Artifacts / Files / Git` 三段折叠区。 -5. `YoBrowser` 不再提供独立窗口和左侧入口,只保留右侧嵌入模式。 -6. 旧 workspace 的 `Plan / Terminal` 能力直接移除,不保留兼容壳。 - -## 背景与目标 - -1. 当前 artifact preview 虽可被唤起,但布局错乱,且与聊天主视图割裂。 -2. 旧 workspace 的结构已经不再适配当前产品方向,尤其 `Plan / Terminal` 与本次需求无关。 -3. 用户希望右侧像 `JSFiddle / CodePen / VS Code side panel` 一样承载文件树、轻量预览、git diff 和浏览器。 -4. 当前 YoBrowser 仍是独立窗口模型,和新的右侧工作流冲突。 - -## 用户故事 - -### US-1:我可以按需打开右侧工作区 - -作为用户,我希望右侧面板默认隐藏,需要时再通过顶部按钮打开,不占用主聊天空间。 - -### US-2:artifact 完成后我能立刻看到预览 - -作为用户,我希望 assistant 产出 artifact 后,在不打断我当前阅读的前提下,于完成时自动滑出 preview。 - -### US-3:我能在一个地方查看 artifact、文件和 git 变化 - -作为用户,我希望右侧 `Workspace` 统一展示当前会话的 artifacts、工作目录文件树和 git 变化,并能切换查看内容。 - -### US-4:我能在同一个面板里使用浏览器 - -作为用户,我希望浏览器也在右侧 panel 内,不再跳出独立窗口。 - -## 功能需求 - -### A. 总体布局 - -- [ ] 聊天页改为“主内容 + 右侧滑出 panel”布局。 -- [ ] 右侧 panel 默认关闭。 -- [ ] panel 打开后不覆盖顶部栏和主聊天区滚动逻辑。 -- [ ] panel 支持宽度调整并记忆宽度。 - -### B. 顶部入口 - -- [ ] `ChatTopBar` 在分享按钮左侧增加 `Workspace` 按钮。 -- [ ] 点击按钮时: - - 若 panel 已关闭,则打开 panel 并切到 `Workspace` tab。 - - 若 panel 已打开且当前 tab 为 `Workspace`,则关闭 panel。 - - 若 panel 已打开但当前 tab 为 `Browser`,则切回 `Workspace`。 - -### C. Artifact 自动滑出 - -- [ ] artifact 流式生成中只更新数据,不自动切换 UI。 -- [ ] artifact 完成时: - - 若 panel 关闭,则自动打开 panel。 - - 自动切到 `Workspace` tab。 - - 选中最新 artifact。 - - 默认进入 `Preview` 模式。 -- [ ] 若 panel 已打开: - - 仅更新 `Artifacts` 列表。 - - 不强制切换当前 viewer,除非当前 viewer 就是该 artifact。 - -### D. Workspace 结构 - -- [ ] `Workspace` 固定为“左导航 + 右 viewer”。 -- [ ] 左导航包含三个独立折叠区: - - `Artifacts` - - `Files` - - `Git` -- [ ] 三个折叠区可同时展开,也可只展开一个。 -- [ ] `Artifacts` 数据来源于当前会话消息中解析出的 artifact。 -- [ ] `Files` 数据来源于当前会话 `projectDir`。 -- [ ] `Git` 仅在当前 workspace 是 git 仓库时显示。 - -### E. Viewer - -- [ ] viewer 只支持三类 source: - - `artifact` - - `file` - - `git-diff` -- [ ] `artifact / file` 共用 `Preview / Code` 切换。 -- [ ] `git-diff` 使用只读 diff 视图。 -- [ ] `Open externally` 只对真实文件可见。 -- [ ] 对无法内联的二进制文件,显示元信息和外部打开入口。 - -### F. Browser - -- [ ] sidepanel 提供 `Browser` 一级 tab。 -- [ ] `Browser` 内嵌 YoBrowser 内容区域。 -- [ ] 浏览器模式包含地址栏和基础 toolbar。 -- [ ] 不再从主界面暴露独立 browser window。 -- [ ] 左侧 `WindowSideBar` 删除浏览器入口。 - -### G. 清理旧 Workspace - -- [ ] 删除旧 workspace 的 `Plan / Terminal` 类型、事件和 presenter 方法。 -- [ ] 删除残留 `WorkspaceView / WorkspacePlan / WorkspaceTerminal` 引用。 -- [ ] 不保留空壳组件或兼容事件。 - -## 验收标准 - -- [ ] 顶部 `Workspace` 按钮位于分享按钮左侧,点击可稳定打开/关闭 panel。 -- [ ] artifact 完成时,panel 关闭则自动滑出并进入最新 artifact preview。 -- [ ] `Artifacts / Files / Git` 三个折叠区独立展开/收起正常。 -- [ ] 文件、artifact、git diff 三类 viewer 切换稳定,无错位、无残留内容。 -- [ ] 非 git 仓库不显示 `Git` 区;git 仓库能展示文件变更和 diff。 -- [ ] 左侧不再有 browser 入口;主界面也不再打开独立 YoBrowser window。 -- [ ] 删除旧 plan/terminal 后,无事件报错、无死引用、无无效 i18n 使用。 - -## 非目标 - -1. 本规格不恢复旧 `Plan`、`Terminal`。 -2. 本规格不做 git 写操作(stage / unstage / commit)。 -3. 本规格不做 multi-root workspace。 -4. 本规格不新增服务端 artifact 列表接口。 - -## 约束 - -1. v1 只支持单 workspace root,即当前会话 `projectDir`。 -2. 用户可见文案必须走 i18n。 -3. 右侧 panel 只在聊天页生效。 -4. 只有 `FILES_CHANGED` 这类对新文件树仍有价值的 workspace 事件可以保留。 - -## 开放问题 - -无。 diff --git a/docs/features/right-sidepanel/tasks.md b/docs/features/right-sidepanel/tasks.md deleted file mode 100644 index bd6cd6262..000000000 --- a/docs/features/right-sidepanel/tasks.md +++ /dev/null @@ -1,52 +0,0 @@ -# 右侧按需 Sidepanel 任务清单 - -## T0 规格文档 - -- [x] 创建 `docs/features/right-sidepanel/spec.md` -- [x] 创建 `docs/features/right-sidepanel/plan.md` -- [x] 创建 `docs/features/right-sidepanel/tasks.md` - -## T1 Workspace 主进程清理 - -- [ ] 删除 workspace 的 `Plan / Terminal` 类型定义 -- [ ] 删除 `WORKSPACE_EVENTS.PLAN_UPDATED / TERMINAL_OUTPUT` -- [ ] 删除 `PlanStateManager` -- [ ] 删除 `AgentBashHandler` 的 terminal snippet 发射 -- [ ] `WorkspacePresenter` 增加 `readFilePreview / getGitStatus / getGitDiff` - -## T2 Sidepanel 状态与布局 - -- [ ] 新增 `sidepanel` store(窗口级 + 会话级) -- [ ] `ChatTabView` 替换 `ArtifactDialog` 为 `ChatSidePanel` -- [ ] `ChatTopBar` 增加 `Workspace` 按钮并接线 -- [ ] 支持 panel 默认隐藏、打开/关闭、宽度拖拽与记忆 - -## T3 Workspace UI - -- [ ] 新增 `WorkspacePanel` 左导航三段折叠区 -- [ ] 从当前会话消息解析 artifact 列表 -- [ ] 接入文件树读取与文件预览 -- [ ] 接入 git 状态与 diff 查看 -- [ ] 统一 viewer 的 `Preview / Code / Open externally` - -## T4 Artifact 自动滑出 - -- [ ] 调整 artifact store,不再驱动独立 drawer -- [ ] 流式阶段仅更新 artifact 数据 -- [ ] 完成阶段在 panel 关闭时自动滑出并选中最新 artifact -- [ ] panel 已打开时只刷新列表,不抢焦点 - -## T5 Browser 嵌入 - -- [ ] `YoBrowserPresenter` 增加单嵌入实例生命周期 -- [ ] 新增 `BrowserPanel` toolbar 与容器 bounds 同步 -- [ ] 删除 `WindowSideBar` 浏览器入口 -- [ ] 主界面不再唤起独立 YoBrowser window - -## T6 清理与验证 - -- [ ] 删除旧 workspace/browser 残留引用与无用组件 -- [ ] 补齐必要 i18n 文案 -- [ ] 运行 `pnpm run format` -- [ ] 运行 `pnpm run i18n` -- [ ] 运行 `pnpm run lint` diff --git a/docs/features/scheduled-tasks/plan.md b/docs/features/scheduled-tasks/plan.md deleted file mode 100644 index 801199c4c..000000000 --- a/docs/features/scheduled-tasks/plan.md +++ /dev/null @@ -1,63 +0,0 @@ -# Implementation Plan - -## Architecture - -- **Shared types** (`src/shared/scheduledTasks.ts`) define the `Trigger`, - `Action`, `ScheduledTask`, and `ScheduledTasksSettings` shapes. -- **Route contracts** (`src/shared/contracts/routes/scheduledTasks.routes.ts`) - expose `scheduledTasks.{list,upsert,delete,toggle,fireNow}` via Zod - schemas, mirroring `onboarding.routes.ts`. -- **Persistence** is handled in `ConfigPresenter` (`get/setScheduledTasks`) - with a `normalizeScheduledTasksConfig` pass identical in pattern to - `normalizeHooksNotificationsConfig`. -- **Scheduling** lives in a new `ScheduledTasksService` - (`src/main/presenter/scheduledTasks/index.ts`). One `setTimeout` per - armed task, chained at most 12h at a time. Public surface: - - `start()` — read tasks, run startup pass (one-shot backfill, arm next - slot for recurring), called from the existing lifecycle init flow. - - `stop()` — clear all armed timers (called on app shutdown). - - `list()` / `upsert(task)` / `delete(id)` / `toggle(id, enabled)` / - `fireNow(id)` — back the IPC routes and rearm timers on mutation. - - `computeNextFireAt(task, after)` — pure function, exported for tests. -- **Action dispatch** is a small helper inside the service: switch on - `task.action.kind`, then call `notificationPresenter` and/or - `eventBus.sendToRenderer(DEEPLINK_EVENTS.START, ...)` and/or - `sessionService.createSession(...)`. - -## Wiring - -- `Presenter` constructor (`src/main/presenter/index.ts`) instantiates - `ScheduledTasksService` next to `hooksNotifications`, passing it - `configPresenter`, `notificationPresenter`, `windowPresenter`, and a - thunk that resolves `sessionService` lazily (the route runtime owns - sessionService, so the service exposes a setter the route runtime calls - during bootstrap). -- `src/main/routes/index.ts` wires the five new route cases against - `runtime.scheduledTasksService` and, in the same place that constructs - the runtime, sets the service's session-service reference so auto-send - has somewhere to call. -- Lifecycle `after-start` hook invokes `scheduledTasksService.start()` - after the other presenters have come up; the existing `beforeQuit` hook - calls `stop()`. - -## UI - -- Settings navigation adds `settings-scheduled-tasks` (group `tools`, - position 5.6, icon `lucide:clock-9`). -- `ScheduledTasksSettings.vue` mirrors `NotificationsHooksSettings.vue`: - ScrollArea + header + "新建任务" button + bordered cards per task. -- A renderer client `ScheduledTasksClient.ts` matches the - `OnboardingClient` shape. -- i18n keys go in every locale under `routes.settings-scheduled-tasks` and - `settings.scheduledTasks.*` so `pnpm run i18n` stays green. - -## Validation - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- `pnpm run typecheck` -- Unit tests in `test/main/presenter/scheduledTasks.test.ts` cover - `computeNextFireAt` (daily wrap, weekly across the week, one-shot past - with/without `lastFiredAt`) and `normalizeScheduledTasksConfig` - (drops malformed entries, deduplicates ids, preserves valid ones). diff --git a/docs/features/scheduled-tasks/spec.md b/docs/features/scheduled-tasks/spec.md deleted file mode 100644 index 725c20bb2..000000000 --- a/docs/features/scheduled-tasks/spec.md +++ /dev/null @@ -1,59 +0,0 @@ -# Scheduled Tasks - -## Problem - -Closes [#1567](https://github.com/ThinkInAIXYZ/deepchat/issues/1567). - -Users want to schedule "reminders" and "planned tasks" inside DeepChat — -either a plain notification ("drink water at 4pm every day") or a scheduled -chat prompt ("every morning at 9 ask the deepchat agent for today's plan"). -No equivalent feature exists today; the only time-aware paths are -`hooksNotifications` (event-driven, not time-driven) and the deeplink -"start" flow (which has `autoSend` security-disabled, so it can only -prefill a chat draft). - -## User Story - -As a DeepChat user, I want to create a scheduled task with a trigger time -(once at a specific datetime, daily, or weekly on a chosen day) and an -action (raise a system notification, prefill a new chat thread with a -preset prompt, or auto-send a preset prompt to a chosen agent/model). I -want my tasks to persist across app restarts; one-shot tasks that I missed -because the app was closed should still fire on next launch. - -## Acceptance Criteria - -- A "定时任务" entry exists in Settings → Tools (between Notifications & - Hooks and Plugins). It lists, creates, edits, toggles, deletes, and - manually fires user-defined scheduled tasks. -- Each task has: - - A name and an enabled toggle. - - A trigger of one of three kinds: `once` (a specific datetime), - `daily` (hour + minute), or `weekly` (day-of-week + hour + minute). - - An action of one of two kinds: `notify` (title + body for the system - notification) or `prompt` (notification title + chat message + optional - agent / provider / model / system prompt + `autoSend` toggle). -- When a task fires: - - `notify`: a system notification appears via `notificationPresenter`, - subject to the existing `notificationsEnabled` config. - - `prompt` with `autoSend = false`: a system notification appears and the - main window's new-thread page receives the deeplink-start payload, - prefilling the chat input. - - `prompt` with `autoSend = true`: `sessionService.createSession` is - invoked directly using the configured agent/provider/model, so the LLM - actually responds without user interaction. A notification is raised - when the session is created. -- One-shot tasks whose `firesAt` was in the past at launch and that have no - `lastFiredAt` recorded are fired once on startup (backfill). Recurring - tasks are not backfilled — they simply jump to the next slot. -- Task records survive app restart (persisted through `ConfigPresenter`'s - ElectronStore as the `scheduledTasks` key). - -## Non-goals - -- Cron-expression input. Daily / weekly / once is sufficient for the - feature ask; a future iteration may add `cron` and `interval` trigger - kinds. -- Per-task timezone handling. Triggers use the OS's local time. -- Letting the LLM schedule tasks via an MCP tool. Possible follow-up. -- Calendar / iCal export. diff --git a/docs/features/scheduled-tasks/tasks.md b/docs/features/scheduled-tasks/tasks.md deleted file mode 100644 index e03a202ef..000000000 --- a/docs/features/scheduled-tasks/tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Define shared types (`src/shared/scheduledTasks.ts`) and route contracts. -- [x] Implement `ScheduledTasksService` (presenter + `computeNextFireAt` + action dispatch). -- [x] Wire `ConfigPresenter` persistence (`scheduledTasks` key) with normalize-on-read. -- [x] Register routes in `src/main/routes/index.ts` and instantiate in `Presenter` constructor. -- [x] Hook lifecycle: start on `after-start`, stop on `beforeQuit`. -- [x] Add renderer client `src/renderer/api/ScheduledTasksClient.ts`. -- [x] Add settings navigation entry and dynamic route component. -- [x] Implement `ScheduledTasksSettings.vue` (CRUD, mirror NotificationsHooks layout). -- [x] Add i18n keys across all locales. -- [x] Unit tests for `computeNextFireAt` and `normalizeScheduledTasksConfig`. -- [x] Add service tests for notification firing, one-shot disable, and prompt auto-send dispatch. -- [x] Run `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, `pnpm run typecheck`. -- [x] Address PR review comments without changing scheduled-task behavior. diff --git a/docs/features/settings-agent-capability-groups/plan.md b/docs/features/settings-agent-capability-groups/plan.md deleted file mode 100644 index 62e28ff5e..000000000 --- a/docs/features/settings-agent-capability-groups/plan.md +++ /dev/null @@ -1,11 +0,0 @@ -# Implementation Plan - -## Change - -- Update shared settings navigation group assignments for Directory Environment, DeepChat Agent, and ACP Agent. -- Rename the models and tools group labels in locale files. -- Align affected page eyebrow labels with their sidebar group where applicable. - -## Validation - -- Run format, i18n, lint, and web typecheck. diff --git a/docs/features/settings-agent-capability-groups/spec.md b/docs/features/settings-agent-capability-groups/spec.md deleted file mode 100644 index aaafaaf21..000000000 --- a/docs/features/settings-agent-capability-groups/spec.md +++ /dev/null @@ -1,18 +0,0 @@ -# Settings Agent Capability Groups - -## User Story - -As a settings user, I want model, agent, and directory environment settings grouped together as Agent settings, so related configuration lives in one place. - -## Acceptance Criteria - -- The sidebar group previously named Models is labeled Agent Settings. -- Provider settings, DeepChat Agent, ACP Agent, and Directory Environment appear in the Agent Settings group. -- The sidebar group previously named Tools & Agents is labeled Capability Extensions. -- MCP Settings, Hooks, and Plugins remain in the Capability Extensions group. -- Existing route paths and page behavior remain unchanged. - -## Non-goals - -- No changes to provider, agent, MCP, hooks, plugin, or environment runtime behavior. -- No changes to route names or persisted settings. diff --git a/docs/features/settings-agent-capability-groups/tasks.md b/docs/features/settings-agent-capability-groups/tasks.md deleted file mode 100644 index dcb37c9f1..000000000 --- a/docs/features/settings-agent-capability-groups/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Move Directory Environment and Agent pages into the Agent Settings group. -- [x] Rename Models and Tools & Agents group labels. -- [x] Align affected page eyebrow labels. -- [x] Run validation commands. diff --git a/docs/features/settings-control-center/plan.md b/docs/features/settings-control-center/plan.md deleted file mode 100644 index b4b4397a0..000000000 --- a/docs/features/settings-control-center/plan.md +++ /dev/null @@ -1,27 +0,0 @@ -# Settings Control Center Plan - -## Layout - -- Add grouped settings navigation metadata and a grouped sidebar. -- Add a shared page shell for consistent headers, descriptions, actions, and scroll behavior. -- Add `/overview` and hide `/dashboard` from navigation while preserving route compatibility. - -## Pages - -- Build `SettingsOverview` with passive status cards, quick tasks, needs attention, recent activity, and embedded usage dashboard. -- Refactor Provider into list plus detail tabs: Connection, Models, Advanced. -- Refactor MCP into status cards, filters, server cards, and a right-side Sheet detail. -- Refactor Data into Backup & Sync, Privacy Mode, Data Operations, and Danger Zone. -- Wrap remaining tabs with the new shell without changing their core behavior. - -## Data - -- Add `settings_activity` SQLite table with a 2000-record retention cap. -- Add a typed list route returning at most 200 records. -- Record key successful mutations from provider, model, MCP, sync, and settings update routes. - -## QA - -- Update unit tests and e2e settings smoke tests. -- Run format, i18n, lint, typecheck, focused tests, and settings e2e. -- Start the app and capture settings screenshots before stopping the dev app. diff --git a/docs/features/settings-control-center/spec.md b/docs/features/settings-control-center/spec.md deleted file mode 100644 index 83969cde2..000000000 --- a/docs/features/settings-control-center/spec.md +++ /dev/null @@ -1,47 +0,0 @@ -# Settings Control Center - -## Goal - -Refactor the settings window from a flat configuration list into a system control center. - -## Requirements - -- Add a default Settings Overview route that merges the old usage Dashboard. -- Refactor every settings tab into the new grouped settings layout. -- Keep Health Check out of this iteration. -- Use existing shadcn-vue components and semantic tokens. -- Support light/dark themes, RTL, i18n, minimum window width, and responsive content. -- Add a durable settings activity table that keeps the newest 2000 records and shows at most 200. -- Preserve existing settings behavior, deeplinks, and e2e navigation. - -## UX - -```text -┌──────────────────────────────────────────────────────────────┐ -│ Settings │ -├───────────────┬──────────────────────────────────────────────┤ -│ Overview │ Settings Overview │ -│ Setup │ Search settings, providers, MCP... │ -│ General │ │ -│ Display │ Providers | MCP | Knowledge | Data │ -│ Environment │ │ -│ Shortcuts │ Quick Tasks Needs Attention │ -│ Models │ Recent Changes Usage Dashboard │ -│ Providers │ │ -│ Tools │ │ -│ MCP │ │ -│ Agents │ │ -│ Knowledge │ │ -│ System │ │ -└───────────────┴──────────────────────────────────────────────┘ -``` - -## Acceptance - -- `/overview` is the default settings route. -- `/dashboard` still works but is no longer shown in the sidebar. -- Overview shows system status, quick tasks, recent activity, and usage dashboard. -- Provider, MCP, and Data pages use the new control-center interaction model. -- Other tabs use the grouped shell and remain functional. -- `settings_activity` never stores secret values. -- Automated tests, linting, typecheck, i18n checks, e2e, and screenshots pass before completion. diff --git a/docs/features/settings-control-center/tasks.md b/docs/features/settings-control-center/tasks.md deleted file mode 100644 index 8fb383bf2..000000000 --- a/docs/features/settings-control-center/tasks.md +++ /dev/null @@ -1,13 +0,0 @@ -# Settings Control Center Tasks - -- [ ] Add navigation metadata, overview route, and dashboard compatibility route. -- [ ] Add settings activity table, route contract, client method, and mutation recording. -- [ ] Build shared settings shell components. -- [ ] Implement Settings Overview. -- [ ] Refactor Provider Center. -- [ ] Refactor MCP Center. -- [ ] Refactor Data & Privacy. -- [ ] Wrap remaining settings tabs in the new shell. -- [ ] Add i18n keys for all locales. -- [ ] Update unit tests and e2e tests. -- [ ] Run checks, start app, capture screenshots, and stop app. diff --git a/docs/features/settings-dashboard/plan.md b/docs/features/settings-dashboard/plan.md deleted file mode 100644 index 09b9113f9..000000000 --- a/docs/features/settings-dashboard/plan.md +++ /dev/null @@ -1,46 +0,0 @@ -# Plan - -## Data Model - -- Add `deepchat_usage_stats` keyed by `message_id`. -- Store final per-message usage snapshots: - - session, provider, model - - input/output/total tokens - - cached input tokens - - estimated USD cost - - local usage date - - source (`backfill` or `live`) - -## Backfill - -- Trigger in `AFTER_START` with a non-blocking hook. -- Scan only `deepchat_messages` joined with `deepchat_sessions`. -- Use message metadata provider/model first, then session fallback. -- Persist backfill status in config under `dashboardStatsBackfillV1`. -- Re-running is safe because stats rows are upserted by `message_id`. - -## Live Recording - -- Extend stream usage metadata with optional `cached_tokens`. -- Persist cached input tokens into assistant message metadata. -- Upsert stats from `DeepChatMessageStore.finalizeAssistantMessage` and `setMessageError`. - -## Dashboard Query - -- Expose `agentSessionPresenter.getUsageDashboard()`. -- Aggregate summary, 365-day calendar, provider breakdown, and model breakdown from `deepchat_usage_stats`. - -## UI - -- Add `DashboardSettings.vue` as a scrollable settings page. -- Keep the visual language aligned with the current project theme. -- Show loading, empty, running backfill, and failed backfill states. -- Render four summary cards only; remove the cache hit rate card from the dashboard overview. -- Adopt the official `shadcn-vue chart` component with `Unovis` for dashboard chart rendering. -- Rebuild the overview layout as `1 large + 3 small`, with total tokens as the hero chart. -- Replace the total-token number card with a donut-based hero chart that visualizes input/output ratio. -- Visualize cached input tokens with a compact horizontal stacked bar for cached versus uncached input. -- Visualize estimated cost with a 30-day area chart while keeping the total cost as the primary value. -- Reuse `recordingStartedAt` to render a locale-specific, number-first "days with DeepChat" summary card in the renderer. -- Keep provider/model ranking queries unchanged, but render them as horizontal token bar charts with internal scrolling. -- Translate changed dashboard copy per locale instead of falling back to English sentence structure. diff --git a/docs/features/settings-dashboard/spec.md b/docs/features/settings-dashboard/spec.md deleted file mode 100644 index 47efb1f69..000000000 --- a/docs/features/settings-dashboard/spec.md +++ /dev/null @@ -1,33 +0,0 @@ -# Settings Dashboard - -## Goal - -Add a dedicated dashboard page under settings to show token usage, cached token usage, estimated cost, and a GitHub-like contribution calendar. - -## User Stories - -- As a user, I want to see my total token usage, cached token usage, and estimated cost in one place. -- As an existing user, I want the dashboard to initialize from the current `deepchat_messages` table once, without scanning legacy tables. -- As a user, I want the dashboard to keep growing from newly recorded usage without repeatedly recomputing from old chat tables. - -## Acceptance Criteria - -- A new settings route named `settings-dashboard` is available after provider settings. -- The dashboard reads from a dedicated `deepchat_usage_stats` table only. -- Existing users get a one-time background backfill from current `deepchat_messages`. -- Historical backfill sets cached input tokens to `0`. -- New assistant message finalization and error finalization upsert usage rows into `deepchat_usage_stats`. -- Price estimation uses current provider pricing first and falls back to `aihubmix` for the same model id when needed. -- The page contains four overview cards arranged as `1 large + 3 small`: a total-token hero chart, a cached-token ratio card, an estimated-cost trend card, and a days-with-DeepChat card. -- The total-token hero chart uses the shared `shadcn-vue chart + Unovis` visual language, keeps the donut semantics for input/output composition, and shows exact values plus percentages. -- The cached-token card uses the same chart system to visualize cached versus uncached input tokens and shows exact values plus percentages. -- The estimated-cost card uses the same chart system to show the total estimated cost plus a lightweight 30-day area trend. -- The "days with DeepChat" card is derived from the earliest recorded usage date and rendered in a number-first, locale-specific layout. -- The page contains a 365-day contribution calendar and provider/model breakdowns. -- Provider and model breakdown cards render horizontal token bar charts with internal scrolling without growing the full page indefinitely. - -## Non-Goals - -- No backfill from legacy `messages` or `conversations` tables. -- No delete-triggered rollback of accumulated usage stats. -- No additional day-level rollup table in v1. diff --git a/docs/features/settings-dashboard/tasks.md b/docs/features/settings-dashboard/tasks.md deleted file mode 100644 index fd2e3e02f..000000000 --- a/docs/features/settings-dashboard/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -1. Add shared dashboard types and cached token usage plumbing. -2. Add `deepchat_usage_stats` table and wire it into `SQLitePresenter`. -3. Record live usage stats on assistant finalize and error finalize. -4. Implement one-time historical backfill and dashboard query methods in `AgentSessionPresenter`. -5. Add settings route, dashboard page, and i18n strings. -6. Add focused main/renderer tests and run format, i18n, and lint. diff --git a/docs/features/settings-environments/plan.md b/docs/features/settings-environments/plan.md deleted file mode 100644 index d84d59f29..000000000 --- a/docs/features/settings-environments/plan.md +++ /dev/null @@ -1,67 +0,0 @@ -# Settings Environments Plan - -## Data Model - -- Add `new_environments` with: - - `path TEXT PRIMARY KEY` - - `session_count INTEGER NOT NULL` - - `last_used_at INTEGER NOT NULL` -- Add index `idx_new_environments_last_used` on `last_used_at DESC`. -- Add schema migration `v17` to create the table. -- Add schema migration `v18` to rebuild environment history from: - - non-draft `new_sessions.project_dir` - - ACP `workdir` rows when the linked session has no `project_dir` - -## Synchronization Strategy - -- Keep `new_environments` as a derived table managed in application code. -- `NewSessionManager` recomputes all affected paths after session create, update, and delete. -- ACP session persistence updates also recompute affected environment paths when `workdir` changes. -- `LegacyChatImportService` performs a full rebuild after import because it writes session rows directly. -- `SQLitePresenter.clearNewAgentData()` clears `new_environments` together with session-domain tables. - -## Presenter / IPC - -- Extend `IProjectPresenter` with: - - `getEnvironments()` - - `openDirectory(path)` -- Extend `IConfigPresenter` with: - - `getDefaultProjectPath()` - - `setDefaultProjectPath(path | null)` -- Add `CONFIG_EVENTS.DEFAULT_PROJECT_PATH_CHANGED` so renderers can react to default directory updates. - -## Renderer - -- Add `EnvironmentsSettings.vue` under settings routes. -- Reuse current settings card layout and controls. -- Use a switch to control temp directory visibility. -- Use the project store to: - - fetch environment history - - manage the default project path - - open directories -- Extend the project store with: - - `environments` - - `defaultProjectPath` - - selection-source tracking so default selection does not override manual selection - - synthetic project injection for missing default/manual paths - -## Sorting and Grouping - -- Sort by: - - default directory first - - then `lastUsedAt DESC` -- Render as one main list, with temp environments appended in a collapsed section. -- Treat app-managed workspace roots, including legacy app-data `workspaces` paths, as temp entries. - -## Test Strategy - -- Main process: - - migration `v18` backfill and idempotency - - `NewEnvironmentsTable` single-path recompute, ACP `workdir` fallback, draft filtering, delete-on-empty, full rebuild - - `ProjectPresenter` environment mapping and directory open behavior - - `NewSessionManager` environment sync calls - - `LegacyChatImportService` rebuild trigger and `workdir` import fallback -- Renderer: - - settings page rendering, temp switch collapse, open/default/clear actions - - project store default selection and synthetic project behavior - - new thread page consumes the preselected project path diff --git a/docs/features/settings-environments/spec.md b/docs/features/settings-environments/spec.md deleted file mode 100644 index 4b47cae08..000000000 --- a/docs/features/settings-environments/spec.md +++ /dev/null @@ -1,50 +0,0 @@ -# Settings Environments - -## Summary - -Add a new settings page, `Environments / 目录环境`, to show project directories that have actually been used by sessions. Users can review basic metadata, open a directory, and set or clear a default directory for new chats. - -## User Stories - -- As a user, I want to see which project directories my sessions have used so I can reopen them quickly. -- As a user, I want to set one default directory for new chats without overriding a directory I select manually later. -- As a user, I want temporary workspace directories separated from regular directories so the primary list stays clean. - -## Acceptance Criteria - -- Settings navigation includes `settings-environments` after `Display` and before provider settings. -- The page shows: - - a single merged environments list - - a temp environments section collapsed by default and controlled by a switch -- Environment entries come from directories used by non-draft sessions, using `new_sessions.project_dir` first and falling back to ACP `workdir` history when `project_dir` is missing. -- Each entry shows: - - directory name - - full path - - session count - - last used time based on `new_sessions.updated_at` - - badges for default, temp, missing, and synthetic default states when applicable -- Users can: - - open a directory with one click - - set a directory as default directly on the item - - clear the current default directory directly on the default item -- Temp directories are determined by whether the path is under `app.getPath('temp')` or an app-managed workspace root under app data / legacy user data. -- Default directory only preselects the project on the new thread page. Manual user selection remains higher priority. -- If the default directory is not present in recent projects, the new thread page still surfaces it through a synthetic project entry. - -## Non-Goals - -- Git status, rename, delete, or add-environment actions -- Tracking directories that were only opened in a picker without being used by a session -- Trigger-based SQLite maintenance - -## Constraints - -- Follow existing settings visual language instead of reproducing the reference screenshot. -- Use i18n for all user-facing strings. -- Use a derived SQLite table for environment history to avoid scanning `new_sessions` on every page load. - -## Migration Notes - -- Introduce `new_environments` as a derived table. -- Backfill it once from existing session history in schema migration `v18`, including ACP `workdir` fallback when `project_dir` is absent. -- Keep it synchronized in application code when sessions change. diff --git a/docs/features/settings-environments/tasks.md b/docs/features/settings-environments/tasks.md deleted file mode 100644 index f9e131388..000000000 --- a/docs/features/settings-environments/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Settings Environments Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/settings-overview-agent-card/plan.md b/docs/features/settings-overview-agent-card/plan.md deleted file mode 100644 index 5e8cc56b2..000000000 --- a/docs/features/settings-overview-agent-card/plan.md +++ /dev/null @@ -1,11 +0,0 @@ -# Implementation Plan - -## Change - -- Replace the knowledge metric card in `SettingsOverview.vue` with a DeepChat Agents metric. -- Load agents through the existing agent store and count enabled DeepChat agents. -- Update locale strings for the new metric and overview description. - -## Validation - -- Run format, i18n, lint, and web typecheck. diff --git a/docs/features/settings-overview-agent-card/spec.md b/docs/features/settings-overview-agent-card/spec.md deleted file mode 100644 index bed880061..000000000 --- a/docs/features/settings-overview-agent-card/spec.md +++ /dev/null @@ -1,17 +0,0 @@ -# Settings Overview Agent Card - -## User Story - -As a settings user, I want the overview metrics to show enabled DeepChat Agents instead of knowledge sources, so the summary focuses on higher-signal setup status. - -## Acceptance Criteria - -- The third overview metric card is labeled DeepChat Agents. -- The card shows how many DeepChat agents are enabled. -- Clicking the card opens the DeepChat Agent settings page. -- The overview copy no longer describes the removed knowledge metric. - -## Non-goals - -- No changes to agent creation, editing, or enablement behavior. -- No changes to the context engineering sidebar group or knowledge base pages. diff --git a/docs/features/settings-overview-agent-card/tasks.md b/docs/features/settings-overview-agent-card/tasks.md deleted file mode 100644 index 30079c8cb..000000000 --- a/docs/features/settings-overview-agent-card/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Replace the knowledge metric with a DeepChat Agents metric. -- [x] Update overview copy and locale strings. -- [x] Run validation commands. diff --git a/docs/features/settings-overview-cards/plan.md b/docs/features/settings-overview-cards/plan.md deleted file mode 100644 index 71c924d61..000000000 --- a/docs/features/settings-overview-cards/plan.md +++ /dev/null @@ -1,20 +0,0 @@ -# Implementation Plan - -## UI - -- Add interactive behavior to the reusable status metric card through an optional select event. -- Wire the four overview metrics to existing settings route names. -- Extract the dashboard nostalgia card into a small reusable component and render it in Settings Overview. -- Add an option to hide the nostalgia card in the embedded dashboard instance to avoid duplication. -- Move the settings activity table below the dashboard in Settings Overview. - -## Data Flow - -- Keep `DashboardSettings` as the owner of usage dashboard loading. -- Emit the latest dashboard payload from `DashboardSettings` when it loads. -- Let `SettingsOverview` pass that payload into the extracted nostalgia component. - -## Test Strategy - -- Rely on existing Vue type checking and lint for component integration. -- Run the repository-required `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` after implementation. diff --git a/docs/features/settings-overview-cards/spec.md b/docs/features/settings-overview-cards/spec.md deleted file mode 100644 index 9dc9d0061..000000000 --- a/docs/features/settings-overview-cards/spec.md +++ /dev/null @@ -1,27 +0,0 @@ -# Settings Overview Cards - -## User Story - -As a user opening Settings Overview, I want the top status cards and setup panels to reflect the destinations and current setup state clearly, so I can jump to the right settings page quickly. - -## Acceptance Criteria - -- The top Providers, MCP, and DeepChat Agents status cards are clickable and navigate to their corresponding settings pages. -- Quick start appears as the fourth top-row card. -- The Quick start card itself is not clickable; only individual check items navigate to their target settings pages. -- The Needs attention panel is removed from the overview. -- The Last backup time metric card is removed from the overview top row. -- The usage dashboard appears immediately below the top row. -- The usage dashboard nostalgia card ("前尘往事") appears beside Token usage. -- Quick start tasks that are complete show a green check icon; incomplete tasks keep their configured icon. -- Recent changes appears at the bottom of the overview and is renamed Recent settings changes. - -## Non-goals - -- No changes to settings persistence, usage aggregation, backup behavior, or MCP/provider configuration. -- No new IPC routes. - -## Constraints - -- Keep user-facing strings in i18n files. -- Preserve existing dashboard loading and refresh behavior. diff --git a/docs/features/settings-overview-cards/tasks.md b/docs/features/settings-overview-cards/tasks.md deleted file mode 100644 index 288841a0f..000000000 --- a/docs/features/settings-overview-cards/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Make overview status metric cards navigable. -- [x] Move the nostalgia card into Settings Overview and hide the duplicate dashboard card. -- [x] Update Quick start completion icon styling. -- [x] Move and rename recent activity. -- [x] Run formatting, i18n, and lint checks. diff --git a/docs/features/settings-overview-quickstart-dashboard/plan.md b/docs/features/settings-overview-quickstart-dashboard/plan.md deleted file mode 100644 index 075ea9ae1..000000000 --- a/docs/features/settings-overview-quickstart-dashboard/plan.md +++ /dev/null @@ -1,12 +0,0 @@ -# Implementation Plan - -## Change - -- Replace the fourth metric card in `SettingsOverview.vue` with a compact Quick start card. -- Remove the standalone Quick start and Nostalgia section from the overview. -- Render `DashboardSettings` directly after the top row without hiding Nostalgia. -- Remove overview state that only existed to host the standalone Nostalgia card. - -## Validation - -- Run format, i18n, lint, web typecheck, and focused dashboard tests. diff --git a/docs/features/settings-overview-quickstart-dashboard/spec.md b/docs/features/settings-overview-quickstart-dashboard/spec.md deleted file mode 100644 index f3de95981..000000000 --- a/docs/features/settings-overview-quickstart-dashboard/spec.md +++ /dev/null @@ -1,20 +0,0 @@ -# Settings Overview Quick Start Dashboard Layout - -## User Story - -As a settings user, I want the overview page to keep the top row compact and bring the usage dashboard higher, so the page reads more like a focused control center. - -## Acceptance Criteria - -- The Last backup time metric card is removed from the overview top row. -- Quick start appears as the fourth top-row card. -- The Quick start card itself is not clickable. -- Only individual Quick start check items navigate to their target settings pages. -- Completed Quick start items use a green check icon; incomplete items use their original icons. -- The usage dashboard appears immediately below the top row. -- The Nostalgia card appears beside Token usage inside the dashboard. - -## Non-goals - -- No changes to backup, sync, dashboard data collection, or quick-start completion logic. -- No changes to settings route names. diff --git a/docs/features/settings-overview-quickstart-dashboard/tasks.md b/docs/features/settings-overview-quickstart-dashboard/tasks.md deleted file mode 100644 index 4eb3cacd1..000000000 --- a/docs/features/settings-overview-quickstart-dashboard/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Move Quick start into the top-row card position. -- [x] Move the usage dashboard directly below the top row. -- [x] Restore Nostalgia beside Token usage in the dashboard. -- [x] Run validation commands. diff --git a/docs/features/shortcut-system-group/plan.md b/docs/features/shortcut-system-group/plan.md deleted file mode 100644 index 8dc797e1f..000000000 --- a/docs/features/shortcut-system-group/plan.md +++ /dev/null @@ -1,9 +0,0 @@ -# Implementation Plan - -## Change - -- Update the `settings-shortcut` navigation item group from `setup` to `system`. - -## Validation - -- Run format, i18n, lint, and web typecheck. diff --git a/docs/features/shortcut-system-group/spec.md b/docs/features/shortcut-system-group/spec.md deleted file mode 100644 index 403e0ca74..000000000 --- a/docs/features/shortcut-system-group/spec.md +++ /dev/null @@ -1,15 +0,0 @@ -# Shortcut System Group - -## User Story - -As a settings user, I want Shortcut settings to appear under the System group, so the sidebar organization matches where I expect app-level controls to live. - -## Acceptance Criteria - -- Shortcut settings appears in the System sidebar group. -- The Shortcut route, label, and page behavior remain unchanged. - -## Non-goals - -- No changes to shortcut editing behavior. -- No changes to route paths or i18n keys. diff --git a/docs/features/shortcut-system-group/tasks.md b/docs/features/shortcut-system-group/tasks.md deleted file mode 100644 index 30d4aa354..000000000 --- a/docs/features/shortcut-system-group/tasks.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Move Shortcut settings to the System group. -- [x] Run validation commands. diff --git a/docs/features/sidebar-session-context-menu/plan.md b/docs/features/sidebar-session-context-menu/plan.md deleted file mode 100644 index e14f22af4..000000000 --- a/docs/features/sidebar-session-context-menu/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Sidebar Session Context Menu Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/sidebar-session-context-menu/spec.md b/docs/features/sidebar-session-context-menu/spec.md deleted file mode 100644 index 56810bc19..000000000 --- a/docs/features/sidebar-session-context-menu/spec.md +++ /dev/null @@ -1,29 +0,0 @@ -# Sidebar Session Context Menu - -## Overview - -Add a session context menu to the left sidebar list so users can right-click any session item and access the same core session actions already available from the chat top bar. - -## User Story - -As a user browsing the left session list, I want to right-click a session and manage it in place, so I do not need to open the session first just to pin, rename, clear, or delete it. - -## Acceptance Criteria - -1. Right-clicking a pinned or unpinned session item in `WindowSideBar` opens a context menu. -2. The context menu includes: - - `Pin` or `Unpin` depending on current state - - `Rename` - - `Clear Messages` - - `Delete` -3. Left-clicking a session item still activates that session exactly as before. -4. Selecting `Rename`, `Clear Messages`, or `Delete` opens the existing confirmation/input dialog flow from the sidebar. -5. Selecting `Pin` or `Unpin` updates the session pinned state through the existing session store action. -6. Pinned sessions remain rendered in the pinned section, and unpinned sessions remain rendered in grouped sections after actions complete. -7. All user-facing labels continue to come from existing i18n keys; no hard-coded menu text is introduced. - -## Non-Goals - -- Replacing the existing top bar session action menu -- Adding new session actions beyond the current top bar set -- Changing session grouping, sorting, or persistence behavior diff --git a/docs/features/sidebar-session-context-menu/tasks.md b/docs/features/sidebar-session-context-menu/tasks.md deleted file mode 100644 index 9c17e2c77..000000000 --- a/docs/features/sidebar-session-context-menu/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Sidebar Session Context Menu Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/sidebar-workspace-shortcuts/plan.md b/docs/features/sidebar-workspace-shortcuts/plan.md deleted file mode 100644 index 334e6ec9a..000000000 --- a/docs/features/sidebar-workspace-shortcuts/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Sidebar And Workspace Shortcuts Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/sidebar-workspace-shortcuts/spec.md b/docs/features/sidebar-workspace-shortcuts/spec.md deleted file mode 100644 index 337723890..000000000 --- a/docs/features/sidebar-workspace-shortcuts/spec.md +++ /dev/null @@ -1,25 +0,0 @@ -# Sidebar And Workspace Shortcuts - -## User Need - -Laptop users need faster access to more chat space without reaching for the mouse. DeepChat should provide keyboard shortcuts to toggle the left sidebar and the right workspace panel from the main chat window. - -## Defaults - -- `ToggleSidebar`: `CommandOrControl+B` -- `ToggleWorkspace`: `CommandOrControl+J` - -## Acceptance Criteria - -- Both shortcuts are registered through the existing shortcut settings flow. -- Both shortcuts appear in the settings shortcut page and can be customized. -- `ToggleSidebar` toggles the main chat sidebar collapsed state. -- `ToggleWorkspace` toggles the workspace side panel for the active chat session only. -- Shortcut events are delivered only to the currently focused DeepChat chat window. -- Existing sidebar and workspace buttons keep working without behavior changes. - -## Non-Goals - -- No persistence change for sidebar collapsed state. -- No new buttons, menu items, or layout redesign. -- No localization sweep for every existing locale in this increment. diff --git a/docs/features/sidebar-workspace-shortcuts/tasks.md b/docs/features/sidebar-workspace-shortcuts/tasks.md deleted file mode 100644 index 269c90f39..000000000 --- a/docs/features/sidebar-workspace-shortcuts/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Sidebar And Workspace Shortcuts Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/subagent-orchestrator/plan.md b/docs/features/subagent-orchestrator/plan.md deleted file mode 100644 index 38cb69ee5..000000000 --- a/docs/features/subagent-orchestrator/plan.md +++ /dev/null @@ -1,219 +0,0 @@ -# Subagent Orchestrator V1 实施计划 - -## 1. 基线 - -当前代码已经具备: - -1. `new_sessions` + `deepchat_sessions` 的新会话栈 -2. `AgentSessionPresenter` 负责 session 生命周期与 renderer IPC -3. `ToolPresenter -> AgentToolManager` 的 agent tool 路由 -4. `AgentRuntimePresenter` 的 tool call / permission / question / resume 流 -5. renderer 的 `MessageBlockToolCall`、`ChatToolInteractionOverlay`、`WorkspacePanel` - -缺的部分是: - -1. session 级 subagent 元数据 -2. orchestrator tool 和 child session runtime bridge -3. child 输出到 parent tool block 的 main-only progress 通道 -4. renderer 对 subagent cards / workspace child list / back link 的展示 - -## 2. 设计决策 - -### 2.1 数据层 - -在 `new_sessions` 保存 parent/child 关系,而不是新建额外表: - -1. child session 已经是完整真实 session -2. 删除、恢复、状态读取都能沿用现有 session 生命周期 -3. workspace 列表和 `getSessionList()` 只需增加过滤 - -### 2.2 Runtime Port 扩展 - -不让 `AgentToolManager` 直接依赖 presenter 实例,继续通过 `AgentToolRuntimePort` 间接访问: - -1. 查询当前会话是否允许 subagent tool -2. 创建 child session -3. 发送 child handoff message -4. 读取 parent/child session 信息 - -### 2.3 进度桥接 - -采用 main-only event: - -1. `dispatch.flushBlocksToRenderer()` 在 child assistant block 流式刷新时,同时发 main-only event -2. `AgentRuntimePresenter.setSessionStatus()` 和 `emitMessageRefresh()` 也发 main-only event -3. orchestrator 订阅这些事件,维护自己的内存态 queue - -这样避免: - -1. renderer 反向回传 -2. 轮询数据库 -3. 额外的 child polling loop - -### 2.4 Tool Progress 写回 - -tool 执行时通过 `IToolPresenter.callTool(..., { onProgress })` 回调: - -1. `dispatch.executeTools()` 收到 progress 时直接更新当前 tool_call block 的 `extra.subagentProgress` -2. tool 完成时写 `extra.subagentFinal` -3. `tool_call.response` 始终保留最终 markdown 汇总文本 - -### 2.5 Renderer Session 状态 - -`sessionStore.sessions` 继续只存 sidebar 可见的 regular sessions;另增 `activeSessionRecord`: - -1. sidebar 默认不显示 child -2. 当当前激活的是 child 时,`activeSession` 仍能拿到完整会话记录 -3. workspace 和 top bar 都能正确读取 child 的 parent metadata - -### 2.6 Workspace 子会话列表 - -在 `WorkspacePanel` 左侧导航追加 `Subagents` section,而不是新建独立侧栏: - -1. 信息层级与 artifacts/files/git 一致 -2. 与 parent workspace 语义自然贴近 -3. 避免引入新的 sidepanel store 结构 - -## 3. 分层改造 - -### Phase 1:Shared & Storage - -1. 扩展 `DeepChatAgentConfig`、session record、tool progress、presenter interface -2. 更新 `new_sessions` schema / migration / table accessors -3. 扩展 `NewSessionManager` 与 `AgentSessionPresenter` 的 create/list/delete API - -### Phase 2:Tool Runtime - -1. 扩展 `AgentToolRuntimePort` -2. 在 `AgentToolManager` 中新增 `subagent_orchestrator` schema、definition、gating -3. 实现 orchestrator 执行器: - - slot 校验 - - child session 创建 - - structured handoff - - parallel / chain 调度 - - progress snapshot 生成 - - final markdown 汇总 - -### Phase 3:DeepChat Progress Bridge - -1. 增加 main-only subagent runtime event 常量 -2. `dispatch` 在 child assistant block streaming 时发 event -3. `AgentRuntimePresenter` 在 message refresh / session status change 时发 event -4. `dispatch.executeTools()` 与 `executeDeferredToolCall()` 透传 `onProgress` / `signal` - -### Phase 4:Renderer - -1. `DeepChatAgentsSettings` 增加 subagent settings UI -2. `draftStore` / `NewThreadPage` / `ChatStatusBar` 支持 session-level subagent toggle -3. `MessageBlockToolCall` 对 `subagent_orchestrator` 渲染 card 视图 -4. `ChatPage` 扩展 pending interaction 扫描到 child progress -5. `WorkspacePanel` 增加 `Subagents` section -6. `ChatTopBar` 在 child session 显示 `Back to Parent` - -## 4. 关键实现细节 - -### 4.1 child handoff 模板 - -模板字段固定: - -1. Parent summary -2. Task title -3. Task prompt -4. Expected output -5. Workspace path -6. Child session rules - -模板输出必须英文,避免模型在 child 里混杂工具协议与 UI 本地化文案。 - -额外约束: - -1. 不注入 `Slot Description` -2. 不复制完整父 transcript -3. ACP-backed child 不追加 runtime/env/skills/tooling system prompt - -### 4.2 child target 路由 - -1. `self` / DeepChat target:继承父 session 的 provider/model/permission/generation settings/disabled tools/active skills -2. ACP target:使用原生 ACP provider 路由,`providerId='acp'`,`modelId=targetAgentId` -3. ACP target 只继承 `projectDir` 与 `permissionMode` -4. 路由决策由 `AgentSessionPresenter` 负责,不由 `subagent_orchestrator` tool 硬编码 provider/model - -### 4.3 preview 行提取 - -从 child 最近一次 assistant blocks 中提取展示行: - -1. `content` -2. `reasoning_content` -3. `action.content` -4. `tool_call.response` - -做法: - -1. 按 block 顺序拼平为文本行 -2. 去空白行 -3. 仅保留最近 3 行 - -### 4.4 waiting 状态判断 - -优先级: - -1. 当前存在 pending `tool_call_permission` -> `waiting_permission` -2. 当前存在 pending `question_request` -> `waiting_question` -3. runtime status=`generating` -> `running` -4. aborted by signal -> `cancelled` -5. runtime status=`error` -> `error` -6. 否则 `completed` - -### 4.5 child 初始化失败重试 - -1. 只在 child 初始化阶段自动重试 1 次 -2. 首次失败后销毁旧 session runtime,并清理 ACP session -3. 第二次失败直接结束,不重放已经开始执行的 child 任务内容 - -### 4.4 父删子级联 - -在 `AgentSessionPresenter.deleteSession()` 递归查 child: - -1. 父删除时先深度删除所有 child -2. child 单独删除不反查父 -3. 删除 child 时不删除 parent - -## 5. 测试策略 - -### Main - -1. SQLite migration / default columns -2. `NewSessionManager` 读写新字段 -3. `AgentSessionPresenter.getSessionList()` 过滤与级联删除 -4. `AgentToolManager` tool gating -5. `subagent_orchestrator` 的 parallel / chain 执行顺序 -6. progress snapshot 与 preview 裁剪 - -### Renderer - -1. `MessageBlockToolCall` subagent card 渲染与自动折叠 -2. `ChatPage` overlay 从 `subagentProgress` 提取 child interaction -3. `WorkspacePanel` 子会话列表点击切换 -4. `ChatTopBar` child session back-to-parent -5. `DeepChatAgentsSettings` slot 默认值、上限和保存载入 - -## 6. 风险与缓解 - -1. 风险:active child session 不在 sidebar sessions 中,导致顶部/状态栏读不到 session。 - 缓解:session store 维护独立 `activeSessionRecord`。 - -2. 风险:tool progress 写回过于频繁,造成 renderer 抖动。 - 缓解:只在 child blocks/status 真变化时发 progress;preview 只保留 3 行。 - -3. 风险:父会话取消时 child 仍继续跑。 - 缓解:`signal` 中断 orchestrator;对子 session 调 `cancelGeneration()` 并标记 `cancelled`。 - -4. 风险:slot 指向 ACP 或禁用 agent。 - 缓解:tool gating 与执行前双重过滤,无效 slot 不暴露、不执行。 - -## 7. 验证门槛 - -1. `pnpm run format` -2. `pnpm run i18n` -3. `pnpm run lint` -4. 关键 main / renderer 测试通过 diff --git a/docs/features/subagent-orchestrator/spec.md b/docs/features/subagent-orchestrator/spec.md deleted file mode 100644 index e0c09d78a..000000000 --- a/docs/features/subagent-orchestrator/spec.md +++ /dev/null @@ -1,312 +0,0 @@ -# Subagent Orchestrator V1 规格 - -## 背景 - -现有多工具式 subagent 方案把 child session 管理、等待、汇总拆散给模型,导致: - -1. 工具暴露面过大,模型需要自己编排 `run / wait / cancel / list` -2. parent loop 会过早接触中间结果,增加 prompt 噪音 -3. child session 与父会话的 UI 关联不够稳定,权限/提问桥接也不够直接 - -V1 目标是把能力收口为单一 agent tool `subagent_orchestrator`,由 tool 内部完成 child session 编排、监听和最终汇总。 - -## 用户故事 - -1. 作为 DeepChat regular session 用户,我希望模型只调用一个 subagent tool,就能并行或串行分派多个子任务。 -2. 作为父会话用户,我希望看到 child 的实时进度卡片,但模型只在全部 child 结束后收到一次最终汇总。 -3. 作为需要审批/回答问题的用户,我希望仍然只在父会话顶部 overlay 处理交互,不需要切到 child 才能继续。 -4. 作为查看上下文的用户,我希望 child 是真实独立 session,能在右侧 workspace 的 `Subagents` 区查看、切换、回到父会话。 - -## 范围 - -### In - -1. 单一公开 agent tool:`subagent_orchestrator` -2. 两种执行模式:`parallel`、`chain` -3. DeepChat agent 配置中的 subagent slot 管理 -4. parent -> child 单层关联 -5. main-process 内部 child 输出监听 -6. 父会话 tool block 进度卡片、顶部 overlay 桥接、workspace 子会话列表 - -### Out - -1. `expanded` 模式 -2. child 再派生 child -3. 多层树状工作流 -4. parent loop 消费 partial child tool result -5. card 内直接批准权限或直接回答问题 - -## 约束 - -1. 单次 `subagent_orchestrator` 最多 5 个 task -2. agent 配置 slot 最多 5 条 -3. 只允许引用当前父会话 agent 配置里已启用的 slot -4. 只有 `sessionKind='regular'` 且 `agentType='deepchat'` 且 `subagentEnabled=true` 的父会话暴露该工具 -5. ACP session 与 subagent child session 不暴露该工具 - -## 配置与数据模型 - -### Agent 配置 - -`DeepChatAgentConfig` 增加: - -1. `subagentEnabled: boolean` -2. `subagents: DeepChatSubagentSlot[]` - -`DeepChatSubagentSlot` 定义: - -1. `id: string` -2. `targetType: 'self' | 'agent'` -3. `targetAgentId?: string` -4. `displayName: string` -5. `description: string` - -规则: - -1. 默认预置 1 条 `self` slot -2. `self` 表示继承父会话 agent 逻辑,但 child 使用独立上下文 -3. 非法 `targetAgentId` 在读取配置和工具执行时都要过滤 - -### Session 输入 - -`CreateSessionInput` / `CreateDetachedSessionInput` 增加: - -1. `subagentEnabled?: boolean` - -### `new_sessions` - -新增列: - -1. `subagent_enabled INTEGER NOT NULL DEFAULT 0` -2. `session_kind TEXT NOT NULL DEFAULT 'regular'` -3. `parent_session_id TEXT` -4. `subagent_meta_json TEXT` - -### Session 读取模型 - -`SessionRecord` / `SessionWithState` 增加: - -1. `sessionKind: 'regular' | 'subagent'` -2. `parentSessionId?: string | null` -3. `subagentEnabled: boolean` -4. `subagentMeta?: { slotId: string; displayName: string; targetAgentId?: string | null } | null` - -## Tool 接口 - -`subagent_orchestrator` 参数固定为: - -```ts -{ - mode: 'parallel' | 'chain' - tasks: Array<{ - id?: string - slotId: string - title: string - prompt: string - expectedOutput?: string - }> -} -``` - -`IToolPresenter.callTool()` 增加可选参数: - -1. `onProgress?: (update: AgentToolProgressUpdate) => void` -2. `signal?: AbortSignal` - -`AgentToolProgressUpdate` V1 只定义: - -```ts -{ - kind: 'subagent_orchestrator' - toolCallId: string - responseMarkdown: string - progressJson: string -} -``` - -`AssistantMessageExtra` 增加: - -1. `subagentProgress?: string` -2. `subagentFinal?: string` - -## Child Session 生命周期 - -### 创建 - -tool 内部通过 runtime port 调 `AgentSessionPresenter` 创建 child session: - -1. `self` slot 继承父 `agentId` -2. `agent` slot 使用 `slot.targetAgentId` -3. 继承父 `projectDir` -4. DeepChat target 继承父 session 的 `provider/model/permission/generationSettings/disabledAgentTools/activeSkills` -5. ACP target 强制使用 `providerId='acp'`、`modelId=targetAgentId`、`agentId=targetAgentId` -6. ACP target 只继承 `projectDir` 与 `permissionMode`,不继承本地 tools / skills / model preset -7. child session 写入 `session_kind='subagent'` -8. child session 写入 `parent_session_id=父 sessionId` -9. child session 写入 `subagent_meta_json` - -### 首条 handoff - -child 首条消息固定使用 structured handoff 模板,仅包含: - -1. 父任务摘要 -2. 当前子任务 -3. 输出契约 -4. 工作区路径 -5. 固定规则(isolated child session / ask via normal tool flow) - -明确不注入: - -1. `Slot Description` -2. 完整父 transcript -3. DeepChat runtime capabilities / env / skills / tooling system prompt(ACP-backed child) - -不复制完整父 transcript。 - -### 初始化失败重试 - -1. 只在 child session 创建早期失败时自动重试 1 次 -2. 覆盖 ACP warmup / session init / createSubagentSession 早期失败 -3. 重试必须用全新 child session / 全新 warmup -4. 若第二次仍失败,直接返回终态 error,并带可诊断错误文本 - -### 结束态 - -child terminal status 归一为: - -1. `queued` -2. `running` -3. `waiting_permission` -4. `waiting_question` -5. `completed` -6. `error` -7. `cancelled` - -## 执行语义 - -### `parallel` - -1. 立即创建并启动全部 child -2. 并发等待全部 child 完成 -3. child 结果先缓存在 orchestrator 内部 queue -4. 全部结束后按原 `tasks` 顺序聚合最终结果 - -### `chain` - -1. 按 `tasks` 顺序逐个创建 child -2. 每个 child 结束后再启动下一个 -3. 每个结果进入内部 queue -4. 最终仍按原 `tasks` 顺序聚合 - -### 共同行为 - -1. parent loop 不消费 partial child output -2. tool 完成前,父会话只写 progress,不把中间结果作为 tool message 喂给模型 -3. 最终 `tool_call.response` 与 tool output 都使用同一份 markdown 汇总文本 -4. ACP-backed child 不加载本地 tool definitions,也不拼 DeepChat tooling system prompt - -## Progress Payload - -`subagentProgress` / `subagentFinal` 使用统一 JSON 结构: - -```ts -{ - runId: string - mode: 'parallel' | 'chain' - tasks: Array<{ - taskId: string - title: string - slotId: string - sessionId: string | null - targetAgentId: string | null - targetAgentName: string - status: 'queued' | 'running' | 'waiting_permission' | 'waiting_question' | 'completed' | 'error' | 'cancelled' - previewMarkdown: string - updatedAt: number - waitingInteraction?: { - messageId: string - toolCallId: string - actionType: 'tool_call_permission' | 'question_request' - toolName: string - toolArgs: string - } | null - resultSummary?: string - }> -} -``` - -规则: - -1. `previewMarkdown` 只保留最近 3 条非空展示行 -2. 进度事件来自 main-process 内部 child 输出观察通道,不依赖 renderer,不轮询 DB - -## 最终汇总 - -最终 markdown 文本按原 `tasks` 顺序输出,每项包含: - -1. 序号 -2. 标题 -3. 子 agent 名称 -4. child sessionId -5. 结果摘要 - -## UI 验收 - -### Agent Settings - -`DeepChat Agents` 扩展 `Subagents` 分区: - -1. 总开关 -2. slot 列表 -3. `+ Add Slot` -4. target agent 选择 -5. 描述编辑 -6. 5 条上限 - -### Session UI - -1. 会话层只保留一个 `Subagents` toggle -2. 只在 DeepChat regular session 可见 -3. ACP 与 child session 隐藏 - -### Tool Block - -`MessageBlockToolCall` 对 `subagent_orchestrator` 特判: - -1. 运行中自动展开 -2. 完成后自动折叠 -3. 详情区显示 subagent cards,不走普通 `pre` -4. summary 固定为 `parallel · N subagents` 或 `chain · N subagents` - -### Overlay - -父会话顶部 overlay 继续复用 `ChatToolInteractionOverlay`: - -1. 同时扫描普通 action block 与 `subagent_orchestrator` 的 `extra.subagentProgress` -2. child 进入等待态时显示待处理项 -3. 响应直接路由到 `respondToolInteraction(childSessionId, ...)` - -### Workspace - -父会话右侧 workspace 增加 `Subagents` section: - -1. 列出当前父会话全部 child -2. 展示 `displayName / target agent / status / updatedAt` -3. 点击切换 child session -4. child 顶部有 `Back to Parent` - -## 兼容与迁移 - -1. 旧库升级后,原有 session 默认 `session_kind='regular'` -2. 旧库升级后,原有 session 默认 `subagent_enabled=0` -3. 左侧 sidebar 默认仍只显示普通会话 -4. 父删子级联;子单删不影响父 - -## 验收标准 - -1. 只有 DeepChat regular session 且 subagentEnabled 打开时,模型能看到 `subagent_orchestrator` -2. `parallel` 并发启动全部 child,`chain` 串行启动 -3. parent tool block 能随 child 输出实时更新 card 预览 -4. child 的 permission/question 会在父会话 overlay 中处理 -5. child session 在 workspace `Subagents` 区可切换 -6. 应用重启后,父子关联与 workspace 子会话列表仍可恢复 diff --git a/docs/features/subagent-orchestrator/tasks.md b/docs/features/subagent-orchestrator/tasks.md deleted file mode 100644 index 011161288..000000000 --- a/docs/features/subagent-orchestrator/tasks.md +++ /dev/null @@ -1,55 +0,0 @@ -# Subagent Orchestrator V1 任务拆分 - -## 1. Shared / Schema - -1. 扩展 `DeepChatAgentConfig`、`DeepChatSubagentSlot`、session record、tool progress 类型 -2. 扩展 `IAgentSessionPresenter` / `IToolPresenter` -3. 为 `new_sessions` 增加 subagent 相关列与迁移测试 - -## 2. Main Session Layer - -1. 更新 `NewSessionsTable` / `NewSessionManager` create-get-list-update -2. 更新 `AgentSessionPresenter`: - - create session / detached session 支持 `subagentEnabled` - - `getSessionList()` 支持 `includeSubagents` / `parentSessionId` - - `setSessionSubagentEnabled()` - - 父删子级联 - - 根据 target agent type 决定 subagent 的 provider/model/tooling 继承矩阵 - - child 初始化失败时只自动重试 1 次 -3. 增加 runtime port 的 subagent session helper - -## 3. Tool Runtime - -1. 在 `AgentToolManager` 增加 `subagent_orchestrator` tool schema / definition / gating -2. 实现 slot 校验与 `self` slot 继承逻辑 -3. 实现 child session 创建、最小 handoff、parallel / chain 调度 -4. 生成 progress payload 与 final markdown -5. 接入 abort signal -6. ACP-backed child 不注入本地 tools 与 DeepChat tool system prompt - -## 4. DeepChat Bridge - -1. 定义 main-only subagent runtime events -2. `dispatch` 发 child block update event -3. `AgentRuntimePresenter` 发 child status / refresh event -4. `dispatch.executeTools()` / `executeDeferredToolCall()` 接 `onProgress` -5. 把 `subagentProgress` / `subagentFinal` 写回 assistant block extra - -## 5. Renderer - -1. `draftStore` / `NewThreadPage` / `ChatStatusBar` 增加 session-level subagent toggle -2. `DeepChatAgentsSettings` 增加 subagent settings UI -3. `MessageBlockToolCall` 渲染 subagent cards -4. `ChatPage` overlay bridge 扫描 child waiting interaction -5. `WorkspacePanel` 增加 `Subagents` section -6. `ChatTopBar` 增加 `Back to Parent` - -## 6. Tests & Validation - -1. main:migration / presenter / tool execution / progress -2. main:覆盖 ACP target 路由、最小 handoff、一次重试 -3. main:覆盖 ACP-backed child 零本地工具注入,且 regular ACP 不回退 -4. renderer:tool cards / overlay / workspace / settings -5. 运行 `pnpm run format` -6. 运行 `pnpm run i18n` -7. 运行 `pnpm run lint` diff --git a/docs/features/tool-call-image-preview/plan.md b/docs/features/tool-call-image-preview/plan.md deleted file mode 100644 index d1adcae97..000000000 --- a/docs/features/tool-call-image-preview/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Tool Call Image Preview Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented feature goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/features/tool-call-image-preview/spec.md b/docs/features/tool-call-image-preview/spec.md deleted file mode 100644 index dbb57d5c8..000000000 --- a/docs/features/tool-call-image-preview/spec.md +++ /dev/null @@ -1,27 +0,0 @@ -# Tool Call Image Preview - -## Summary - -Tool calls can return image output through structured MCP content, screenshot payloads, file reads, or explicit image URLs. The chat UI should keep existing params and response sections intact, then show a dedicated preview area below them when image output is available. - -## Acceptance Criteria - -- Existing params and response rendering stays in the same order and keeps current copy actions, diff rendering, terminal styling, and text output behavior. -- Tool call blocks may persist `tool_call.imagePreviews`, where each item includes `id`, `data`, `mimeType`, optional `title`, and a source value. -- Expanded tool blocks render image previews below the response section. -- Collapsed tool blocks show a compact image count badge when previews exist. -- Text-only tool output keeps the current UI. - -## Data Flow - -- Main process extracts image previews from tool output before response normalization. -- MCP structured image items use source `mcp_image`. -- `cdp_send` `Page.captureScreenshot` results use source `screenshot`. -- Agent `read` image results keep vision analysis in `response` and attach the original image as `file_read`. -- Image data is cached as `imgcache://` when an image cache function is available, with data URL and web URL fallbacks. - -## Test Coverage - -- Renderer tests cover collapsed image count, expanded preview placement, and image rendering. -- Main dispatch tests cover ordinary tool results with structured image output. -- Deferred tool execution tests cover image previews returned through `rawData`. diff --git a/docs/features/tool-call-image-preview/tasks.md b/docs/features/tool-call-image-preview/tasks.md deleted file mode 100644 index f1ab7e984..000000000 --- a/docs/features/tool-call-image-preview/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tool Call Image Preview Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/unified-tts-provider/plan.md b/docs/features/unified-tts-provider/plan.md deleted file mode 100644 index 2d6f02b05..000000000 --- a/docs/features/unified-tts-provider/plan.md +++ /dev/null @@ -1,37 +0,0 @@ -# Plan - -## Approach -Treat TTS as a first-class model capability and follow the `ImageGeneration` routing strategy: -- Extend shared model/type schema to include `tts`. -- Add runtime TTS routing ahead of default chat generation. -- Dispatch by model pattern: - - Pattern A: `/v1/audio/speech` - - Pattern B: `/v1/chat/completions` with `audio` output -- Normalize returned audio into data URL and cache through existing device cache, then emit `image_data` with audio MIME type. - -## Affected Areas -- Shared types/contracts: - - `src/shared/model.ts` - - `src/shared/types/model-db.ts` - - `src/shared/types/presenters/legacy.presenters.d.ts` - - `src/shared/contracts/common.ts` - - `src/shared/contracts/domainSchemas.ts` - - `src/shared/ttsSettings.ts` (new) -- Main runtime/provider: - - `src/main/presenter/llmProviderPresenter/aiSdk/runtime.ts` - - `src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts` -- Model DB: - - `resources/model-db/providers.json` -- Renderer model type detection: - - `src/renderer/src/composables/useModelTypeDetection.ts` - -## Compatibility -- Existing chat and image generation paths remain unchanged. -- Existing renderer audio playback remains unchanged because it already handles `image_data` with `audio/*` MIME. - -## Verification Strategy -Run: -- `pnpm run typecheck` -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` diff --git a/docs/features/unified-tts-provider/spec.md b/docs/features/unified-tts-provider/spec.md deleted file mode 100644 index beba05f9f..000000000 --- a/docs/features/unified-tts-provider/spec.md +++ /dev/null @@ -1,34 +0,0 @@ -# Unified TTS Provider (Model-Level) - -## User Need -Users want TTS integrated as a model capability (`ModelType.TTS`) instead of per-provider custom integration, so any OpenAI-compatible provider can work if its model metadata marks TTS support. - -## Goal -Enable model-level TTS routing in DeepChat similar to image generation routing, including: -- Standard OpenAI `/v1/audio/speech` TTS models -- Chat-completions-audio TTS models that return base64 audio - -## Acceptance Criteria -1. `ModelType.TTS` is available in shared model contracts and model-db schema. -2. Runtime can route TTS models by model capability metadata and endpoint hints. -3. Runtime supports both TTS patterns and emits `image_data` events with `audio/*` MIME type for existing renderer playback. -4. Model DB can represent TTS model type for built-in provider entries. -5. Frontend model type detection exposes TTS model state for UI behavior alignment. -6. Validation commands pass: -- `pnpm run typecheck` -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` - -## Constraints -- Reuse existing audio rendering path via `image_data`; avoid introducing new stream event types. -- Keep provider integration generic for OpenAI-compatible providers. -- Do not introduce dedicated UI for TTS settings in this scope. - -## Non-Goals -- New TTS player UI. -- Voice catalog fetching UX. -- VoiceAI provider refactor. - -## Open Questions -- None for current scope. diff --git a/docs/features/unified-tts-provider/tasks.md b/docs/features/unified-tts-provider/tasks.md deleted file mode 100644 index 10e2e1cb3..000000000 --- a/docs/features/unified-tts-provider/tasks.md +++ /dev/null @@ -1,22 +0,0 @@ -# Tasks - -## Shared Types + Runtime -- [x] Add `ModelType.TTS` and `ApiEndpointType.AudioSpeech` in shared model enums. -- [x] Extend model-db schema and parser for `tts` type. -- [x] Add `src/shared/ttsSettings.ts` helpers for pattern detection and format normalization. -- [x] Extend presenter model config contracts with optional `tts` settings. -- [x] Add TTS route in runtime supporting pattern A and pattern B. -- [x] Inject `shouldUseTts` capability check from AI SDK provider. - -## Model DB -- [x] Mark relevant `aihubmix` models as `type: "tts"` in provider model list. -- [x] Evaluate whether built-in `xiaomimimo` provider entry exists; it does not, so built-in DB coverage is skipped. - -## Renderer -- [x] Extend `useModelTypeDetection` to include `tts` and expose `isTtsModel`. - -## Validation -- [x] Run `pnpm run typecheck`. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. diff --git a/docs/features/user-message-collapse/plan.md b/docs/features/user-message-collapse/plan.md deleted file mode 100644 index 24e287925..000000000 --- a/docs/features/user-message-collapse/plan.md +++ /dev/null @@ -1,114 +0,0 @@ -# 用户长文本消息默认折叠实施计划 - -## 1. 关键决策 - -1. 本功能仅作用于 renderer 的聊天消息展示层,不改消息存储、Presenter、IPC 或模型上下文构造。 -2. 折叠对象仅限用户消息正文;附件区、工具区、编辑态不参与正文裁切。 -3. 默认折叠是运行时 UI 判断,不新增持久化字段,也不记忆展开状态到消息数据层。 -4. 折叠按钮复用现有 `common.expand` / `common.collapse` 文案,不新增设置项和自定义阈值。 - -## 2. 范围映射 - -### 对应 spec 目标 - -1. 折叠规则: - - 以可见正文字符串为基础计算字符数与换行数。 - - 同时满足“长度/换行阈值命中”与“实际渲染高度超过 12 行预览高度”才进入可折叠态。 -2. 触发时机: - - 初次渲染消息时评估。 - - 消息正文内容变化时重评估。 - - 容器尺寸变化或字体度量变化后重评估。 -3. UI 状态: - - `not-collapsible` - - `collapsible-collapsed` - - `collapsible-expanded` - - `editing` - -## 3. 渲染层实现方案 - -### 3.1 消息正文检测 - -1. 在用户消息组件或其正文子组件中抽出纯函数: - - 提取可见正文文本 - - 统计字符数 - - 统计显式换行数 -2. 结构化内容优先按渲染可见文本拼接。 -3. 非结构化内容退回 `message.content.text`。 - -### 3.2 折叠判定与高度测量 - -1. 使用正文容器 `ref` 读取真实渲染高度。 -2. 预览高度计算规则: - - 优先读取 computed `line-height` - - 若无法解析,回退 `font-size * 1.5` - - 预览高度 = `line-height * 12` -3. 只有正文真实高度超过预览高度时,才显示遮罩与切换按钮。 - -### 3.3 UI 交互 - -1. 默认命中折叠条件时进入折叠态。 -2. 点击 `Expand` 展开完整正文。 -3. 点击 `Collapse` 回到预览高度。 -4. 按钮和渐隐遮罩仅在 `collapsible-collapsed` 显示。 - -### 3.4 例外态 - -1. 编辑态始终展示完整内容,不应用裁切高度。 -2. 附件区域放在正文折叠容器之外,始终完整可见。 -3. 若内容变化或 resize 后不再超高,自动移除折叠状态和切换按钮。 - -## 4. 里程碑 - -### M1 判定逻辑 - -1. 实现正文文本提取与阈值判断。 -2. 实现高度测量与 12 行预览高度计算。 -3. 补纯函数和边界条件测试。 - -### M2 消息 UI - -1. 给用户消息正文接入默认折叠态。 -2. 增加渐隐遮罩与展开/收起按钮。 -3. 确保附件区和编辑态不受影响。 - -### M3 动态重评估 - -1. 监听内容变化与容器 resize。 -2. 在窗口尺寸变化后重新判定是否仍需折叠。 -3. 确保展开态和自动解折叠之间的状态切换一致。 - -### M4 QA 与回归 - -1. 验证短文本、长文本、仅多行、带附件、编辑态五类场景。 -2. 验证 resize 后从可折叠变不可折叠的自动恢复。 -3. 验证现有消息渲染、搜索、高亮、编辑流程无回归。 - -## 5. 测试与验收 - -### Renderer 单测 - -1. 文本提取逻辑覆盖结构化/非结构化消息。 -2. 阈值判断覆盖字符阈值、换行阈值、双阈值未命中。 -3. UI 状态覆盖默认折叠、手动展开、再次折叠、自动取消折叠。 -4. 编辑态与附件区的豁免逻辑单独验证。 - -### 手动验证 - -1. 在聊天页发送超长纯文本,确认默认折叠。 -2. 在聊天页发送带附件的超长文本,确认仅正文折叠。 -3. 进入编辑态,确认 textarea 为完整内容。 -4. 调整窗口宽度,确认会重新评估并在不再超高时取消折叠。 - -## 6. 风险与缓解 - -1. 风险:结构化内容提取与真实渲染文本不一致。 - - 缓解:先做最小可见文本拼接规则,并用现有消息样本补测试。 -2. 风险:频繁测量高度导致渲染抖动。 - - 缓解:把重评估限制在挂载后、消息变化后和 `ResizeObserver` 回调中。 -3. 风险:正文和附件布局耦合,导致附件误被裁切。 - - 缓解:明确正文容器与附件容器分离,折叠样式只挂在正文容器。 - -## 7. 澄清状态 - -1. 当前 `spec.md` 中没有遗留 `[NEEDS CLARIFICATION]` 条目。 -2. 若实现阶段新增阈值、遮罩样式或动画行为分歧,需先回写 spec 再继续实现。 diff --git a/docs/features/user-message-collapse/spec.md b/docs/features/user-message-collapse/spec.md deleted file mode 100644 index a8165f1c4..000000000 --- a/docs/features/user-message-collapse/spec.md +++ /dev/null @@ -1,42 +0,0 @@ -# 用户长文本消息默认折叠 - -## 概述 - -为聊天页中的用户消息正文增加“长文本默认折叠”能力,避免单条超长输入把消息面板撑得过高,影响滚动和后续对话阅读。 - -本次只处理 renderer 展示层,不改变消息存储、消息 schema、Presenter / IPC 或模型上下文拼装逻辑。 - -## 目标 - -1. 已发送的超长用户消息默认只展示预览高度。 -2. 用户可以手动展开和重新折叠正文。 -3. 附件展示不参与折叠,始终完整可见。 -4. 编辑态始终展示完整文本,不受折叠影响。 - -## 非目标 - -1. 不把长文本转换成文件引用再交给模型读取。 -2. 不新增设置项,不支持自定义折叠阈值。 -3. 不调整聊天搜索的当前行为,搜索命中折叠消息时不自动展开。 - -## 需求 - -- 折叠仅作用于用户消息正文区域。 -- 正文长度判定规则: - - 使用结构化内容时,按可见字符串内容拼接后判断。 - - 否则按 `message.content.text` 判断。 -- 触发条件固定为: - - 文本长度大于等于 `600` 字符,或显式换行行数大于等于 `8`; - - 且实际渲染高度超过 `12` 行预览高度。 -- 预览高度使用正文实际 `line-height * 12`;若 `line-height` 无法直接解析,则回退为 `fontSize * 1.5`。 -- 若窗口或内容变化后不再超过预览高度,自动取消折叠状态并隐藏切换按钮。 -- 折叠按钮复用 `common.expand` / `common.collapse` 文案。 - -## 验收标准 - -1. 短文本用户消息不出现折叠按钮。 -2. 长文本用户消息默认折叠,并显示渐隐遮罩与展开按钮。 -3. 点击展开后显示完整正文,再次点击可重新折叠。 -4. 带附件的长文本消息中,附件区域始终完整显示。 -5. 进入编辑态后显示完整 textarea,不保留正文折叠裁切。 -6. 内容区域尺寸变化后会重新评估折叠状态。 diff --git a/docs/features/user-message-collapse/tasks.md b/docs/features/user-message-collapse/tasks.md deleted file mode 100644 index 95673d17b..000000000 --- a/docs/features/user-message-collapse/tasks.md +++ /dev/null @@ -1,60 +0,0 @@ -# 用户长文本消息默认折叠任务清单 - -## T0 规格文档 - -- [x] 创建 `docs/features/user-message-collapse/spec.md` -- [x] 创建 `docs/features/user-message-collapse/plan.md` -- [x] 创建 `docs/features/user-message-collapse/tasks.md` -- [x] 检查 `spec.md` 中是否存在未解决的 `[NEEDS CLARIFICATION]` - -当前状态: -当前没有未解决的 `[NEEDS CLARIFICATION]` 项;如后续新增,需先找 stakeholder 确认再进入实现。 - -## T1 正文提取与判定逻辑 - -- [x] 抽取用户消息正文可见文本的纯函数 -- [x] 支持结构化内容拼接后的字符数与换行数统计 -- [x] 支持 `message.content.text` 的后备路径 -- [x] 实现 `>= 600` 字符或 `>= 8` 行的阈值判断 -- [x] 为正文提取和阈值逻辑补 renderer 单测 - -## T2 高度测量与可折叠检测 - -- [x] 为用户消息正文增加可测量的容器 `ref` -- [x] 从 computed style 读取 `line-height` -- [x] `line-height` 不可解析时回退到 `font-size * 1.5` -- [x] 根据 `12` 行高度计算预览高度 -- [x] 真实渲染高度未超过预览高度时隐藏折叠按钮 - -## T3 折叠/展开 UI - -- [x] 给用户消息正文增加默认折叠态 -- [x] 增加渐隐遮罩样式 -- [x] 接入 `common.expand` / `common.collapse` 按钮文案 -- [x] 支持展开后再次折叠 -- [x] 补默认折叠、展开、再次折叠的组件测试 - -## T4 附件与编辑态豁免 - -- [x] 把附件区域放在正文折叠容器之外 -- [x] 验证长文本 + 附件时附件始终完整可见 -- [x] 编辑态直接显示完整 textarea,不应用正文裁切 -- [x] 补附件豁免与编辑态豁免测试 - -## T5 动态重评估 - -- [x] 在正文内容变化后重新检测可折叠状态 -- [x] 用 `ResizeObserver` 或等价机制在容器尺寸变化后重扫 -- [x] resize 后若不再超高,自动取消折叠并隐藏按钮 -- [x] 补窗口 resize / 内容变化后的状态切换测试 - -## T6 QA 与回归 - -- [x] 手测短文本消息不出现折叠按钮 -- [x] 手测超长纯文本默认折叠 -- [x] 手测带附件的超长消息仅正文折叠 -- [x] 手测编辑态显示完整内容 -- [x] 手测 resize 后重新判定和自动解折叠 -- [x] 运行 `pnpm run format` -- [x] 运行 `pnpm run i18n` -- [x] 运行 `pnpm run lint` diff --git a/docs/features/v1-0-4-stable-release/plan.md b/docs/features/v1-0-4-stable-release/plan.md deleted file mode 100644 index 2833a811b..000000000 --- a/docs/features/v1-0-4-stable-release/plan.md +++ /dev/null @@ -1,20 +0,0 @@ -# v1.0.4 Stable Release Plan - -## Release Metadata - -- Update `package.json` from `1.0.4-beta.8` to `1.0.4`. -- Add the official `v1.0.4` changelog section dated `2026-05-15`. - -## Validation - -- Run `pnpm run format`. -- Run `pnpm run i18n`. -- Run `pnpm run lint`. -- Run `pnpm run typecheck` before cutting the release branch. - -## Publishing - -- Commit the metadata on `dev`. -- Push `dev`. -- Cut `release/v1.0.4` from the release-ready `dev` commit. -- Fast-forward `main` and create `v1.0.4` when the release commit is ready to publish. diff --git a/docs/features/v1-0-4-stable-release/spec.md b/docs/features/v1-0-4-stable-release/spec.md deleted file mode 100644 index 971c78c9d..000000000 --- a/docs/features/v1-0-4-stable-release/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# v1.0.4 Stable Release Spec - -## User Story - -As a DeepChat maintainer, I want to promote the stable v1.0.4 beta series to an official release so users can receive a non-prerelease update with the accumulated improvements from the 1.0.4 cycle. - -## Acceptance Criteria - -- The root package version is `1.0.4`. -- `CHANGELOG.md` contains a top-level `v1.0.4 (2026-05-15)` section. -- Release notes summarize user-visible and release-relevant changes since `v1.0.3`. -- The release branch and tag use `release/v1.0.4` and `v1.0.4`. - -## Non-Goals - -- No product behavior changes are introduced as part of the release metadata update. -- No historical changelog sections are rewritten. - -## Constraints - -- Follow the repository release flow with `dev` as the integration branch and `main` updated by fast-forward. -- Keep changelog entries bilingual with English bullets before Chinese bullets. diff --git a/docs/features/v1-0-4-stable-release/tasks.md b/docs/features/v1-0-4-stable-release/tasks.md deleted file mode 100644 index ecef36746..000000000 --- a/docs/features/v1-0-4-stable-release/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# v1.0.4 Stable Release Tasks - -- [x] Confirm current branch, working tree, release branch, and tag state. -- [x] Update package version and changelog. -- [x] Run format, i18n, lint, and typecheck checks. -- [ ] Commit and push release metadata on `dev`. -- [ ] Cut and push `release/v1.0.4`. -- [ ] Fast-forward `main`, tag `v1.0.4`, and clean up the release branch. diff --git a/docs/features/v1-0-5-beta-1-release/plan.md b/docs/features/v1-0-5-beta-1-release/plan.md deleted file mode 100644 index d476c9d54..000000000 --- a/docs/features/v1-0-5-beta-1-release/plan.md +++ /dev/null @@ -1,22 +0,0 @@ -# v1.0.5 Beta 1 Release Plan - -## Release Metadata - -- Update `package.json` from `1.0.4` to `1.0.5-beta.1`. -- Add the `v1.0.5-beta.1` changelog section dated `2026-05-19`. -- Derive release notes from commits after `v1.0.4`. - -## Validation - -- Run `pnpm run format`. -- Run `pnpm run i18n`. -- Run `pnpm run lint`. -- Run `pnpm run typecheck` before cutting the release branch. - -## Publishing - -- Commit the metadata on `dev`. -- Push `dev`. -- Cut `release/v1.0.5-beta.1` from the release-ready `dev` commit. -- Open the release PR to `main` for review and CI. -- Fast-forward `main`, tag `v1.0.5-beta.1`, and clean up the release branch after PR approval. diff --git a/docs/features/v1-0-5-beta-1-release/spec.md b/docs/features/v1-0-5-beta-1-release/spec.md deleted file mode 100644 index 092d768f5..000000000 --- a/docs/features/v1-0-5-beta-1-release/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# v1.0.5 Beta 1 Release Spec - -## User Story - -As a DeepChat maintainer, I want to publish `v1.0.5-beta.1` so beta users can try the accumulated changes after the `v1.0.4` stable release. - -## Acceptance Criteria - -- The root package version is `1.0.5-beta.1`. -- `CHANGELOG.md` contains a top-level `v1.0.5-beta.1 (2026-05-19)` section. -- Release notes summarize user-visible and release-relevant changes since `v1.0.4`. -- The release branch and tag use `release/v1.0.5-beta.1` and `v1.0.5-beta.1`. - -## Non-Goals - -- No product behavior changes are introduced as part of the release metadata update. -- No historical changelog sections are rewritten. - -## Constraints - -- Follow the repository release flow with `dev` as the integration branch and `main` updated by fast-forward. -- Keep changelog entries bilingual with English bullets before Chinese bullets. diff --git a/docs/features/v1-0-5-beta-1-release/tasks.md b/docs/features/v1-0-5-beta-1-release/tasks.md deleted file mode 100644 index 15fba38a6..000000000 --- a/docs/features/v1-0-5-beta-1-release/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# v1.0.5 Beta 1 Release Tasks - -- [x] Confirm current branch, working tree, release branch, and tag state. -- [x] Update package version and changelog. -- [x] Run format, i18n, lint, and typecheck checks. -- [ ] Commit and push release metadata on `dev`. -- [ ] Cut and push `release/v1.0.5-beta.1`. -- [ ] Open the release PR to `main`. -- [ ] Fast-forward `main`, tag `v1.0.5-beta.1`, and clean up the release branch after approval. diff --git a/docs/features/voice-input-transcription/plan.md b/docs/features/voice-input-transcription/plan.md deleted file mode 100644 index e9b95b6d4..000000000 --- a/docs/features/voice-input-transcription/plan.md +++ /dev/null @@ -1,44 +0,0 @@ -# Voice Input Transcription Plan - -## Implementation Approach - -1. Keep the existing model-level `speechRecognition` gate and page-level availability checks unchanged. -2. Replace the renderer speech-recognition implementation with local microphone capture that records audio and normalizes it to `wav` before upload. -3. Add a typed `models.transcribeAudio` route plus renderer `ModelClient` wrapper that accepts `providerId`, `modelId`, `audioBase64`, `mimeType`, and optional `filename`, then returns plain text. -4. Add `LLMProviderPresenter.transcribeAudioStandalone` as a provider-agnostic main-process entry point that prefers provider-native OpenAI-style `/audio/transcriptions` uploads and falls back to `ChatMessage` `input_audio` plus `generateCompletionStandalone` when no direct endpoint is available or the third-party endpoint is unsupported. -5. Keep button/shortcut wiring in `ChatInputBox`, `ChatPage`, and `NewThreadPage`, but only inject text after the transcription route resolves. -6. Keep transcription failures observable by letting the standalone transcription path rethrow provider errors instead of silently returning an empty string. -7. Race the renderer transcription await against local abort/timeout handling so loading clears on cancellation or stalled provider calls. -8. Render recording mode with a waveform-style SVG animation so active capture is obvious. -9. Update existing i18n/button copy from generic voice input wording to local recording wording where needed. -10. Apply review hardening by aligning OpenAI-compatible audio fallback MIME data URLs, bounding typed route payload fields, guarding disposed recorder callbacks, and using ACP draft session model state for new-thread audio attachment filtering. - -## Affected Areas - -- `src/shared/contracts/routes/models.routes.ts` -- `src/shared/contracts/routes.ts` -- `src/shared/types/presenters/llmprovider.presenter.d.ts` -- `src/shared/types/presenters/legacy.presenters.d.ts` -- `src/main/routes/models/modelRouteHandler.ts` -- `src/main/presenter/llmProviderPresenter/index.ts` -- `src/main/presenter/llmProviderPresenter/baseProvider.ts` -- `src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts` -- `src/renderer/src/components/chat/ChatInputToolbar.vue` -- `src/renderer/api/ModelClient.ts` -- `src/renderer/src/components/chat/composables/useAudioRecorder.ts` -- `src/renderer/src/components/chat/composables/useSpeechRecognition.ts` -- `src/renderer/src/pages/ChatPage.vue` -- `src/renderer/src/pages/NewThreadPage.vue` -- `src/renderer/src/i18n/**/chat.json` -- `test/renderer/composables/useSpeechRecognition.test.ts` -- `test/main/presenter/llmProviderPresenter/openAICompatibleProvider.test.ts` -- `test/main/presenter/llmProviderPresenter/openAIResponsesProvider.test.ts` -- `test/renderer/components/ChatInputToolbar.test.ts` - -## Test Strategy - -- Update the focused speech-recognition composable test to cover local recording, wav conversion, transcription success, failure handling, and stalled-request timeout cleanup. -- Add a focused main-presenter test ensuring standalone audio transcription propagates provider errors. -- Run a focused renderer test for the composable first. -- Add focused regression coverage for timeout classification, recorder cleanup suppression, audio attachment negative detection, and ACP draft-target filtering. -- Run a narrow type-aware validation for the touched route/presenter/renderer files, then finish with repo-required format/i18n/lint. diff --git a/docs/features/voice-input-transcription/spec.md b/docs/features/voice-input-transcription/spec.md deleted file mode 100644 index d855b3c6a..000000000 --- a/docs/features/voice-input-transcription/spec.md +++ /dev/null @@ -1,45 +0,0 @@ -# Voice Input Transcription - -## User Need - -Users can click the chat mic button, speak directly, and have recognized text inserted into the input box without manually attaching audio files. - -## Goal - -- Add local audio capture plus model-based transcription support in renderer chat inputs. -- Let each model opt into the mic affordance through a model-level `语音识别` setting. -- Insert recognized speech into the existing text input flow only after transcription succeeds. -- Keep the transcription entry point provider-agnostic so providers beyond Xiaomi Mimo can reuse it. - -## Acceptance Criteria - -- Chat and new-thread input areas can start/stop local recording from the mic button or shortcut. -- The mic button and shortcut only work when the effective current model config has `speechRecognition === true`. -- Local recording is converted to `wav`, sent through a typed route with `{ providerId, modelId, audioBase64, mimeType, filename? }`, and returns `{ text }`. -- The main-process transcription flow prefers an OpenAI-compatible `multipart/form-data` `/audio/transcriptions` request for providers that support it, while automatically falling back to the existing `ChatMessage` `input_audio` + standalone completion path when that endpoint is unavailable on third-party OpenAI-compatible services. -- Recognized speech is inserted into the current input box text only after the transcription response returns successfully. -- A model-level `语音识别` setting controls whether the mic entry point is available for that model. -- The mic button shows a clearly active recording state with a waveform-style SVG animation while the microphone is capturing. -- Unsupported browsers, denied microphone permissions, or transcription failures show an i18n user-facing explanation instead of failing silently. -- Provider/runtime transcription errors surface a user-facing failure state instead of being collapsed into an empty transcript. -- If transcription stalls, the UI must leave the loading state after a bounded timeout or user cancellation instead of spinning indefinitely. -- Transcription payload validation must reject oversized audio/base64 metadata before crossing the main-process route boundary. -- Cleanup/disposal must not emit stale recording callbacks after the voice-input lifecycle has been torn down. -- Audio attachment filtering in new-thread ACP drafts must use the draft submission target, not an unrelated fallback chat model. - -## Constraints - -- Keep capture local in the renderer, but allow a transient audio upload to the selected model runtime for transcription. -- Use the typed route system rather than Mimo-only branches or ad hoc IPC. -- Preserve the existing mic gate logic in page components. -- Reuse the existing chat input/editor flow rather than introducing a parallel composer. -- Prefer OpenAI-style audio transcription compatibility when the selected provider/runtime exposes that endpoint. -- Preserve current send, queue, and command-submission behavior. -- Do not persist raw microphone audio after transcription completes. -- Keep review-fix changes narrow and compatible with existing typed routes, provider message mapping, and page submission flow. - -## Non-Goals - -- Persisting raw microphone audio. -- Wake-word detection or always-on listening. -- Building a Xiaomi-only transcription code path. diff --git a/docs/features/voice-input-transcription/tasks.md b/docs/features/voice-input-transcription/tasks.md deleted file mode 100644 index 235a2a5f1..000000000 --- a/docs/features/voice-input-transcription/tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# Voice Input Transcription Tasks - -- [x] Update the existing voice-input SDD docs to reflect local recording plus model transcription. -- [x] Add a provider-agnostic `models.transcribeAudio` typed route and renderer client method. -- [x] Add `LLMProviderPresenter.transcribeAudioStandalone` with OpenAI-style multipart transcription as the primary path and `input_audio` + standalone completion fallback. -- [x] Add automatic fallback when third-party OpenAI-compatible `/audio/transcriptions` endpoints are unsupported. -- [x] Convert local recordings to wav before transcription and update the speech-recognition composable. -- [x] Keep existing mic gate logic, but only insert text after transcription succeeds in chat/new-thread pages. -- [x] Update button/error copy for local recording where needed. -- [x] Make the recording state visually obvious on the mic button with an active background/motion cue. -- [x] Replace the recording-state icon with a waveform-style SVG animation. -- [x] Make standalone transcription surface provider/runtime errors instead of silently returning an empty transcript. -- [x] Add renderer-side abort/timeout cleanup so stalled transcription requests do not leave the mic button loading forever. -- [x] Apply PR review hardening for MIME fallback consistency, recorder cleanup, timeout classification, payload limits, ACP draft attachment filtering, and locale copy. -- [x] Add focused regression tests for the review hardening items. -- [x] Run focused tests, then format/i18n/lint. diff --git a/docs/features/workspace-lifecycle/plan.md b/docs/features/workspace-lifecycle/plan.md deleted file mode 100644 index 69e62e124..000000000 --- a/docs/features/workspace-lifecycle/plan.md +++ /dev/null @@ -1,45 +0,0 @@ -# Workspace Lifecycle Plan - -## Architecture - -- `WorkspacePresenter` owns watcher runtimes keyed by `workspacePath`. -- Each runtime contains: - - a recursive filesystem watcher for workspace content - - a targeted Git metadata watcher for `HEAD`, `index`, `packed-refs`, and `refs` - - a debounce buffer that emits one invalidation payload -- `registerWorkspace` remains the read-security boundary. -- `watchWorkspace` / `unwatchWorkspace` control watcher lifecycle separately from path registration. - -## Event Contract - -- Channel: `workspace:files-changed` -- Canonical constant: `WORKSPACE_EVENTS.INVALIDATED` -- Legacy alias: `WORKSPACE_EVENTS.FILES_CHANGED` -- Payload: - -```ts -type WorkspaceInvalidationEvent = { - workspacePath: string - kind: 'fs' | 'git' | 'full' - source: 'watcher' | 'fallback' | 'lifecycle' -} -``` - -## Renderer Refresh Flow - -- Initial load and all invalidation refreshes use the same sync helper. -- `fs/full` refresh: - - reload root tree - - restore expanded directories - - reload Git state - - reload current preview and diff - - clear stale selected file/diff state -- `git` refresh: - - reload Git state - - reload current diff - - leave file tree and plain file preview untouched - -## Test Strategy - -- Main-process tests cover watcher ref counting, debounce behavior, Git invalidation emission, and destroy cleanup. -- Renderer tests cover watcher lifecycle, full-vs-git refresh routing, expanded directory restoration, and stale selection cleanup. diff --git a/docs/features/workspace-lifecycle/spec.md b/docs/features/workspace-lifecycle/spec.md deleted file mode 100644 index d8561636b..000000000 --- a/docs/features/workspace-lifecycle/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -# Workspace Lifecycle - -## Summary - -Workspace sidepanel state should refresh through a single invalidation pipeline instead of ad-hoc reloads. The main process owns invalidation production, and the renderer owns refresh execution. - -## User Stories - -- As a user, when files are created, edited, moved, or removed in the current workspace, the file tree should refresh without manual action. -- As a user, when Git metadata changes without a file content change, the Git section should still refresh. -- As a maintainer, I need one clear contract for workspace invalidation so future features do not add more bespoke refresh paths. - -## Acceptance Criteria - -- Workspace content changes emit a typed invalidation event with `kind: 'fs'`. -- Git metadata changes emit a typed invalidation event with `kind: 'git'`. -- Renderer refreshes file tree, Git state, preview, and diff through one shared sync path. -- Expanded directories stay expanded after a full refresh when the directory still exists. -- Stale selected file and diff state are cleared when the backing file or Git change disappears. -- Read-only filesystem operations do not emit workspace invalidation events. - -## Non-Goals - -- Artifact refresh is not part of workspace invalidation. -- Hidden file visibility rules are unchanged. -- Watchers are not promoted to a global always-on workspace service. diff --git a/docs/features/workspace-lifecycle/tasks.md b/docs/features/workspace-lifecycle/tasks.md deleted file mode 100644 index 512e000b0..000000000 --- a/docs/features/workspace-lifecycle/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Workspace Lifecycle Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/features/yobrowser-activity-visualization/plan.md b/docs/features/yobrowser-activity-visualization/plan.md deleted file mode 100644 index 2766a9335..000000000 --- a/docs/features/yobrowser-activity-visualization/plan.md +++ /dev/null @@ -1,28 +0,0 @@ -# YoBrowser Activity Visualization Plan - -## Architecture - -- Add `browser.activity.changed` as a typed shared event carrying `sessionId`, optional `windowId`, action `id`, `kind`, `action`, `phase`, optional point/rect/direction metadata, and timestamp. -- Add a per-session transparent child `BrowserWindow` owned by `YoBrowserPresenter`; it follows the active YoBrowser bounds and receives activity payloads directly. -- Keep the overlay window pointer-transparent with `setIgnoreMouseEvents(true, { forward: true })`. -- Load a dedicated `browser-overlay` renderer entry that renders only the active border halo. - -## Event Flow - -- `YoBrowserToolHandler` marks only agent tool calls as activity sources. -- `YoBrowserPresenter.loadUrl(..., 'agent')` emits navigation activity. -- `YoBrowserPresenter.sendCdpCommand(..., 'agent')` maps CDP commands to pointer, scroll, vision, keyboard, or navigation activity. -- Presenter publishes the typed shared event and also forwards the payload to the active overlay window for that session. -- Overlay renderer maintains pending activity IDs, keeps the halo on while pending, and applies a short safety TTL for missing completion events. -- Overlay ignores action-specific metadata in V1; the metadata remains in the event for observability and future use. - -## Compatibility - -- Existing tool names and route contracts stay unchanged. -- BrowserPanel bounds sync remains the source of truth for YoBrowser and overlay placement. -- User-triggered BrowserPanel navigation remains visually unchanged in V1. - -## Risks - -- Native `WebContentsView` stacking can cover Vue DOM overlays, so the visual layer uses a transparent child `BrowserWindow`. -- Fast actions can complete before the overlay renderer is ready, so the overlay queues payloads until its web contents finishes loading. diff --git a/docs/features/yobrowser-activity-visualization/spec.md b/docs/features/yobrowser-activity-visualization/spec.md deleted file mode 100644 index b0e49c65e..000000000 --- a/docs/features/yobrowser-activity-visualization/spec.md +++ /dev/null @@ -1,29 +0,0 @@ -# YoBrowser Activity Visualization Spec - -## User Story - -When an agent operates YoBrowser, users need a clear visual signal that the browser is under agent control, without noisy or fragile action-specific animations. - -## Acceptance Criteria - -- Agent-triggered YoBrowser tool actions show a transparent overlay above the embedded browser view. -- The overlay shows an active border halo while agent actions are pending and fades out after activity settles. -- The overlay uses only the border halo in V1; pointer, scroll, vision, keyboard, and navigation-specific animations are intentionally omitted. -- The overlay does not intercept mouse or keyboard input and does not modify the target page DOM. -- The overlay does not expose page text, Runtime expressions, screenshots, or user-entered text through activity events. -- Manual BrowserPanel toolbar actions do not trigger the agent halo. -- Motion is reduced when the renderer reports `prefers-reduced-motion`. - -## Non-goals - -- Adding new YoBrowser tool names or changing the existing tool contract. -- Mirroring third-party computer-use implementation details. -- Recording or replaying YoBrowser sessions. -- Adding a user-facing setting in V1. -- Rendering mouse cursor trails, scroll wheels, keyboard icons, or element boxes. - -## Constraints - -- Use DeepChat typed event contracts for app-wide activity notifications. -- Keep overlay rendering separate from the page DOM and the BrowserPanel DOM because YoBrowser is an Electron `WebContentsView`. -- Keep the implementation scoped to YoBrowser. diff --git a/docs/features/yobrowser-activity-visualization/tasks.md b/docs/features/yobrowser-activity-visualization/tasks.md deleted file mode 100644 index afa999cc1..000000000 --- a/docs/features/yobrowser-activity-visualization/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# YoBrowser Activity Visualization Tasks - -- [x] Define SDD documents. -- [x] Add browser activity shared types and event contract. -- [x] Add overlay preload and renderer entry. -- [x] Add main-process overlay window lifecycle. -- [x] Emit agent-only activity from YoBrowser tool calls. -- [x] Add main, renderer, and contract tests. -- [x] Run format, i18n, lint, and targeted tests. diff --git a/docs/features/yobrowser-optimization/plan.md b/docs/features/yobrowser-optimization/plan.md deleted file mode 100644 index 03432b02d..000000000 --- a/docs/features/yobrowser-optimization/plan.md +++ /dev/null @@ -1,42 +0,0 @@ -# YoBrowser Session 单实例实施计划 - -## 1. 主进程模型 - -1. `YoBrowserPresenter` 用 `Map` 替代全局单状态。 -2. 每个 `SessionBrowserState` 仅包含一个 `WebContentsView`、一个 `BrowserTab`、attach 信息、可见性和最后一次 bounds。 -3. `load_url` 负责首次懒加载:创建 browser、发起 sidepanel open、等待 host ready、再导航。 - -## 2. 工具路由 - -1. `YoBrowserToolDefinitions` 仅注册 `load_url`、`get_browser_status`、`cdp_send`。 -2. `YoBrowserToolHandler.callTool` 必须接收 `conversationId` 并据此路由 session。 -3. `AgentToolManager` 和 `ToolPresenter` 把这 3 个名字视为内建 YoBrowser 工具。 -4. MCP 同名工具在定义收集阶段直接过滤。 - -## 3. Renderer 行为 - -1. `BrowserPanel` 接收 `sessionId`,所有 presenter 调用都显式带 sessionId。 -2. 切换 session 时先 detach 旧 session browser。 -3. 若旧 session 状态不是 `working`,立即 destroy。 -4. 若旧 session 状态是 `working`,加入 `pendingBrowserDestroySessionIds`,等状态变更后再 destroy。 -5. YoBrowser 事件 payload 带 `sessionId`,只更新当前 panel 对应的会话。 - -## 4. 独立 browser 下线 - -1. 删除 `src/renderer/browser` 旧壳入口。 -2. `windowPresenter` 中旧 `browser` window 类型不再创建独立窗口,统一回退到 chat window。 -3. 清理 `tabPresenter` 中依赖 `browserTabId` 的 YoBrowser 分支。 - -## 5. 测试策略 - -1. main: - - tool definitions 只剩 3 个新工具。 - - 旧工具名报 unknown tool。 - - `load_url` 懒加载与 host-ready 流程成立。 - - session 间 browser state 隔离。 -2. renderer: - - `BrowserPanel` 仅响应当前 session 事件。 - - session 切换时的 detach / destroy / pending destroy 成立。 -3. 回归: - - `cdp_send` 仍走 offload。 - - disabled tools 存储和展示使用新工具名。 diff --git a/docs/features/yobrowser-optimization/spec.md b/docs/features/yobrowser-optimization/spec.md deleted file mode 100644 index 958b66848..000000000 --- a/docs/features/yobrowser-optimization/spec.md +++ /dev/null @@ -1,67 +0,0 @@ -# YoBrowser Session 单实例收敛 - -## 背景 - -当前 YoBrowser 还保留了多 window / 多 tab 的旧抽象,但实际运行时已经收敛为单个 sidepanel browser host。继续暴露 `open / close / focus / list`、windowId、tabId 等接口,只会增加状态分叉和错误恢复成本。 - -同时,session 切换后 browser 的回收策略需要和会话状态对齐:如果旧 session 仍在 `working`,切走时只能先 detach,不能立刻销毁,否则会打断本轮工具调用。 - -## 目标 - -1. 每个 session 最多持有一个 YoBrowser `webContents`。 -2. agent 仅暴露 3 个裸工具名:`load_url`、`get_browser_status`、`cdp_send`。 -3. `load_url` 首次调用时懒创建 browser,并自动完成 sidepanel attach 流程。 -4. session 切换时按会话状态销毁: - - 非 `working`:立即 detach 并销毁。 - - `working`:先 detach,待状态结束后再销毁。 -5. 下线旧独立 browser shell,只保留聊天右侧 sidepanel 的 YoBrowser。 - -## 非目标 - -- 不扩成通用多窗口 browser 系统。 -- 不保留旧 `yo_browser_*` 别名兼容。 -- 不让 `cdp_send` 自动创建 browser;必须先 `load_url`。 -- 不额外重构通用 window presenter 架构。 - -## 用户故事 - -- 作为 agent 用户,我希望 browser 工具是直接、稳定、少状态的,不需要理解 window/tab 多实体模型。 -- 作为使用多会话的用户,我希望切换 session 时前一个 session 的 browser 不串到当前会话。 -- 作为正在执行 browser 工具的用户,我希望切走会话不会打断仍在运行中的 browser 操作。 - -## 约束与假设 - -- “正在 loading” 统一按当前 session 状态 `working` 处理。 -- 一个 session 同时只允许一个 sidepanel browser 实例。 -- `cdp_send` 永远绑定当前 tool call 的 `conversationId`。 -- `load_url`、`get_browser_status`、`cdp_send` 视为内建保留工具名,MCP 不得覆盖。 - -## 验收标准 - -### A. 工具面 - -- [ ] agent tool definitions 仅包含 `load_url`、`get_browser_status`、`cdp_send`。 -- [ ] 旧 `yo_browser_*` 名称调用时返回 unknown tool。 -- [ ] `cdp_send` 若 session browser 尚未初始化,返回明确错误,要求先 `load_url`。 - -### B. Session 生命周期 - -- [ ] `load_url` 首次调用时才创建对应 session 的 browser。 -- [ ] 不同 session 持有各自独立 browser state,不共享 page / visibility / attach 状态。 -- [ ] session 切换时,旧 session 若非 `working`,立即 destroy。 -- [ ] session 切换时,旧 session 若为 `working`,仅 detach;该 session 结束后再 destroy。 - -### C. UI 与事件 - -- [ ] Renderer 仅响应当前 `sessionId` 的 YoBrowser 事件。 -- [ ] 切换 session 后,browser panel 不显示前一个 session 的状态。 -- [ ] 旧独立 browser shell 入口不再可用。 - -### D. 文档与接口 - -- [ ] `IYoBrowserPresenter` 与共享类型收敛到 session-aware 单实例接口。 -- [ ] 架构文档与本 spec 使用新工具名与新生命周期语义。 - -## Open Questions - -无。 diff --git a/docs/features/yobrowser-optimization/tasks.md b/docs/features/yobrowser-optimization/tasks.md deleted file mode 100644 index e0fa7202f..000000000 --- a/docs/features/yobrowser-optimization/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# YoBrowser Session 单实例任务拆分 - -1. 收敛共享类型与 presenter 接口到 session-aware 单实例模型。 -2. 重写 `YoBrowserPresenter` 的 session 状态管理、attach、detach、destroy 流程。 -3. 将 tool definitions / handler / agent routing 切到 `load_url`、`get_browser_status`、`cdp_send`。 -4. 在 renderer sidepanel 中按 `sessionId` 驱动 browser panel,并实现 `working` 态延迟销毁。 -5. 删除旧独立 browser shell 与 `browserTabId` 相关残留。 -6. 更新 main / renderer / agent presenter 测试到新工具名和新生命周期。 -7. 更新规格与架构文档,并跑格式化、i18n、lint、关键测试。 diff --git a/docs/guides/code-navigation.md b/docs/guides/code-navigation.md index 01f59343f..bf07cc485 100644 --- a/docs/guides/code-navigation.md +++ b/docs/guides/code-navigation.md @@ -116,7 +116,7 @@ rg "settingsChangedEvent|sessionsUpdatedEvent|chatStream" src/shared src/main sr | `agentSessionPresenter` | presenter-backed runtime collaborator,不是 migrated renderer 的直连入口 | | `agentRuntimePresenter` | 当前聊天 runtime 与持久化 owner | | `SessionPresenter` | legacy conversation 兼容层,不是 migrated chat 主链路 | -| `agentPresenter` | 已退休;只会出现在 archive 或历史 spec 里 | +| `agentPresenter` | 已退休;只应出现在旧提交或已删除的历史 spec 里 | ## 不要再从这里找主链路 @@ -127,8 +127,4 @@ rg "settingsChangedEvent|sessionsUpdatedEvent|chatStream" src/shared src/main sr - `agentLoopHandler` - `streamGenerationHandler` -如果确实需要历史对照,请去: - -- `docs/archives/legacy-agentpresenter-architecture.md` -- `docs/archives/legacy-agentpresenter-flows.md` -- `docs/archives/thread-presenter-migration-plan.md` +如果确实需要历史对照,请用 `git log` / `git show` 查看旧提交中的文档或源码快照。 diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index e190146cc..27c916daa 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -45,13 +45,13 @@ Renderer -> agentSessionPresenter / agentRuntimePresenter / toolPresenter / llmProviderPresenter ``` -如果你在历史文档或旧提交里看到 `AgentPresenter`、`startStreamCompletion`、`agentLoopHandler`, -那已经是 archive 内容。 +如果你在旧提交里看到 `AgentPresenter`、`startStreamCompletion`、`agentLoopHandler`, +那已经是退休实现。 如果你在现有代码里看到 `useLegacyPresenter()`、`window.electron`、`window.api`,请先把它理解为兼容层, -而不是新功能默认入口。`phase5` 之后的默认规则见 -`docs/architecture/renderer-main-single-track/`。如果迁移期间必须临时保留 legacy transport,唯一允许的 -quarantine 路径是 `src/renderer/api/legacy/**`。 +而不是新功能默认入口。当前默认规则写在 `docs/ARCHITECTURE.md`:新 renderer-main 能力走 +`renderer/api/*Client` + `window.deepchat` + shared contracts;临时 legacy transport 只允许放在 +`src/renderer/api/legacy/**`。 ## 项目目录速览 @@ -152,8 +152,5 @@ pnpm run lint:architecture ## 历史资料 -要对照旧实现时,请看: - -- `docs/archives/legacy-agentpresenter-architecture.md` -- `docs/archives/legacy-agentpresenter-flows.md` -- `docs/archives/thread-presenter-migration-plan.md` +历史 SDD 和旧架构快照不再长期保留在 `docs/`。要对照旧实现时,请用 +`git log -- docs` 或 `git show :` 查看对应提交。 diff --git a/docs/issues/acp-context-budget-bypass/plan.md b/docs/issues/acp-context-budget-bypass/plan.md deleted file mode 100644 index 20f03fbf2..000000000 --- a/docs/issues/acp-context-budget-bypass/plan.md +++ /dev/null @@ -1,26 +0,0 @@ -# ACP Context Budget Bypass Plan - -## Implementation - -- Add an internal `providerId === 'acp'` budget-bypass helper in `AgentRuntimePresenter`. -- For ACP new turns and resume turns, use existing persisted summary state but skip new - compaction-intent preparation caused by DeepChat context pressure. -- Build ACP request contexts with an effectively unbounded context budget so DeepChat does not trim - history based on ACP model metadata. -- In the provider loop, bypass `preflightRequestContext`, context-pressure recovery, overflow error - creation, and effective max-token shrinking for ACP. Keep rate-limit, abort, and steer handling. -- Treat ACP resume tool-budget checks as unbounded so DeepChat does not replace tool results with - context-window errors before the ACP provider sees the continuation. - -## Compatibility - -- The bypass applies to all ACP agents, including registry and custom agents. -- Non-ACP behavior remains unchanged. -- Existing persisted summaries remain available and are appended when present; this change only - prevents ACP turns from creating new budget-driven summaries. - -## Tests - -- Add main-process tests for oversized ACP prompt delivery and lack of budget overflow errors. -- Add coverage proving ACP context pressure does not start DeepChat compaction. -- Keep existing non-ACP overflow tests as regression coverage. diff --git a/docs/issues/acp-context-budget-bypass/spec.md b/docs/issues/acp-context-budget-bypass/spec.md deleted file mode 100644 index 0e554ade1..000000000 --- a/docs/issues/acp-context-budget-bypass/spec.md +++ /dev/null @@ -1,36 +0,0 @@ -# ACP Context Budget Bypass Spec - -> Status: Draft -> Date: 2026-05-12 - -## Background - -ACP agents such as Claude Code maintain their own conversation and model context policy. DeepChat -currently routes ACP sessions through the generic agent runtime context preflight, which can block -ACP prompts before the ACP agent receives them with: - -`Request was not sent because it cannot fit within the model context window after applying the safety margin.` - -That check is appropriate for DeepChat-managed model calls, but ACP-backed requests should be -delegated to the ACP agent. - -## Goals - -- Skip DeepChat model-context preflight and recovery for every `providerId === 'acp'` request. -- Let ACP agents receive the prompt and handle context-window pressure themselves. -- Preserve current behavior for non-ACP providers. - -## Acceptance Criteria - -- ACP requests that exceed DeepChat's estimated context budget still reach the ACP provider. -- ACP requests do not trigger DeepChat context-pressure compaction or request trimming solely due to - DeepChat's context-window estimate. -- ACP request max tokens are not shrunk by DeepChat's safety-margin preflight. -- Non-ACP providers keep existing preflight, compaction recovery, and overflow failure behavior. -- No public API, IPC, schema, or renderer UI changes are introduced. - -## Non-Goals - -- Redesign ACP prompt formatting. -- Reset or migrate existing session summaries. -- Change ACP workdir, permission, rate-limit, abort, or session lifecycle behavior. diff --git a/docs/issues/acp-context-budget-bypass/tasks.md b/docs/issues/acp-context-budget-bypass/tasks.md deleted file mode 100644 index fb7bf8887..000000000 --- a/docs/issues/acp-context-budget-bypass/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# ACP Context Budget Bypass Tasks - -- [x] Add SDD spec and plan. -- [x] Add ACP budget-bypass helper. -- [x] Skip ACP budget compaction and request trimming for new and resume turns. -- [x] Bypass provider-loop preflight and max-token shrinking for ACP. -- [x] Treat ACP resume tool budget as unbounded. -- [x] Add focused main-process regression tests. -- [x] Run targeted tests and required project checks. diff --git a/docs/issues/acp-initialization-logging/plan.md b/docs/issues/acp-initialization-logging/plan.md deleted file mode 100644 index 7402fb157..000000000 --- a/docs/issues/acp-initialization-logging/plan.md +++ /dev/null @@ -1,29 +0,0 @@ -# ACP Initialization Logging Plan - -## Scope - -Implement a focused diagnostics increment in the existing ACP runtime: - -- `AcpProcessManager` owns subprocess startup, JSON-RPC stream creation, initialize, and lifecycle monitoring. -- `AcpSessionManager` owns normal chat session setup through `loadSession` and `newSession`. -- `AcpProvider` owns normal chat prompt dispatch through `session/prompt`. - -## Implementation - -1. Replace the direct SDK `ndJsonStream` use with an equivalent traced NDJSON stream wrapper inside `AcpProcessManager`. - - Log inbound and outbound JSON-RPC summaries as messages pass through the same stream used by the SDK. - - Track request ids to correlate responses with methods. - - Record parse failures from stdout as ACP debug errors. - -2. Attach subprocess stderr/error/exit monitoring before `initialize`. - - Keep the monitor after initialization so process exits still clear handles. - - Race initialization with process exit and connection closure in addition to the existing timeout. - -3. Add session and prompt logs. - - Record summarized `session/load`, `session/new`, and `session/prompt` request/response/error events. - - Avoid storing full prompt text or full MCP server/env payloads. - -## Test Strategy - -- Run focused ACP main-process tests for the touched runtime paths where practical. -- Run repository formatting/i18n/lint gates after implementation as required by project instructions. diff --git a/docs/issues/acp-initialization-logging/spec.md b/docs/issues/acp-initialization-logging/spec.md deleted file mode 100644 index 88e6f0cba..000000000 --- a/docs/issues/acp-initialization-logging/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -# ACP Initialization Logging - -## Problem - -Claude Code over ACP can remain in a loading state with too little diagnostic information from the normal chat path. Existing logs show some high-level initialization results, but they do not consistently expose early subprocess exits, initialization-time stderr, stream closure before an initialize response, or the request/response stages for session setup. - -## Goals - -- Log ACP subprocess startup, initialization request/response timing, initialization failure, early process exit, and stream closure. -- Add protocol-level JSON-RPC frame summaries without consuming stdout separately from the ACP stream. -- Log normal chat-path `newSession`, `loadSession`, and `prompt` request/response/error stages. -- Keep logged protocol payloads summarized so prompts, file contents, and environment details are not dumped by default. - -## Acceptance Criteria - -- When an ACP process starts, logs include agent id, workdir, pid, command summary, and initialization start. -- If the ACP process exits, emits stderr, or the protocol stream closes during initialization, the failure is logged and initialization rejects with a concrete message instead of waiting only for the long timeout. -- ACP debug event history includes lifecycle/request/response/error entries for initialization and session setup. -- Protocol frame logging does not add an extra `stdout` data listener that could steal bytes from the ACP SDK stream. -- No `[NEEDS CLARIFICATION]` markers remain. - -## Non-Goals - -- No ACP protocol redesign. -- No renderer UI redesign for the ACP inspector. -- No full raw prompt/content logging by default. diff --git a/docs/issues/acp-initialization-logging/tasks.md b/docs/issues/acp-initialization-logging/tasks.md deleted file mode 100644 index d6978a4a7..000000000 --- a/docs/issues/acp-initialization-logging/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# ACP Initialization Logging Tasks - -- [x] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [x] Add traced ACP protocol stream summaries. -- [x] Attach initialization-time process stderr/error/exit monitoring. -- [x] Race initialize against process exit and connection closure. -- [x] Add session setup and prompt request/response debug events. -- [x] Run format, i18n, lint, and focused ACP tests. diff --git a/docs/issues/acp-pr-1614-review-fixes/plan.md b/docs/issues/acp-pr-1614-review-fixes/plan.md deleted file mode 100644 index 7f02bbcb4..000000000 --- a/docs/issues/acp-pr-1614-review-fixes/plan.md +++ /dev/null @@ -1,16 +0,0 @@ -# Implementation Plan - -## Change - -- Update ACP process cwd resolution and initialization timeout cleanup in place. -- Harden ACP session cleanup paths with guarded cleanup and original-error rethrowing. -- Await ACP turn persistence in `AcpProvider.runPrompt`, using small helper methods to keep error handling explicit. -- Replace the duplicated ACP event union with the shared contract plus provider-only tool event variants. -- Update the reviewed locale entries for the lifecycle event kind. - -## Validation - -- Add focused test coverage for relative terminal cwd resolution. -- Run focused ACP tests where changed. -- Run repository-required `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`, plus - `pnpm run typecheck`. diff --git a/docs/issues/acp-pr-1614-review-fixes/spec.md b/docs/issues/acp-pr-1614-review-fixes/spec.md deleted file mode 100644 index 420aee9c0..000000000 --- a/docs/issues/acp-pr-1614-review-fixes/spec.md +++ /dev/null @@ -1,21 +0,0 @@ -# ACP PR 1614 Review Fixes - -## Problem - -PR review feedback on the ACP client runtime changes found several small reliability and consistency issues that should be fixed before merge. - -## Acceptance Criteria - -- Terminal cwd values supplied by ACP agents are resolved relative to the active session workdir when they are not absolute, and cwd escapes still fall back safely. -- ACP session initialization cleanup preserves the original initialization error even if unbinding fails. -- Persisted ACP session hook disposal cannot prevent fallback to `newSession`. -- ACP connection initialization clears its timeout handle after `Promise.race` settles. -- ACP turn persistence awaits start and finish writes and reports persistence failures instead of silently dropping them. -- ACP event types use the shared contract as the source of truth. -- Newly added lifecycle event labels are localized for reviewed locales. - -## Non-goals - -- No broad ACP behavior redesign. -- No full localization rewrite outside the reviewed lifecycle labels. -- No database schema changes beyond the existing ACP turn persistence shape. diff --git a/docs/issues/acp-pr-1614-review-fixes/tasks.md b/docs/issues/acp-pr-1614-review-fixes/tasks.md deleted file mode 100644 index b1357f43f..000000000 --- a/docs/issues/acp-pr-1614-review-fixes/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Fix validated PR review findings. -- [x] Add or update focused tests. -- [x] Run validation commands. -- [x] Push the synchronized PR branch. diff --git a/docs/issues/acp-workdir-npx-recovery/plan.md b/docs/issues/acp-workdir-npx-recovery/plan.md deleted file mode 100644 index 45ae38a8d..000000000 --- a/docs/issues/acp-workdir-npx-recovery/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# Implementation Plan - -## Renderer - -- Add a `FileClient.isDirectory` check in `NewThreadPage`. -- Track directory status as `none`, `checking`, `valid`, or `invalid`. -- Use a monotonically increasing request sequence so late async results are ignored. -- Render a `lucide:circle-alert` icon with a localized title when the selected project path is invalid. -- Include ACP workdir unavailability in `submit-disabled`, toolbar `send-disabled`, submit handlers, and the ACP draft watcher. - -## Main - -- Replace ACP spawn cwd HOME fallback with explicit directory validation. -- Keep fallback workdir behavior only for empty workdir input. -- Wrap ACP process spawn in one npx repair retry. -- Parse `_npx//package.json` ENOENT from stderr/error text, rename only that hash directory, and retry once. - -## Tests - -- Extend NewThreadPage tests for universal warning, DeepChat non-blocking behavior, ACP blocking behavior, and draft suppression. -- Add AcpProcessManager tests for missing cwd rejection and npx repair gating/rename/retry behavior. diff --git a/docs/issues/acp-workdir-npx-recovery/spec.md b/docs/issues/acp-workdir-npx-recovery/spec.md deleted file mode 100644 index cecd29b32..000000000 --- a/docs/issues/acp-workdir-npx-recovery/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -# ACP Workdir Validation and npx Recovery - -## User Story - -When a project directory selected for a new thread no longer exists or cannot be read, users should see that state immediately instead of silently running in a different directory. ACP agents must not create draft sessions, warm up, or send messages against an invalid workdir. - -When an npx ACP package launch fails because npm left a broken `_npx/` cache directory, DeepChat should repair only that broken cache directory and retry once. - -## Acceptance Criteria - -- The new thread project selector validates the selected project path with `FileClient.isDirectory`. -- An empty project selection is not treated as an invalid directory. -- Stale directory checks cannot overwrite newer project selections. -- Any selected agent shows a warning icon next to the project selector when the selected directory is missing or inaccessible. -- ACP agents disable sending and skip draft session creation while the selected workdir is missing, invalid, or still being checked. -- DeepChat agents show the same warning but keep sending enabled. -- Main process ACP launch throws when an explicit workdir/cwd does not exist or is not a directory. -- Empty ACP workdir continues using the existing fallback directory. -- npx repair only triggers for `distributionType === 'npx'` and `_npx//package.json` ENOENT failures. -- npx repair moves only the named bad hash directory and retries once. - -## Non-Goals - -- Do not change DeepChat Agent send behavior for invalid project directories. -- Do not introduce a new IPC surface for directory checks. -- Do not clear the whole npm cache. diff --git a/docs/issues/acp-workdir-npx-recovery/tasks.md b/docs/issues/acp-workdir-npx-recovery/tasks.md deleted file mode 100644 index a90bc63dd..000000000 --- a/docs/issues/acp-workdir-npx-recovery/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -- [x] Add renderer directory validation state and warning icon. -- [x] Block ACP draft/send paths when workdir is missing or invalid. -- [x] Add main process explicit cwd validation. -- [x] Add npx `_npx//package.json` repair and one retry. -- [x] Add focused renderer and main tests. -- [x] Run format, i18n, lint, focused tests, and affected typecheck. diff --git a/docs/issues/agent-tool-context-budget/plan.md b/docs/issues/agent-tool-context-budget/plan.md deleted file mode 100644 index d1e2cb804..000000000 --- a/docs/issues/agent-tool-context-budget/plan.md +++ /dev/null @@ -1,81 +0,0 @@ -# Agent Tool Context Budget Plan - -## Diagnosis - -The core issue is request budgeting, not MiniMax alone. MiniMax exposes the issue quickly because -its model metadata advertises a very large output limit and reasoning-capable tool calls. DeepChat -was using that limit as the session default and reserving it on every turn, while the tool schema -payload was not included in initial history selection. - -Function-call failures have a second path: models that do not use native tools fall back to a text -protocol appended to the latest user message. That protocol is verbose and brittle, so it both -consumes more context and fails on small formatting deviations. - -## First Increment - -- Cap agent-loop default max output tokens to a practical default and never reserve more than half - of the context window for output. -- Reserve tool definition tokens when building the initial/resume context. -- Fit request messages again immediately before each provider call. -- Preserve the active tool continuation tail during trimming. -- Resolve an effective per-request max output value from the fitted request and tool-schema budget. -- Make legacy function-call parsing tolerant of code fences and a missing closing tag at end of - stream. - -## Follow-Up Work - -- Add request trace telemetry for message tokens, tool tokens, system tokens, output reserve, and - final effective output cap. -- Add a reasoning retention budget: keep provider-required continuation metadata, but summarize or - omit old reasoning text once it is no longer needed. -- Add provider capability overrides for services whose model metadata says `tool_call: true` but - whose endpoint rejects native tool payloads. -- Add a compact tool-schema mode for legacy function-call fallback. -- Add UI diagnostics for "context budget pressure" and suggested remediation. - -## Second Increment - -- Add a provider-call preflight helper that estimates messages, tool definitions, the safety margin, - and the temporary effective output cap before every loop request. -- Reserve a 256-token safety margin for normal model context windows so off-by-one provider - validators do not reject otherwise fitted requests. -- When preflight pressure would reduce a normal request below 4000 output tokens, run an internal - recovery pass before the provider call: compact persisted old turns when enabled, then rely on the - request fitter to trim older in-memory messages while preserving the active tail. -- Write recovered messages back into the active request array so later tool-continuation loops use - the same compacted/trimmed history. -- Keep generation settings unchanged; only the provider call's `maxTokens` argument is reduced. -- Use the same safety-adjusted budget in tool-output fitting so continuation turns do not inject - tool results that leave the next request over the provider limit. -- Report zero effective output tokens when a fitted request still cannot fit at all, and fail before - calling the provider. - -## Review Hardening - -- Treat non-positive context windows as unknown/unbounded during request preflight, matching the - existing fitting and max-token helpers. -- Judge tool-output continuation fitting against the next preflight-fitted request shape, so older - history that would be trimmed before the next provider call does not falsely fail the tool result. - -## Retry Overflow Hardening - -- Route unfittable provider-call preflight results through the existing context-pressure recovery - path once before failing. -- Keep the latest user/system/tool payload protected; recovery may compact persisted history, - replace stale summary-bearing system prompt text, trim older in-memory history, and reduce only - the per-call output cap. -- When recovery still cannot fit, fail before rate-limit wait/provider streaming with a budget - diagnostic that includes usable context, estimated input, tool-schema reserve, requested/effective - output, and remaining output room. - -## Manual Validation Notes - -For a MiniMax-M2.7 agent session, inspect trace/log output rather than running automated test -suites: - -- New session default `maxTokens` should be capped. -- Requests with tools should reserve tool schema tokens before selecting history. -- After a tool result, the next request should retain the assistant tool call and corresponding tool - result while dropping older history first. -- A legacy response like `{"function_call":...}` at end of stream should still parse - if JSON is repairable. diff --git a/docs/issues/agent-tool-context-budget/spec.md b/docs/issues/agent-tool-context-budget/spec.md deleted file mode 100644 index 7aa224df4..000000000 --- a/docs/issues/agent-tool-context-budget/spec.md +++ /dev/null @@ -1,87 +0,0 @@ -# Agent Tool Context Budget And Function Call Reliability - -> Status: Draft -> Date: 2026-04-27 - -## Background - -DeepChat agent sessions can fail after only a few tool calls on MiniMax and other providers. The -same class of issue appears outside ACP, so the failure is not isolated to ACP transport. - -Observed risk points: - -- Agent default `maxTokens` can inherit very large provider output limits, for example 131072. -- Context selection reserves output tokens but did not reserve request-level tool schemas. -- Provider loop iterations after tool execution relied on tool-output fitting, but did not preflight - the whole request before sending it. -- Legacy text-based function calls depend on exact `...` output. - Small provider formatting deviations can turn a valid intent into plain assistant text. -- Interleaved reasoning models may preserve reasoning content across tool calls, increasing history - size much faster than normal chat. - -## Goals - -- Make the agent loop treat tools as first-class context budget consumers. -- Avoid pathological default output reservations for agent sessions. -- Preflight every provider request, not just the initial user turn. -- Improve legacy function-call parsing tolerance without changing the public tool protocol. -- Keep the first increment provider-agnostic and applicable beyond MiniMax. - -## Non-Goals - -- Do not replace the AI SDK provider runtime. -- Do not redesign ACP. -- Do not remove reasoning continuation support. -- Do not change the renderer UI in this increment. - -## User Stories - -1. As a user, I want tool-heavy sessions to continue beyond a few tool calls without context window - failures caused by avoidable budgeting errors. -2. As a provider adapter, I want each request to account for messages, tool schemas, and output - reserve before it is sent. -3. As a legacy function-call model, I want minor formatting differences to still be parsed when the - intended tool call is recoverable. - -## Acceptance Criteria - -- New agent sessions do not default to provider-scale output limits above the agent loop cap. -- Initial user turn and resume turn reserve tokens for tool definitions. -- Every provider-loop iteration fits messages to the effective request budget before sending. -- Tool-continuation turns preserve the assistant tool-call message and matching tool results when - trimming older context. -- Legacy parser accepts complete function-call tags, a trailing unclosed function-call tag, and JSON - wrapped in Markdown fences. -- No ACP-specific behavior is required for the first increment. - -## Second Increment: Context Pressure Backoff - -Additional acceptance criteria: - -- Every agent-loop provider request must satisfy - `estimated messages + tool schemas + effective maxTokens <= contextLength - 256` for normal model - windows. -- If the estimated input and configured request output would exceed that budget, DeepChat must - temporarily reduce only the per-call `maxTokens` value passed to the provider. -- If context pressure would reduce a normally sized request below 4000 output tokens, DeepChat must - internally recover context first, using auto-compaction when enabled and transient request-window - trimming otherwise. -- User-configured `maxTokens` values below 4000 are respected and do not force recovery by - themselves. -- If no output token can fit after request fitting, preflight reports `effectiveMaxTokens = 0` - instead of a positive budget. -- Context-pressure recovery updates the in-memory request history used by later tool-continuation - loops. - -## Retry Overflow Hardening - -Additional acceptance criteria: - -- Retry and resume provider calls that still cannot fit after request fitting attempt the same - internal recovery pass before failing, even when the user configured fewer than 4000 output - tokens. -- If recovery cannot make the latest user/system/tool payload fit, DeepChat fails before sending a - provider request and stores a clear budget error on the assistant message. -- The error explains that no provider request was sent and suggests shortening current input or - attachments, reducing active tools/skills/system prompt content, lowering max output tokens, or - increasing context length. diff --git a/docs/issues/agent-tool-context-budget/tasks.md b/docs/issues/agent-tool-context-budget/tasks.md deleted file mode 100644 index e816ba110..000000000 --- a/docs/issues/agent-tool-context-budget/tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# Agent Tool Context Budget Tasks - -- [x] Document diagnosis and first increment. -- [x] Add tool-definition token estimation to context budgeting. -- [x] Cap agent-loop default output budget. -- [x] Preflight-fit provider-loop requests. -- [x] Add effective per-request output cap and shared budget module. -- [x] Harden legacy function-call parsing. -- [x] Add 256-token provider-call safety margin and preflight backoff. -- [x] Trigger internal compaction/trim recovery before pressure-shrunk calls below 4000 output. -- [x] Keep recovered request messages in sync for later provider-loop iterations. -- [x] Apply safety-adjusted budget checks to tool-output continuation fitting. -- [x] Drop orphaned tool results and invalid provider options before AI SDK requests. -- [x] Report zero effective output tokens for unfittable preflight results. -- [x] Harden preflight for unknown context windows and refitted tool continuations. -- [x] Recover unfittable retry/resume preflights before provider calls. -- [x] Add actionable budget diagnostics for irreducible retry/resume overflow. -- [ ] Add request budget telemetry. -- [ ] Add reasoning retention budget. -- [ ] Add compact legacy tool schema mode. diff --git a/docs/issues/background-exec-shell-fallback/plan.md b/docs/issues/background-exec-shell-fallback/plan.md deleted file mode 100644 index 2402448b4..000000000 --- a/docs/issues/background-exec-shell-fallback/plan.md +++ /dev/null @@ -1,27 +0,0 @@ -# Background Exec Shell Fallback Plan - -## Approach - -- Centralize fallback behavior in `getUserShell()` so foreground exec, background exec, and shell - environment bootstrap share the same shell resolution. -- Check absolute shell candidates for path and executable availability before returning them. -- Search `PATH` plus DeepChat default paths when `SHELL` is a bare command name. -- Use conservative POSIX fallback chains and keep Windows behavior intact. -- Return only resolved fallback candidates from the platform fallback chain; if none resolve, use - `/bin/sh` as the final default. -- Validate shell process working directories before calling `spawn`, because Node reports missing - `cwd` as `spawn ENOENT`. - -## Affected Paths - -- `src/main/lib/agentRuntime/shellEnvHelper.ts` -- `src/main/lib/agentRuntime/backgroundExecSessionManager.ts` -- Existing shell environment and background exec tests. - -## Compatibility - -- Existing valid user shells are still preferred. -- Missing or non-executable shells now fall back to an available POSIX shell instead of failing - with `ENOENT`/`EACCES`. -- Plain `sh` bootstrap no longer receives login-shell flags it may not support. -- Missing working directories now produce a direct working-directory error. diff --git a/docs/issues/background-exec-shell-fallback/spec.md b/docs/issues/background-exec-shell-fallback/spec.md deleted file mode 100644 index 8cfae9d74..000000000 --- a/docs/issues/background-exec-shell-fallback/spec.md +++ /dev/null @@ -1,25 +0,0 @@ -# Background Exec Shell Fallback - -## User Story - -Users can run foreground and background shell commands even when the configured POSIX login shell -path, such as `/bin/zsh`, is unavailable in the current runtime environment. - -## Acceptance Criteria - -- POSIX shell execution does not blindly spawn a missing `process.env.SHELL` path. -- POSIX shell fallback skips existing but non-executable shell candidates. -- macOS falls back from zsh to bash and then sh; Linux falls back from bash to sh and then zsh. -- If no configured or platform fallback shell resolves, DeepChat uses `/bin/sh` instead of an - unchecked rejected candidate. -- Background exec sessions use the resolved executable shell path. -- Shell environment bootstrap uses plain `sh -c` flags when the fallback is `sh`. -- Missing or inaccessible working directories are reported before spawn instead of surfacing as a - misleading shell `ENOENT`. -- Windows shell selection is unchanged. - -## Non-goals - -- Do not add renderer settings or IPC for shell configuration. -- Do not persist a detected fallback shell. -- Do not change user command permission behavior or output formatting. diff --git a/docs/issues/background-exec-shell-fallback/tasks.md b/docs/issues/background-exec-shell-fallback/tasks.md deleted file mode 100644 index bf7bb360f..000000000 --- a/docs/issues/background-exec-shell-fallback/tasks.md +++ /dev/null @@ -1,10 +0,0 @@ -# Background Exec Shell Fallback Tasks - -- [x] Document the issue and desired behavior. -- [x] Add available shell resolution and POSIX fallback ordering. -- [x] Verify fallback shell candidates are executable before spawn. -- [x] Avoid returning an unchecked platform fallback after candidate validation fails. -- [x] Avoid login bootstrap flags for plain `sh`. -- [x] Validate spawn working directories before launching shell processes. -- [x] Add unit coverage for missing configured shell fallback. -- [x] Run format, i18n, lint, typecheck, and targeted tests. diff --git a/docs/issues/beta-7-release-test-gate/plan.md b/docs/issues/beta-7-release-test-gate/plan.md deleted file mode 100644 index c5490d47f..000000000 --- a/docs/issues/beta-7-release-test-gate/plan.md +++ /dev/null @@ -1,15 +0,0 @@ -# Plan - -## Scope - -- Update release metadata for the next beta version. -- Repair test fixtures that lag behind the structured message/search table additions. -- Keep platform path expectations stable on macOS `/var` and `/private/var` aliases. - -## Validation - -- Run `pnpm run format`. -- Run `pnpm run i18n`. -- Run `pnpm run lint`. -- Run `pnpm run typecheck`. -- Run focused failing test files, then the full test suite if practical. diff --git a/docs/issues/beta-7-release-test-gate/spec.md b/docs/issues/beta-7-release-test-gate/spec.md deleted file mode 100644 index 91d97420b..000000000 --- a/docs/issues/beta-7-release-test-gate/spec.md +++ /dev/null @@ -1,21 +0,0 @@ -# Beta 7 Release Test Gate - -## User Story - -As a maintainer preparing `v1.0.4-beta.7`, I need the local release gate to pass so the beta branch is cut from a verified `dev` commit. - -## Acceptance Criteria - -- Release metadata reflects `v1.0.4-beta.7`. -- Required release checks pass locally. -- Test failures caused by stale mocks or platform-sensitive assertions are fixed before cutting the release branch. - -## Non-goals - -- No unrelated feature work. -- No changelog entries for test-only maintenance. - -## Constraints - -- Preserve the existing `dev` to `release/` flow. -- Keep release branch contents identical to a commit on `dev`. diff --git a/docs/issues/beta-7-release-test-gate/tasks.md b/docs/issues/beta-7-release-test-gate/tasks.md deleted file mode 100644 index 08862f93f..000000000 --- a/docs/issues/beta-7-release-test-gate/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -- [x] Inspect release state and choose `v1.0.4-beta.7`. -- [x] Update `package.json` and `CHANGELOG.md`. -- [x] Fix stale release-gate tests. -- [x] Re-run release checks and focused tests. -- [ ] Commit metadata and test-gate fixes on `dev`. -- [ ] Cut and push `release/v1.0.4-beta.7`. diff --git a/docs/issues/chat-history-pagination-stability/plan.md b/docs/issues/chat-history-pagination-stability/plan.md deleted file mode 100644 index 1b9761cca..000000000 --- a/docs/issues/chat-history-pagination-stability/plan.md +++ /dev/null @@ -1,29 +0,0 @@ -# 会话历史分页稳定性 Plan - -## 实现方向 - -- 以 [spec.md](./spec.md) 为准,优先修复共享 message store 的竞态和刷新语义。 -- 在 renderer store 中区分“首屏恢复/切会话恢复”和“同会话刷新”: - - 首屏恢复仍以最近消息窗口为主。 - - 同会话刷新根据当前已加载条数补齐相同规模的窗口,避免历史被截断。 -- 为顶部翻页增加请求代次保护,确保异步返回只影响当前有效请求。 -- 在主进程消息分页 helper 中允许多取 1 条记录,仅用于 `hasMore` 探测,不扩大公开分页上限。 - -## 兼容性 - -- 不调整 shared route schema,因此 IPC/client 调用方无需改协议。 -- 刷新后的消息顺序仍保持 `orderSeq ASC`。 -- `hasMoreHistory` / `nextCursor` 继续由 store 管理,聊天页滚动逻辑无需改交互。 - -## 测试策略 - -- renderer store: - - 覆盖 `loadOlderMessages()` 的跨会话失效保护。 - - 覆盖同会话刷新时保留已加载历史窗口。 -- main message store: - - 覆盖 `limit=500` 时依然正确返回 `hasMore`。 - -## 验证 - -- 运行聚焦 Vitest 用例验证回归场景。 -- 完成后运行 `pnpm run format`、`pnpm run i18n`、`pnpm run lint`。 diff --git a/docs/issues/chat-history-pagination-stability/spec.md b/docs/issues/chat-history-pagination-stability/spec.md deleted file mode 100644 index 2b3c36eaf..000000000 --- a/docs/issues/chat-history-pagination-stability/spec.md +++ /dev/null @@ -1,35 +0,0 @@ -# 会话历史分页稳定性 - -## 背景 - -当前未提交改动为会话恢复增加了分页能力,但在 renderer store 和主进程分页实现里引入了三个回归: - -1. `loadOlderMessages()` 在异步请求返回后没有校验当前活跃会话,切换线程时可能把旧线程历史写进新线程。 -2. `loadMessages()` 每次刷新都只恢复最近 100 条消息,会把用户已经向上加载出来的历史重新截断。 -3. 主进程分页通过 `limit + 1` 判断 `hasMore`,但底层 SQL helper 把请求再次限制到 500,导致 `limit=500` 时无法正确探测下一页。 - -## 目标 - -- 历史分页请求只能更新发起它的会话,不得污染当前活跃会话。 -- 会话刷新应保留用户已经加载出来的历史窗口,避免流结束、重试、删除、工具交互后出现历史截断。 -- 保持分页接口现有契约不变,同时修复 `limit=500` 时的 `hasMore` 误判。 -- 修复不能让滚动加载、流式渲染或会话切换变卡顿。 - -## 非目标 - -- 不重做聊天页的滚动 UI 或消息渲染结构。 -- 不修改 `sessions.restore` / `sessions.listMessagesPage` 的对外输入输出结构。 -- 不改变消息排序、分页方向或现有默认页大小(100)。 - -## 约束 - -- 遵循 typed route / typed client / store 现有边界。 -- renderer 继续使用 Vue 3 Composition API 和 Pinia store 模式。 -- 保持已有流式事件刷新逻辑可用,避免破坏 `chat.stream.*` 的行为。 - -## 验收标准 - -- 当用户在顶部加载旧历史期间切换会话,旧请求返回后不会改写新会话的 `messages`、`nextCursor`、`hasMoreHistory`、`isLoadingHistory`。 -- 对同一会话执行刷新时,若用户此前已加载超过 100 条历史,刷新后保留相同数量级的已加载窗口,不回退到最近 100 条。 -- `limit=500` 的分页请求在仍有更旧消息时返回 `hasMore=true` 且提供可继续翻页的 `nextCursor`。 -- 为上述场景补齐 renderer/main 单测。 diff --git a/docs/issues/chat-history-pagination-stability/tasks.md b/docs/issues/chat-history-pagination-stability/tasks.md deleted file mode 100644 index 0258bd001..000000000 --- a/docs/issues/chat-history-pagination-stability/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# 会话历史分页稳定性 Tasks - -- [ ] 确认 [spec.md](./spec.md) 没有遗留 `[NEEDS CLARIFICATION]`。 -- [ ] 在 renderer message store 中补齐历史分页请求失效保护。 -- [ ] 调整同会话刷新逻辑,保留已加载历史窗口。 -- [ ] 修复主进程分页的 `limit=500` `hasMore` 探测边界。 -- [ ] 更新 renderer/main 聚焦测试。 -- [ ] 运行 `pnpm run format`、`pnpm run i18n`、`pnpm run lint`。 diff --git a/docs/issues/cua-driver-v0-1-5-sync/plan.md b/docs/issues/cua-driver-v0-1-5-sync/plan.md deleted file mode 100644 index e07b2a00e..000000000 --- a/docs/issues/cua-driver-v0-1-5-sync/plan.md +++ /dev/null @@ -1,30 +0,0 @@ -# Plan - -## Source Review - -- Compare upstream `trycua/cua` tags `cua-driver-v0.1.4` and - `cua-driver-v0.1.5`. -- Confirm whether `libs/cua-driver/Skills/cua-driver` changed. -- Review local fork differences before applying upstream changes. - -## Implementation - -- Cherry-pick the small upstream source changes into - `plugins/cua/vendor/cua-driver/source`. -- Update `plugins/cua/vendor/cua-driver/upstream.json` to record - `cua-driver-v0.1.5`. -- Leave packaged `plugins/cua/skills/cua-driver` MCP-first guidance intact - unless upstream skill files changed. - -## Validation - -- Run `pnpm run format`. -- Run `pnpm run i18n`. -- Run `pnpm run lint`. -- Run `pnpm run plugin:cua:validate`. - -## Risk - -The driver source is a maintained local fork, so direct upstream replacement -would risk losing DeepChat packaging, permissions, and MCP behavior. A focused -manual cherry-pick keeps the change auditable. diff --git a/docs/issues/cua-driver-v0-1-5-sync/spec.md b/docs/issues/cua-driver-v0-1-5-sync/spec.md deleted file mode 100644 index b4cde1a77..000000000 --- a/docs/issues/cua-driver-v0-1-5-sync/spec.md +++ /dev/null @@ -1,36 +0,0 @@ -# CUA Driver v0.1.5 Sync - -## Problem - -The vendored DeepChat CUA driver snapshot is based on upstream `cua-driver-v0.1.4`. -Upstream `cua-driver-v0.1.5` fixes window enumeration for system overlay or -non-layer-0 windows when callers filter by pid. - -## User Story - -As a DeepChat user using the bundled Computer Use plugin, I need `list_windows` -and `get_window_state` to surface relevant overlay windows for a target pid so -agent workflows can inspect and act on the correct UI. - -## Acceptance Criteria - -- The vendored driver records upstream `cua-driver-v0.1.5` metadata. -- The upstream overlay-window fix is applied without replacing DeepChat's local - fork changes. -- Packaged plugin skills remain MCP-first. If upstream skills change in this - sync, start from upstream skill content and preserve DeepChat's MCP-first - guidance in the packaged skill. -- Validation covers formatting, i18n generation, lint, and CUA plugin package - validation where practical. - -## Non-goals - -- No rewrite of the CUA plugin manifest, settings UI, or MCP policy. -- No wholesale replacement of the DeepChat-owned driver fork. -- No behavior changes outside the CUA driver vendored source and plugin skills. - -## Constraints - -- Preserve local DeepChat fork behavior and packaging paths. -- Keep user-facing CUA skill guidance aligned with MCP tools, not CLI-first - workflows. diff --git a/docs/issues/cua-driver-v0-1-5-sync/tasks.md b/docs/issues/cua-driver-v0-1-5-sync/tasks.md deleted file mode 100644 index d89c9ffbc..000000000 --- a/docs/issues/cua-driver-v0-1-5-sync/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tasks - -- [x] Inspect upstream CUA tags and identify the latest driver release. -- [x] Compare upstream `cua-driver-v0.1.4` to `cua-driver-v0.1.5`. -- [x] Confirm upstream skills are unchanged in this release. -- [x] Apply the overlay-window source fix to the local fork. -- [x] Update vendored upstream metadata to `cua-driver-v0.1.5`. -- [x] Verify packaged skills keep DeepChat MCP-first guidance. -- [x] Run required formatting, i18n, lint, and CUA plugin validation. diff --git a/docs/issues/cua-driver-v014-sync/plan.md b/docs/issues/cua-driver-v014-sync/plan.md deleted file mode 100644 index 68b36b057..000000000 --- a/docs/issues/cua-driver-v014-sync/plan.md +++ /dev/null @@ -1,25 +0,0 @@ -# CUA Driver v0.1.4 Sync Plan - -## Approach - -Apply the upstream `libs/cua-driver` delta from `cua-driver-v0.0.15` to -`cua-driver-v0.1.4`, then re-apply DeepChat fork patches where conflicts overlap. -Keep the DeepChat plugin MCP server configured as the normal `cua-driver` server. - -## Compatibility - -- Remove removed upstream tools from plugin policy and manifest: - `get_accessibility_tree`, `type_text_chars`. -- Keep `type_text` approved for text input and document its `delay_ms` fallback - path in local skills. -- Preserve DeepChat TCC ownership and app bundle identity throughout the driver, - helper build scripts, diagnostics, and permission flows. -- Preserve the DeepChat-owned update path; the bundled helper must not install - standalone upstream releases. - -## Validation - -- Build the Swift package from the vendored driver source. -- Run focused plugin presenter and packaging/signing tests. -- Run repository formatting, i18n, and lint checks after implementation. -- Inspect protected files for accidental skill or fork-patch regressions. diff --git a/docs/issues/cua-driver-v014-sync/spec.md b/docs/issues/cua-driver-v014-sync/spec.md deleted file mode 100644 index 34312f2a7..000000000 --- a/docs/issues/cua-driver-v014-sync/spec.md +++ /dev/null @@ -1,29 +0,0 @@ -# CUA Driver v0.1.4 Sync Spec - -## Goal - -Sync the vendored DeepChat CUA driver fork with upstream `trycua/cua` -`cua-driver-v0.1.4` while preserving DeepChat-specific runtime behavior and local -skill customizations. - -## Acceptance Criteria - -- Vendored upstream metadata points to tag `cua-driver-v0.1.4` and commit - `d422294b848afec99b979ac1229446c83fa44807`. -- Removed upstream tools `get_accessibility_tree` and `type_text_chars` are no - longer advertised by the DeepChat plugin manifest or policy. -- `type_text` remains the text-entry tool and supports upstream automatic CGEvent - fallback behavior. -- DeepChat fork patches remain intact: - `DeepChatPermissionProbeCommand`, `DeepChat Computer Use.app`, - `com.wefonk.deepchat.computeruse`, non-blocking MCP/daemon startup, - DeepChat-managed update messaging, and window-scoped zoom contexts. -- DeepChat-owned plugin skill files receive only minimal compatibility edits for - removed tool names. - -## Non-Goals - -- Do not expose a second Claude Code compatibility MCP server in the DeepChat plugin. -- Do not rewrite or replace local DeepChat skill content with upstream skill text. -- Do not change user-facing plugin UX beyond the upstream driver compatibility - surface required for this sync. diff --git a/docs/issues/cua-driver-v014-sync/tasks.md b/docs/issues/cua-driver-v014-sync/tasks.md deleted file mode 100644 index c24d84def..000000000 --- a/docs/issues/cua-driver-v014-sync/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# CUA Driver v0.1.4 Sync Tasks - -- [x] Apply upstream driver source changes through `cua-driver-v0.1.4`. -- [x] Resolve `CuaDriverCommand.swift` conflicts while keeping DeepChat commands - and startup behavior. -- [x] Update upstream metadata, plugin manifest, and tool policy. -- [x] Patch local DeepChat skill files only for removed tool compatibility. -- [x] Verify protected fork strings and commands remain present. -- [x] Run Swift build, focused Vitest suites, format, i18n, and lint. diff --git a/docs/issues/cua-settings-empty-message/plan.md b/docs/issues/cua-settings-empty-message/plan.md deleted file mode 100644 index 24e70a50c..000000000 --- a/docs/issues/cua-settings-empty-message/plan.md +++ /dev/null @@ -1,23 +0,0 @@ -# CUA Settings Empty Message Plan - -## Scope - -The issue is isolated to the static Computer Use plugin settings UI in -`plugins/cua/settings/assets/index.js`. - -## Implementation - -- Keep `setText()` as the fallback helper for data rows that should display `Unknown`. -- Change `setMessage()` so the transient status/error area writes the provided text verbatim and - allows an empty string after success. -- Add a jsdom regression test that loads the static settings script, invokes the Check button, and - verifies the message area is empty after a successful permission check. - -## Test Strategy - -- Run the focused renderer test for the plugin settings script. -- Run repository-required formatting, i18n, and lint checks. - -## Risks - -- Low. The change only affects the bottom message element and keeps status rows unchanged. diff --git a/docs/issues/cua-settings-empty-message/spec.md b/docs/issues/cua-settings-empty-message/spec.md deleted file mode 100644 index 9a6e2c175..000000000 --- a/docs/issues/cua-settings-empty-message/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# CUA Settings Empty Message - -## User Story - -As a user checking Computer Use plugin permissions, I want the plugin settings page to show the -permission results only in the status rows so that a completed check does not leave an unrelated -`Unknown` message at the bottom of the page. - -## Acceptance Criteria - -- A successful permission check updates the Accessibility and Screen Recording rows. -- After a successful permission check with no diagnostic error, the bottom message area is empty. -- Runtime fields can still show `Unknown` when their underlying status values are unavailable. - -## Non-goals - -- Redesigning the plugin settings layout. -- Changing runtime or permission detection behavior. - -## Open Questions - -None. diff --git a/docs/issues/cua-settings-empty-message/tasks.md b/docs/issues/cua-settings-empty-message/tasks.md deleted file mode 100644 index 8cce9085e..000000000 --- a/docs/issues/cua-settings-empty-message/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# CUA Settings Empty Message Tasks - -- [x] Confirm the source of the bottom `Unknown` message. -- [x] Update message rendering to allow an empty success state. -- [x] Add focused regression coverage. -- [x] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/issues/danger-zone-button-overflow/plan.md b/docs/issues/danger-zone-button-overflow/plan.md deleted file mode 100644 index 66913be3d..000000000 --- a/docs/issues/danger-zone-button-overflow/plan.md +++ /dev/null @@ -1,11 +0,0 @@ -# Implementation Plan - -## Change - -- Increase the Danger Zone destructive button height and vertical padding. -- Override the default button no-wrap behavior for these labels. -- Keep icons fixed while allowing the text label to wrap within the button. - -## Validation - -- Run format, i18n, lint, typecheck:web, and focused Data Settings tests. diff --git a/docs/issues/danger-zone-button-overflow/spec.md b/docs/issues/danger-zone-button-overflow/spec.md deleted file mode 100644 index 166058189..000000000 --- a/docs/issues/danger-zone-button-overflow/spec.md +++ /dev/null @@ -1,16 +0,0 @@ -# Danger Zone Button Overflow - -## Problem - -The Data Settings Danger Zone action buttons can clip long labels, especially in English where "Reset Knowledge Base Data" exceeds the default button width and single-line height. - -## Acceptance Criteria - -- Danger Zone action buttons provide enough vertical height for long labels. -- Button labels can wrap instead of overflowing their destructive button container. -- The layout remains compact and keeps the three destructive actions grouped together on wider screens. -- Existing reset behavior remains unchanged. - -## Non-goals - -- No changes to reset confirmation logic or reset data semantics. diff --git a/docs/issues/danger-zone-button-overflow/tasks.md b/docs/issues/danger-zone-button-overflow/tasks.md deleted file mode 100644 index e72d2ba73..000000000 --- a/docs/issues/danger-zone-button-overflow/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Adjust Danger Zone destructive button layout. -- [x] Update focused coverage for the overflow-safe class structure. -- [x] Run validation commands. diff --git a/docs/issues/e2e-smoke-regression/plan.md b/docs/issues/e2e-smoke-regression/plan.md deleted file mode 100644 index da75afea3..000000000 --- a/docs/issues/e2e-smoke-regression/plan.md +++ /dev/null @@ -1,355 +0,0 @@ -# E2E Smoke Regression Plan - -## Planning Summary - -This plan adds a small but real desktop smoke layer on top of the existing test pyramid. - -The recommended architecture is: - -1. Playwright Electron launches the built app. -2. The suite runs against the operator's existing local DeepChat environment. -3. The suite does not introduce an alternate `userData` path, mock backend, or E2E-only bootstrap mode. -4. Smoke tests assert on user-visible success signals rather than exact provider output. - -## Current Repository Constraints - -The local codebase already shows a few important constraints that shape the plan: - -1. Electron boot starts in `src/main/index.ts`. -2. The app already has persistent local state and provider configuration managed by `ConfigPresenter`. -3. The app already uses `data-testid` in some places, so adding a focused set of E2E anchors fits the current codebase style. -4. Because this suite intentionally runs against the current local environment, it must avoid destructive or profile-reset behavior. - -## Proposed Architecture - -### 1. Runner and Script - -Add Playwright Electron under `test/e2e` with a single smoke script: - -```json -{ - "scripts": { - "e2e:smoke": "playwright test -c test/e2e/playwright.config.ts --workers=1" - } -} -``` - -Configuration defaults: - -- `workers: 1` -- `retries: 0` locally -- generous startup and generation timeout -- `screenshot: 'only-on-failure'` -- `video: 'retain-on-failure'` -- `trace: 'retain-on-failure'` -- `outputDir: 'test-results/e2e'` - -### 2. Launch Model - -Use Playwright `_electron.launch()` against the built app after `pnpm run build`. - -Recommended launch strategy: - -- working directory: repository root -- launch args: `['.']` -- rely on `package.json` `main: "./out/main/index.js"` - -This keeps the execution model close to the real desktop app and avoids a parallel dev-server lifecycle. - -### 3. Real-Environment Execution Model - -Do not create a dedicated E2E runtime branch. - -In particular, do not: - -- redirect `userData` -- inject app-specific E2E environment variables -- start a mock LLM backend -- bootstrap a synthetic provider -- wipe or reseed current user state - -Instead, the suite should assume: - -- the operator has already configured a working provider and model -- the app is launched as-is -- tests interact with the same UI and same local state shape the operator normally uses - -Implication: - -- the suite is closer to real usage -- but it is also more stateful and less deterministic than a synthetic E2E sandbox - -### 4. Provider and Model Assumptions - -For v1, do not try to manage provider configuration in test code. - -Instead: - -- require at least one usable chat model to already be configured -- prefer the currently selected model if chat is ready -- if the UI requires an explicit model choice, select the first available chat-capable model in a stable way -- fail fast with an actionable setup error when no usable model can be found - -This keeps the suite aligned with the operator's real environment and avoids hidden bootstrap logic. - -### 5. Real-Provider Assertion Strategy - -Because the suite uses a real provider path, assertions should stay at the behavior level: - -- app launches -- input is editable -- send action triggers generation state -- assistant message appears -- assistant message is non-empty -- generation state returns to idle -- created session remains accessible after restart - -Do not depend on: - -- exact response text -- token usage numbers -- provider-specific toast copy -- provider-specific latency assumptions -- markdown-specific rendering contracts in v1 - -### 6. State Safety Strategy - -Since the suite runs in the operator's real local environment, tests must be conservative. - -Rules: - -- prefer additive actions only -- create a new conversation for smoke runs rather than mutating existing important threads -- avoid deleting sessions, providers, or settings -- avoid changing global settings in v1 unless the test immediately restores them and the risk is low -- use a unique smoke prompt prefix so created conversations are easy to recognize - -Recommended smoke prompt shape: - -```txt -[E2E_SMOKE ] Please reply with a short confirmation sentence. -``` - -### 7. Selectors and E2E Anchors - -Selector policy: - -1. Prefer `getByRole`, `getByLabel`, and other accessible locators when they are stable. -2. Add `data-testid` only for structural anchors and async-state anchors that would otherwise be brittle. -3. Avoid asserting on translated copy for core success states when a stable UI state is available. - -Recommended anchor groups: - -- app shell - - `app-root` - - `app-sidebar` - - `app-settings-button` - - `app-new-chat-button` -- chat - - `chat-page` - - `chat-message-list` - - `chat-message-user` - - `chat-message-assistant` - - `chat-input-editor` - - `chat-send-button` - - `chat-stop-button` - - `chat-generation-status` -- chat selectors - - `app-agent-switcher` - - `app-model-switcher` - - `sidebar-session-item` - - `sidebar-current-session` -- settings - - `settings-page` - - `settings-tab-general` - - `settings-tab-model-providers` - - `settings-tab-mcp` - - `settings-tab-acp-agents` - - `settings-tab-appearance` - - `settings-close-button` - -### 8. Fixture and Helper Layout - -Recommended test structure: - -```txt -test/e2e/ - playwright.config.ts - fixtures/ - electronApp.ts - testData.ts - helpers/ - chat.ts - settings.ts - wait.ts - specs/ - 01-launch.smoke.spec.ts - 02-chat.smoke.spec.ts - 03-persistence.smoke.spec.ts - 04-settings.smoke.spec.ts -``` - -Helper responsibilities: - -- `chat.ts` - - create new chat - - resolve a usable current or first available model - - send message - - wait for generation completion - - read user and assistant messages -- `settings.ts` - - open and close settings - - switch settings tabs -- `wait.ts` - - wait for app-ready shell - - detect fatal renderer errors - -### 9. P0 Smoke Flow - -Main chat smoke target: - -```txt -┌──────────────────────────────────────────────────────────┐ -│ AppBar │ -├───────────────┬──────────────────────────────────────────┤ -│ Sidebar │ ChatPage │ -│ + New Chat │ Agent: [current] Model: [usable] │ -│ Smoke Thread │ │ -│ │ User: [E2E_SMOKE ...] │ -│ │ AI: non-empty response │ -│ │ │ -│ │ [ input editor ] │ -└───────────────┴──────────────────────────────────────────┘ -``` - -Settings smoke target: - -```txt -┌──────────────────────────────────────────────────────────┐ -│ Settings │ -├─────────────────┬────────────────────────────────────────┤ -│ General │ basic settings content visible │ -│ Model Providers │ provider page reachable │ -│ Appearance │ appearance page reachable │ -│ MCP │ │ -│ ACP Agents │ │ -└─────────────────┴────────────────────────────────────────┘ -``` - -## Increment Plan - -### Increment 1 (v1) - -Target: - -- `P0-01` launch app -- `P0-02` basic chat flow -- `P0-03` restart persistence -- `P0-04` settings navigation - -Why: - -- this is the shortest path to a useful regression line -- it validates boot, selector strategy, real provider requests, end-to-end message flow, - restart persistence, and settings routing - -### Later Phases - -Add optional or broader coverage: - -- explicit provider connectivity checks behind an opt-in environment flag -- the deferred P1 scenarios from [spec.md](./spec.md) - -## Validation Plan - -After implementation, run: - -```bash -pnpm run format -pnpm run i18n -pnpm run lint -pnpm run typecheck -pnpm run build -pnpm run e2e:smoke -``` - -## Risks and Mitigations - -### Risk 1: tests touch the operator's real local state - -Impact: - -- smoke runs may create conversations or affect visible recent history - -Mitigation: - -- keep tests additive only -- create dedicated smoke conversations with a recognizable prefix -- avoid destructive actions and avoid mutating global settings in v1 - -### Risk 2: no usable provider or model is configured - -Impact: - -- the suite cannot reach the chat generation flow - -Mitigation: - -- fail fast with a clear setup error -- document the required local setup in the E2E README - -### Risk 3: tests become brittle because they depend on translated copy - -Impact: - -- harmless i18n changes break smoke tests - -Mitigation: - -- prefer role-based selectors -- add `data-testid` for structural and async-state anchors -- assert stable UI states instead of translated copy where practical - -### Risk 4: provider latency and rate limits cause flaky assertions - -Impact: - -- random failures on slower machines or under quota pressure - -Mitigation: - -- use web-first assertions -- wait on explicit generation-done state -- keep smoke prompts short -- keep `workers=1` for smoke runs - -### Risk 5: real provider responses are non-deterministic - -Impact: - -- exact response-text assertions may become flaky or invalid - -Mitigation: - -- assert on non-empty assistant output and stable UI states instead of exact content -- avoid provider-specific response-text expectations in smoke specs - -### Risk 6: existing local state changes the starting UI - -Impact: - -- startup may land on different pages or selected sessions - -Mitigation: - -- helpers should normalize the UI into a known path before asserting -- smoke specs should explicitly create a new conversation before the main chat assertions - -## Rollout Recommendation - -Use one PR for the spec and discussion, then implement in this order: - -1. infrastructure: Playwright config and basic fixtures -2. selectors: minimal anchor set for launch and main chat -3. smoke specs: `P0-01` through `P0-04` -4. follow-up PRs for optional provider integration and deferred P1 scenarios diff --git a/docs/issues/e2e-smoke-regression/spec.md b/docs/issues/e2e-smoke-regression/spec.md deleted file mode 100644 index 6a0977b27..000000000 --- a/docs/issues/e2e-smoke-regression/spec.md +++ /dev/null @@ -1,140 +0,0 @@ -# E2E Smoke Regression - -## Status - -Draft on `2026-04-22`. - -## Goal - -Add a manual desktop smoke regression suite for DeepChat using Playwright Electron. - -The suite should validate the highest-risk user flows after large refactors in a real local usage -environment rather than a synthetic E2E-only runtime. - -The primary target flows are: - -- app launch -- main chat shell -- conversation creation -- message sending and generation completion -- restart persistence -- settings navigation - -## Background - -DeepChat already has Vitest coverage for main-process and renderer-process logic under `test/main` and -`test/renderer`, but it does not yet have a true desktop E2E layer that exercises: - -- Electron main-process boot -- preload bridge wiring -- renderer startup -- real settings routing -- real chat UI interactions -- persistence behavior across restart - -That gap becomes visible after architectural refactors where unit tests still pass but the desktop flow -breaks because of startup ordering, route initialization, or real UI interaction regressions. - -## Current Decision - -This spec adopts the following baseline approach: - -1. Create a new `test/e2e` layer with Playwright Electron. -2. Run the E2E suite manually, not as mandatory CI coverage in v1. -3. Launch the built app, not the dev server, to match shipped desktop behavior. -4. Run the suite in the operator's real local app environment rather than a separate E2E-only profile. -5. Do not depend on dedicated E2E environment variables or `userData` redirection. -6. Assume the operator has already configured at least one working provider and chat-capable model. -7. Keep the entrypoint simple with a single `pnpm run e2e:smoke` command. -8. Assert on user-visible success signals instead of mock-specific or provider-specific exact output. -9. Deliver the suite in phases, with v1 smoke coverage including `P0-01` launch app, - `P0-02` basic chat flow, `P0-03` restart persistence, and `P0-04` settings navigation. - Later phases can add optional provider-integration checks and broader P1 scenarios. - -## User Stories - -- As a maintainer, I want one command to validate the core desktop path after a large refactor. -- As a maintainer, I want the E2E suite to exercise the real provider path I actually use locally. -- As a maintainer, I want the suite to stay close to the real user environment instead of adding test-only runtime forks. -- As a maintainer, I want failure artifacts like trace, video, and screenshots so regressions are debuggable. -- As a maintainer, I want the first version to stay small and stable instead of trying to automate every feature. - -## In Scope - -- Playwright Electron runner under `test/e2e` -- a single smoke entry script in `package.json` -- stable selectors for critical smoke anchors -- manual smoke specs for core desktop flows -- E2E README with run instructions and caveats for real-environment execution - -## Acceptance Criteria - -- `pnpm run e2e:smoke` is available and runs Playwright against the built Electron app. -- The E2E suite launches the desktop app through Playwright Electron rather than a browser-only runner. -- The suite does not require an alternate `userData` path or dedicated E2E app environment variables. -- The suite runs against the operator's current local DeepChat environment. -- The suite fails fast with an actionable error when no usable provider or model is configured. -- The smoke suite covers these flows end to end: - - launch app - - create a conversation - - send a message - - wait for generation to complete - - verify that an assistant message is produced - - restart the app and verify the created session still exists - - open settings and verify basic navigation works -- Failed runs retain screenshot, video, and trace artifacts under `test-results/e2e`. -- The suite is documented as a manual regression tool for large refactors and core desktop changes. - -## Non-Goals - -- Full CI gating in v1 -- Cross-platform certification for every OS in the first increment -- Exhaustive feature coverage across MCP, ACP, attachments, retry, fork, tabs, deeplinks, and advanced settings -- Deterministic response-content assertions across different providers -- Pixel-perfect visual regression baselines -- Automatic protection from changes made to the operator's current local profile -- Replacing existing Vitest coverage - -## Preconditions - -- Before running E2E, the operator must configure a valid provider and chat-capable model in DeepChat. -- The configured provider must have working credentials and network reachability. -- The suite runs in the current local environment, so it may create real conversations or touch real local state. -- The suite should avoid destructive actions and should only perform additive smoke interactions. - -## Test Scope - -### P0 Smoke Coverage (v1) - -The target P0 smoke matrix is: - -| ID | Scenario | Expected outcome | -| --- | --- | --- | -| P0-01 | Launch app | Main shell becomes visible without fatal renderer errors | -| P0-02 | Basic chat flow | User message is sent and a non-empty assistant response is produced | -| P0-03 | Restart persistence | The newly created session remains visible after app restart | -| P0-04 | Settings navigation | Settings opens, basic tabs switch, and closing returns to chat | - -### Deferred P1 Scenarios - -These remain valid follow-up work, but are not required for the first implementation increment: - -- stop generation -- retry or regenerate -- fork conversation -- file attachments -- MCP settings CRUD -- ACP agent navigation and selection -- multi-tab or multi-window isolation -- deeplink smoke - -## Discussion Defaults - -Unless we explicitly change direction during discussion, the implementation should assume: - -1. The suite is manual-only in v1. -2. The suite runs against `pnpm run build` output. -3. The only official E2E script in v1 is `pnpm run e2e:smoke`. -4. The suite runs against the user's current local profile instead of an isolated E2E profile. -5. The v1 implementation slice includes `P0-01` through `P0-04`; later phases can add optional - provider-integration checks and the deferred P1 scenarios listed above. diff --git a/docs/issues/e2e-smoke-regression/tasks.md b/docs/issues/e2e-smoke-regression/tasks.md deleted file mode 100644 index 011c344f5..000000000 --- a/docs/issues/e2e-smoke-regression/tasks.md +++ /dev/null @@ -1,139 +0,0 @@ -# E2E Smoke Regression Tasks - -Feature: `e2e-smoke-regression` -Spec: [spec.md](./spec.md) -Plan: [plan.md](./plan.md) - -## Readiness And Scope Decisions - -- [x] `T0.1` Keep the spec package complete with `spec.md`, `plan.md`, and `tasks.md` under - `docs/issues/e2e-smoke-regression/`. - Owner: QA + E2E maintainer - Effort: XS - Status: Completed - References: `spec.md` Status / In Scope, `plan.md` Planning Summary -- [x] `T0.2` Resolve the v1 scope decision: default `pnpm run e2e:smoke` covers `P0-01` through - `P0-04`, while the live provider connectivity check stays opt-in rather than part of the - default v1 smoke slice. - Owner: QA + E2E maintainer - Effort: XS - Status: Completed - References: `spec.md` Current Decision 9 / Test Scope / Discussion Defaults 5, - `plan.md` Increment Plan / Provider And Model Assumptions -- [x] `T0.3` Confirm there are no remaining `[NEEDS CLARIFICATION]` markers in - [spec.md](./spec.md) or [plan.md](./plan.md). - Owner: QA + E2E maintainer - Effort: XS - Status: Completed - References: `spec.md` Discussion Defaults, `plan.md` Planning Summary - -## Epic E1 Runner, Fixture, And Safety Baseline - -- [x] `T1.1` Add the Playwright Electron runner, `test/e2e` layout, and the single - `pnpm run e2e:smoke` entrypoint. - Owner: QA + Electron tooling - Effort: M - Status: Completed - References: `spec.md` Current Decision 1-8 / In Scope / Acceptance Criteria, - `plan.md` Proposed Architecture 1 / 8 -- [x] `T1.2` Launch the built desktop app from repository build output without an isolated - `userData` profile, mock backend, or E2E-only bootstrap branch. - Owner: QA + Electron tooling - Effort: S - Status: Completed - References: `spec.md` Current Decision 3-6 / Preconditions, - `plan.md` Proposed Architecture 2 / 3 -- [x] `T1.3` Keep fixture teardown safe across relaunches and setup failures so launched - Electron processes are always tracked and closed. - Owner: QA + Electron tooling - Effort: S - Status: Completed - References: `spec.md` Acceptance Criteria / Preconditions, - `plan.md` Proposed Architecture 8 / Risks And Mitigations Risk 6 -- [x] `T1.4` Retain screenshot, video, trace, and renderer diagnostics for failed runs under - `test-results/e2e`. - Owner: QA + Electron tooling - Effort: S - Status: Completed - References: `spec.md` User Stories / Acceptance Criteria, - `plan.md` Proposed Architecture 1 / Validation Plan - -## Epic E2 V1 Smoke Coverage - -- [x] `T2.1` Implement `P0-01` launch coverage that waits for the main shell and sidebar to - render without fatal startup failures. - Owner: QA + Renderer - Effort: S - Status: Completed - References: `spec.md` Test Scope `P0-01`, - `plan.md` Proposed Architecture 9 / Risks And Mitigations Risk 6 -- [x] `T2.2` Implement `P0-02` basic chat coverage that selects a usable agent/model, sends a - real prompt, waits for generation start and completion, and asserts on a non-empty assistant - reply. - Owner: QA + Renderer - Effort: M - Status: Completed - References: `spec.md` Goal / Acceptance Criteria / Test Scope `P0-02`, - `plan.md` Proposed Architecture 4 / 5 / 7 / 9 -- [x] `T2.3` Implement `P0-03` restart persistence coverage that relaunches the app and verifies - the created smoke sessions remain visible and reopen correctly. - Owner: QA + Renderer - Effort: M - Status: Completed - References: `spec.md` Acceptance Criteria / Test Scope `P0-03`, - `plan.md` Proposed Architecture 5 / Increment Plan -- [x] `T2.4` Implement `P0-04` settings navigation coverage that opens the real Settings window, - switches core tabs, and returns control to the main chat flow. - Owner: QA + Renderer - Effort: M - Status: Completed - References: `spec.md` Acceptance Criteria / Test Scope `P0-04`, - `plan.md` Proposed Architecture 7 / 9 / Increment Plan -- [x] `T2.5` Keep the live provider connectivity smoke check available only behind an explicit - environment flag so default smoke runs do not require credentials or network-dependent - integration assertions. - Owner: QA + Provider integration - Effort: XS - Status: Completed - References: `spec.md` Preconditions / Non-Goals / Deferred P1 Scenarios, - `plan.md` Proposed Architecture 4 / Later Phases / Risks And Mitigations Risk 2 and Risk 4 - -## Epic E3 Helper Stability And Documentation - -- [x] `T3.1` Use stable selectors and exact selected-state attributes for agent/model selection - instead of substring text matching. - Owner: QA + Renderer - Effort: S - Status: Completed - References: `spec.md` In Scope / Acceptance Criteria, - `plan.md` Proposed Architecture 7 / Risks And Mitigations Risk 3 -- [x] `T3.2` Make generation waiting lifecycle-aware by observing both the transition to - `data-generating="true"` and the return to idle. - Owner: QA + Renderer - Effort: XS - Status: Completed - References: `spec.md` Acceptance Criteria, - `plan.md` Proposed Architecture 5 / Risks And Mitigations Risk 4 -- [x] `T3.3` Keep the E2E README environment-agnostic with repo-relative links and explicit notes - about the opt-in provider connectivity check. - Owner: QA + Documentation - Effort: XS - Status: Completed - References: `spec.md` In Scope / Preconditions / Acceptance Criteria, - `plan.md` Proposed Architecture 3 / Validation Plan - -## Epic E4 Validation - -- [x] `T4.1` Run repository quality gates after E2E smoke maintenance updates: - `pnpm run format`, `pnpm run i18n`, `pnpm run lint`. - Owner: QA + E2E maintainer - Effort: XS - Status: Completed - References: `plan.md` Validation Plan -- [ ] `T4.2` Run `pnpm run build` and `pnpm run e2e:smoke` in a configured local profile to - validate the manual smoke flow end to end. - Owner: QA / local operator - Effort: M - Status: Pending operator run - References: `spec.md` Acceptance Criteria / Preconditions, - `plan.md` Validation Plan diff --git a/docs/issues/failed-message-context/plan.md b/docs/issues/failed-message-context/plan.md deleted file mode 100644 index f1ff8e48b..000000000 --- a/docs/issues/failed-message-context/plan.md +++ /dev/null @@ -1,19 +0,0 @@ -# Failed Message Context Preservation Plan - -## Approach - -- Add a shared runtime predicate for context-eligible records: include `sent` records and `assistant:error` records, exclude `pending` and `user:error`. -- Update `recordToChatMessages` to append a readable assistant summary for error blocks while preserving existing content, reasoning, and settled tool call replay behavior. -- Use the same eligibility predicate in new-turn history collection, resume context, and compaction inputs. - -## Data Flow - -- Failed generation already persists an assistant `error` record with terminal error blocks. -- On the next turn, `buildContext` selects eligible records and converts the failed assistant record into provider-facing assistant text. -- Compaction receives the same eligible assistant error records so summarized history matches provider context. - -## Compatibility - -- Stored records are unchanged. -- Existing sent history behavior and provider tool-message ordering remain unchanged. -- Pending interactions keep their existing resume path and are not included in normal next-turn context. diff --git a/docs/issues/failed-message-context/spec.md b/docs/issues/failed-message-context/spec.md deleted file mode 100644 index 9e066c18c..000000000 --- a/docs/issues/failed-message-context/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# Failed Message Context Preservation - -## User Story - -When an agent loop fails or is canceled, the next user turn should still know what happened. The failed assistant message, including any partial progress and the failure reason, must be included in the next request context so the model can recover instead of repeating work. - -## Acceptance Criteria - -- Assistant messages with status `error` are eligible for future agent context. -- User messages with status `error` and all `pending` messages remain excluded from normal next-turn context. -- Error blocks are converted into readable context text: - - `common.error.userCanceledGeneration` becomes `User canceled generation`. - - Unknown error text is preserved as-is. -- If the failed assistant message has partial content, that content is preserved and followed by a failure or cancellation summary. -- Settled tool calls with non-empty responses are still replayed; unfinished tool calls are not fabricated into tool results. -- Auto-compaction and resume context budgeting consider assistant error records consistently with next-turn context. - -## Non-Goals - -- No database schema changes. -- No IPC, renderer UI, or message storage format changes. -- No attempt to resume unfinished tool calls without stored tool responses. diff --git a/docs/issues/failed-message-context/tasks.md b/docs/issues/failed-message-context/tasks.md deleted file mode 100644 index c8aba9e09..000000000 --- a/docs/issues/failed-message-context/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Failed Message Context Preservation Tasks - -- [x] Add SDD spec, plan, and task documents. -- [x] Add focused context-builder tests for assistant error and cancel records. -- [x] Update context-builder record eligibility and assistant error conversion. -- [x] Reuse eligibility in agent runtime and compaction history selection. -- [x] Run focused tests and required repository quality commands. diff --git a/docs/issues/feishu-post-message-payload/plan.md b/docs/issues/feishu-post-message-payload/plan.md deleted file mode 100644 index 0674c2e75..000000000 --- a/docs/issues/feishu-post-message-payload/plan.md +++ /dev/null @@ -1,12 +0,0 @@ -# Feishu Post Message Payload Plan - -## Approach - -- Add a focused Feishu client test that asserts `sendMarkdown()` serializes `msg_type: 'post'` content using the Feishu i18n object directly, without an extra `post` wrapper. -- Update the Feishu markdown payload builder to match the Feishu API content schema. -- Re-run the focused Feishu client tests to confirm the regression is fixed. - -## Validation - -- Run the Feishu client unit test file. -- If the focused suite passes, the command reply path will stop failing on Feishu API code `230001 invalid message content` for normal markdown replies. diff --git a/docs/issues/feishu-post-message-payload/spec.md b/docs/issues/feishu-post-message-payload/spec.md deleted file mode 100644 index 5fad58b29..000000000 --- a/docs/issues/feishu-post-message-payload/spec.md +++ /dev/null @@ -1,21 +0,0 @@ -# Feishu Post Message Payload - -## User Story - -When DeepChat replies to Feishu remote-control commands, the bot should send a valid Feishu `post` message so users see the actual command result instead of the generic internal-error fallback. - -## Acceptance Criteria - -- Feishu command replies such as `/pair` and `/help` send valid `msg_type: 'post'` payloads accepted by the Feishu reply API. -- Successful Feishu command handling no longer falls back to `An internal error occurred while processing your request.` because of invalid markdown payload shape. -- The Feishu client has a focused regression test that verifies the serialized `post` payload shape. - -## Constraints - -- Keep the existing Feishu runtime reply flow and markdown optimization behavior. -- Limit the change to the Feishu remote-control client and focused tests. - -## Non-Goals - -- No changes to pairing authorization semantics. -- No changes to Feishu parser, router, or runtime retry behavior. diff --git a/docs/issues/feishu-post-message-payload/tasks.md b/docs/issues/feishu-post-message-payload/tasks.md deleted file mode 100644 index f59a1fabd..000000000 --- a/docs/issues/feishu-post-message-payload/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Feishu Post Message Payload Tasks - -- [x] Capture the failure from runtime logs and confirm the Feishu API rejects the current markdown reply payload with code `230001`. -- [x] Add SDD spec, plan, and tasks documents. -- [x] Add a focused Feishu client regression test for `sendMarkdown()` payload serialization. -- [x] Update the Feishu markdown payload builder to match the Feishu `post` message schema. -- [x] Run focused tests for the Feishu client. diff --git a/docs/issues/markdown-smooth-streaming-control/plan.md b/docs/issues/markdown-smooth-streaming-control/plan.md deleted file mode 100644 index acdb73f14..000000000 --- a/docs/issues/markdown-smooth-streaming-control/plan.md +++ /dev/null @@ -1,19 +0,0 @@ -# Markdown Smooth Streaming Control Plan - -## Approach - -- Add a `smoothStreaming` prop to `MarkdownRenderer`, defaulting to `false`. -- Forward that prop to `markstream-vue`'s `NodeRenderer`. -- In chat message text rendering, enable the prop only when the assistant content block status is `pending` or `loading`. -- Do not derive this from parsed part loading state, because that state reflects artifact/tag parsing rather than message generation. - -## Compatibility - -- Existing non-chat markdown surfaces inherit the default `false`. -- Existing `fade=false` behavior remains unchanged. - -## Validation - -- Cover the markdown renderer default and explicit prop behavior. -- Cover completed versus generating message block behavior. -- Run targeted renderer tests plus formatting, i18n, and lint checks. diff --git a/docs/issues/markdown-smooth-streaming-control/spec.md b/docs/issues/markdown-smooth-streaming-control/spec.md deleted file mode 100644 index 569221d03..000000000 --- a/docs/issues/markdown-smooth-streaming-control/spec.md +++ /dev/null @@ -1,17 +0,0 @@ -# Markdown Smooth Streaming Control - -## User Story - -As a chat reader, I want completed markdown messages to render without streaming animation so that history and already-generated responses feel stable. - -## Acceptance Criteria - -- Assistant text blocks that are actively generating use `smoothStreaming`. -- Completed assistant text blocks do not use `smoothStreaming`. -- Markdown artifact previews and workspace markdown previews keep non-streaming behavior by default. -- The change does not alter markdown content parsing, references, code previews, or artifact syncing. - -## Non-Goals - -- No new user setting is added. -- No IPC, database, or shared message schema changes are needed. diff --git a/docs/issues/markdown-smooth-streaming-control/tasks.md b/docs/issues/markdown-smooth-streaming-control/tasks.md deleted file mode 100644 index 54d1dddef..000000000 --- a/docs/issues/markdown-smooth-streaming-control/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Markdown Smooth Streaming Control Tasks - -- [x] Document the intended behavior and implementation approach. -- [x] Add the `smoothStreaming` prop to `MarkdownRenderer`. -- [x] Enable smooth streaming only for pending/loading assistant content blocks. -- [x] Add renderer tests for default, explicit, completed, and generating states. -- [x] Run targeted renderer tests. -- [x] Run final format, i18n, and lint checks. diff --git a/docs/issues/mcp-toggle-opens-detail/plan.md b/docs/issues/mcp-toggle-opens-detail/plan.md deleted file mode 100644 index 530ccee7b..000000000 --- a/docs/issues/mcp-toggle-opens-detail/plan.md +++ /dev/null @@ -1,16 +0,0 @@ -# Implementation Plan - -## Cause - -`McpServers` attaches a click listener to the `McpServerCard` root to open the detail sheet. Inner controls in `McpServerCard`, including the switch, do not stop native click bubbling. - -## Change - -- Stop click propagation from the card's switch wrapper. -- Stop click propagation from card menu actions, footer buttons, and description expansion. -- Add focused component tests for propagation behavior. - -## Validation - -- Run the focused renderer component test. -- Run the repository-required format, i18n, and lint checks. diff --git a/docs/issues/mcp-toggle-opens-detail/spec.md b/docs/issues/mcp-toggle-opens-detail/spec.md deleted file mode 100644 index 459b3eb7d..000000000 --- a/docs/issues/mcp-toggle-opens-detail/spec.md +++ /dev/null @@ -1,20 +0,0 @@ -# MCP Toggle Opens Detail - -## Problem - -Clicking a MCP server enable/disable switch also opens the MCP server detail/debug sheet. - -## User Story - -As a user managing MCP servers, I want the enable/disable switch to only toggle that server, so I can quickly start or stop servers without an unrelated detail panel opening. - -## Acceptance Criteria - -- Clicking a server switch emits the toggle action and does not trigger the card click/detail action. -- Clicking the card body still opens the server detail sheet. -- Other interactive controls inside the card do not accidentally trigger the card detail action. - -## Non-goals - -- No changes to MCP server start/stop behavior. -- No changes to the detail/debug sheet content. diff --git a/docs/issues/mcp-toggle-opens-detail/tasks.md b/docs/issues/mcp-toggle-opens-detail/tasks.md deleted file mode 100644 index 6a0722d59..000000000 --- a/docs/issues/mcp-toggle-opens-detail/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Stop MCP card internal control clicks from bubbling to the card root. -- [x] Add regression coverage for switch propagation. -- [x] Run format, i18n, lint, and focused tests. diff --git a/docs/issues/merge-dev-into-gen-video/plan.md b/docs/issues/merge-dev-into-gen-video/plan.md deleted file mode 100644 index caf4ddd68..000000000 --- a/docs/issues/merge-dev-into-gen-video/plan.md +++ /dev/null @@ -1,20 +0,0 @@ -# Plan - -## Scope -将 `origin/dev` 合并到当前 `gen-video` 分支,识别并解决冲突文件,保留双方必要改动,并执行仓库要求的基础校验。 - -## Implementation decisions -- 先 `git fetch origin dev`,再执行 `git merge origin/dev` 以基于最新远端 `dev` 合并。 -- 冲突解决前先阅读每个冲突文件的上下文,按文件现有模式做最小修改。 -- 若冲突涉及文档或配置,同样遵循最小差异原则,不借机整理无关内容。 -- 合并完成后执行仓库要求的 `pnpm run format`、`pnpm run i18n`、`pnpm run lint`。若命令失败,记录失败点并告知用户。 - -## Risks and mitigations -- 风险:冲突文件较多且分散,容易误删一侧逻辑。 - - 缓解:逐文件阅读冲突块上下文后再编辑,并在完成后检查 diff。 -- 风险:格式化或 lint 暴露既有问题,影响本次验证。 - - 缓解:优先区分新引入问题与仓库既有问题,向用户明确说明。 - -## Test strategy -- 使用 `git status` 确认冲突已清除。 -- 使用格式化、i18n、lint 命令验证合并后仓库状态。 diff --git a/docs/issues/merge-dev-into-gen-video/spec.md b/docs/issues/merge-dev-into-gen-video/spec.md deleted file mode 100644 index 825557004..000000000 --- a/docs/issues/merge-dev-into-gen-video/spec.md +++ /dev/null @@ -1,23 +0,0 @@ -# Merge dev into gen-video - -## User stories -- 作为 `gen-video` 分支开发者,我需要合并最新 `dev` 变更到当前分支,以便继续在最新主线基础上开发。 -- 作为评审者,我需要本次冲突解决范围清晰、仅限必要文件,并保留两侧已完成的有效修改。 - -## Acceptance criteria -- 当前分支成功合并 `origin/dev`,不存在未解决的 merge conflict。 -- 冲突文件采用最小变更原则解决,不引入与本次合并无关的重构。 -- 合并后工作区状态可继续提交,且相关校验命令已执行并记录结果。 - -## Non-goals -- 不在本次任务中实现新的产品功能。 -- 不主动修改与冲突无关的历史代码风格。 -- 不提交 commit,除非用户额外要求。 - -## Constraints -- 仅处理 `dev` 合并到当前 `gen-video` 分支产生的冲突。 -- 遵循仓库现有 SDD、格式化、i18n、lint 规范。 -- 如需保留双方逻辑,优先基于现有实现做兼容合并,而非重写。 - -## Open questions -- 无 diff --git a/docs/issues/merge-dev-into-gen-video/tasks.md b/docs/issues/merge-dev-into-gen-video/tasks.md deleted file mode 100644 index f234a30a5..000000000 --- a/docs/issues/merge-dev-into-gen-video/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -1. 获取最新 `origin/dev` 并确认当前分支状态。 -2. 创建本次合并的 SDD 文档并记录范围、约束、验证方式。 -3. 执行 `git merge origin/dev`,定位所有冲突文件。 -4. 阅读冲突文件上下文,逐个解决冲突并保留必要改动。 -5. 运行 `pnpm run format`、`pnpm run i18n`、`pnpm run lint`。 -6. 汇总结果与后续建议,等待用户决定是否提交。 diff --git a/docs/issues/permission-flow-stabilization/plan.md b/docs/issues/permission-flow-stabilization/plan.md deleted file mode 100644 index e2fffb975..000000000 --- a/docs/issues/permission-flow-stabilization/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# 权限流程稳定性(多工具/批量)(v2) Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented issue fix goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/issues/permission-flow-stabilization/spec.md b/docs/issues/permission-flow-stabilization/spec.md deleted file mode 100644 index fa8c5d11d..000000000 --- a/docs/issues/permission-flow-stabilization/spec.md +++ /dev/null @@ -1,119 +0,0 @@ -# 权限流程稳定性(多工具/批量)(v2) - -## 背景与问题(更新) - -当前 permission 流在多工具/批量场景下出现“批准后仍不继续执行、反复 tool_use、顺序混乱、重复启动 loop”等问题。结合日志与现有代码结构,根因主要来自两类: - -1) **可见性/持久化竞态(DB flush race)** -- permission 批准后会走“恢复执行工具 -> 继续生成”路径; -- 但工具执行产生的 message content 更新通常通过 `StreamUpdateScheduler` 异步落库(默认 600ms 周期); -- `StreamGenerationHandler.prepareConversationContext()` 会从 DB 重新 `getMessage/getMessageHistory` 组上下文; -- 若在落库前继续生成,模型看不到刚执行的 tool end/result,于是再次 `tool_use`,形成“批准了仍重复要权限/不执行”的表象。 - -2) **恢复链路并发/重入窗口(resume re-entry)** -- 恢复执行中存在释放 resume lock 的窗口,可能导致同一 `messageId` 的恢复链路被重复触发; -- 结果是重复 start loop / 重复继续生成 / 工具执行与 UI 状态交错。 - -此外还有“预检查不统一/载荷不完整”问题会加剧混乱: -- agent 工具 pre-check 被跳过或 permission_request payload 丢失(paths/commandInfo 等),导致执行期才触发 permission-required,破坏 batch 语义与顺序可控性。 - -本规格目标:把 permission 从“提示事件”升级为**严格的执行门闩(gating latch)**,同时约束: -- 何时暂停、何时恢复、恢复只能一次; -- 恢复后工具结果必须对后续续跑模型“可见”(至少 DB 可见); -- 批次内多权限串行处理后再统一执行。 - -## 目标 - -- 强暂停语义:出现 permission-required 后必须暂停后续工具执行与对话继续,直到用户完成决策。 -- 批次一致性:一次模型输出的多个 tool call 作为一个 batch;permission 决策绑定到该 batch(消息级)。 -- 恢复幂等:同一 `assistant messageId` 的恢复动作最多触发一次,避免重复 resume / 重复 loop。 -- 顺序与可部分拒绝:恢复后按原 tool 顺序执行;允许的执行,拒绝的回填一致错误结果并继续生成。 -- **工具结果可见性保证**:恢复执行工具后,在继续生成前,必须保证 tool 结果已写入 message content 且对“构建上下文的读路径”可见(至少 DB 可见)。 -- 不改变业务语义:不改变工具本身行为,仅修复 permission gating / resume / 持久化一致性。 - -## 非目标 - -- 不做大规模 UI 重构(可后续增强批次分组、一键允许等)。 -- 不新增权限类型(仍为 `read|write|all|command`)。 -- 不改变 MCP autoApprove 策略模型。 -- 不做跨会话/跨设备权限同步。 - -## 术语与批次定义 - -- `Batch`: 同一条 assistant message(`messageId`)中由模型一次输出的 tool_calls 集合。 -- `Permission Block`: message content 中的 `action_type: tool_call_permission` block。 -- `Pending Permission`: session runtime 中的 `pendingPermissions[]` 项。 - -批次键:`(conversationId, messageId)`。 - -## 状态机(必须满足的约束) - -### Pause(进入 waiting_permission) - -当任意 tool call 需要权限时: -- 在 message content 中创建对应 permission block(status=pending) -- session 状态进入 `waiting_permission` -- **停止执行该 batch 中任何 tool**(除非本规格显式允许“边批边跑”,本版本不允许) - -### Decide(用户逐个批准/拒绝) - -每次用户响应: -- 仅更新对应 permission block 状态为 granted/denied -- 更新 `pendingPermissions`(移除已决策项) -- 如果仍存在 pending permission:只发 `PERMISSION_UPDATED` 刷新 UI,不得触发恢复执行 - -### Resume(恢复执行,仅一次) - -当且仅当该 `messageId` 下所有 permission block 都已决策(无 pending): -- 获取并持有 resume lock(messageId 级互斥) -- 按原 tool 顺序执行: - - granted: callTool -> tool_call running/end - - denied: 生成一致的 error tool result(例如 "User denied the request.") -- **关键:在继续生成前,必须保证 message content 已落库可见** -- 然后继续生成(startStreamCompletion / 或等价续跑) - -### Idempotency(幂等) - -- 同一个 `messageId` 的恢复链路在任意时刻只能有一个在跑; -- 重复点击批准/快速点击不会导致重复恢复; -- 如果恢复过程中再次出现新的 permission-required(执行期触发),应回到 Pause,且不会丢 batch 上下文。 - -## 数据一致性与“可见性”要求(新增) - -### 为什么必须强制落库 - -`prepareConversationContext()` 从 DB 读 message/history。若恢复执行更新只停留在内存(或等待 scheduler 600ms 异步落库),下一次生成构建上下文会读到旧内容,导致模型重复 tool_use。 - -### 规范要求 - -在 `continueAfterToolsExecuted()` 触发继续生成前: -- 必须执行一次“同步落库”动作,确保当前 message content(含 tool end/result、permission block status)对 DB 读路径可见。 - -实现策略(v1): -- 直接调用 `messageManager.editMessageSilently(messageId, JSON.stringify(contentSnapshot))` 强制落库; -- 不依赖 `StreamUpdateScheduler` 的定时 flush 来保证一致性。 - -## 统一预检查(Pre-check)要求(补全) - -- 所有工具(MCP + agent)都必须在 batch pre-check 阶段尽可能产出 permission-required,避免执行期才触发导致顺序/批次语义被破坏。 -- `permission_request` 必须携带足够 payload 让 PermissionHandler 能正确 approve: - - command: `command`, `commandSignature`, `commandInfo`, `rememberable` - - filesystem write: `paths` -- 禁止 payload 丢失或被覆盖(合并策略:保留 tool 层提供的字段)。 - -## 验收标准(更新) - -- 批次内任意 tool 需要 permission:未决策前不执行任何 tool。 -- 同一 message 多个 permission:逐个决策不触发恢复,直到全部决策完成。 -- 批次恢复只发生一次,不出现同一 messageId 的重复 start loop。 -- 批次恢复后: - - 允许的工具产生 running/end 且结果回填; - - 拒绝的工具产生一致错误结果; - - **继续生成时模型能看到上述结果**,不会再次 tool_use 同一工具来要权限。 -- “批准后仍重复要 permission”的问题不再出现(同一 tool_call_id 不会再次 permission-required)。 - -## 设计决策(v1) - -- 恢复时机:只要 batch 内仍存在 pending permission,就不恢复执行;全部决策后统一恢复。 -- lock 粒度:messageId 级互斥,覆盖“恢复执行工具 + 同步落库 + 继续生成”的整个临界区。 -- 持久化策略:恢复路径强制同步落库,不依赖 scheduler 定时 flush。 diff --git a/docs/issues/permission-flow-stabilization/tasks.md b/docs/issues/permission-flow-stabilization/tasks.md deleted file mode 100644 index 9ae43229d..000000000 --- a/docs/issues/permission-flow-stabilization/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# 权限流程稳定性(多工具/批量)(v2) Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/issues/plugin-mcp-lifecycle-isolation/plan.md b/docs/issues/plugin-mcp-lifecycle-isolation/plan.md deleted file mode 100644 index 13a214f73..000000000 --- a/docs/issues/plugin-mcp-lifecycle-isolation/plan.md +++ /dev/null @@ -1,32 +0,0 @@ -# Plugin MCP Lifecycle Isolation Plan - -## Architecture - -- Treat plugin MCP servers as managed plugin resources when `ownerPluginId` is present or - `source/sourceId` identifies a plugin. -- Keep plugin-owned server configs in the MCP config store for compatibility, but branch lifecycle - behavior in `McpPresenter`. -- Store transient per-server runtime errors in `ServerManager` and expose them through - `IMCPPresenter.getServerLastError`. - -## Implementation - -- Update MCP initialization and global enable/disable to skip plugin-owned servers when applying the - global switch. -- Start plugin MCP servers from `PluginPresenter` without checking the global MCP setting. -- Filter tools, prompts, and resources so global MCP disabled hides non-plugin results but keeps - plugin-owned results. -- Suppress global MCP error notifications for plugin-owned start/listTools failures. -- Surface plugin MCP errors in plugin list and CUA settings status. - -## Test Strategy - -- Add main-process tests for global switch isolation and plugin start behavior. -- Add error-notification tests for plugin-owned connection and tool-list failures. -- Add renderer tests for disabled-global MCP state with plugin tools still visible. -- Add CUA settings regression coverage for MCP error state. - -## Risks - -- Moderate. MCP tools are shared across runtime and UI surfaces, so filtering must happen in the - presenter/store boundaries without changing stored config. diff --git a/docs/issues/plugin-mcp-lifecycle-isolation/spec.md b/docs/issues/plugin-mcp-lifecycle-isolation/spec.md deleted file mode 100644 index eea53dc21..000000000 --- a/docs/issues/plugin-mcp-lifecycle-isolation/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -# Plugin MCP Lifecycle Isolation - -## User Story - -As a user with the CUA plugin enabled, I want the plugin MCP runtime to follow plugin state rather -than the global MCP switch, so Computer Use remains available when I disable normal MCP tools and -does not spam global connection failure toasts. - -## Acceptance Criteria - -- The global MCP switch starts and stops only non-plugin MCP servers. -- Plugin-owned MCP servers are identified by `ownerPluginId` or `source: plugin`. -- Enabled plugin MCP servers start when the plugin is active even if global MCP is disabled. -- Plugin MCP connection and tool-list failures do not show global MCP error toasts. -- Plugin MCP failures are visible from plugin status surfaces. -- DeepChat agent tools can still include plugin MCP tools while global MCP is disabled. - -## Non-goals - -- Redesigning plugin installation or runtime detection. -- Changing ACP agent MCP selection behavior. -- Migrating existing MCP settings. - -## Open Questions - -None. diff --git a/docs/issues/plugin-mcp-lifecycle-isolation/tasks.md b/docs/issues/plugin-mcp-lifecycle-isolation/tasks.md deleted file mode 100644 index b4a5d3aa0..000000000 --- a/docs/issues/plugin-mcp-lifecycle-isolation/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Plugin MCP Lifecycle Isolation Tasks - -- [x] Document plugin MCP lifecycle requirements. -- [x] Make MCP presenter lifecycle owner-aware. -- [x] Suppress plugin MCP global notifications and track last error. -- [x] Keep plugin MCP visible in renderer state when global MCP is disabled. -- [x] Surface plugin MCP errors in plugin settings UI. -- [x] Add focused regression coverage. -- [x] Run format, i18n, lint, and focused tests. diff --git a/docs/issues/plugin-settings-surface-isolation/plan.md b/docs/issues/plugin-settings-surface-isolation/plan.md deleted file mode 100644 index 8343cb84a..000000000 --- a/docs/issues/plugin-settings-surface-isolation/plan.md +++ /dev/null @@ -1,67 +0,0 @@ -# Plugin Settings Surface Isolation Plan - -## Implementation Approach - -- Show the plugin settings action whenever the plugin list item exposes a settings contribution, - regardless of whether the plugin is currently enabled. -- Remove the enablement guard from the dedicated plugin settings window flow. -- In development, prefer workspace plugin directories over user-data installation directories when - discovering official plugins so stale local installs cannot mask newer settings metadata. -- In development, re-copy workspace directory plugins into userData installs during activation so - script-only fixes land even when `plugin.json` is unchanged. -- Treat an installed official plugin as stale when its hydrated manifest differs from the current - official manifest, even if the version string is unchanged. -- Preserve plugin-local `config.json` when reinstalling a stale official plugin so credentials do - not disappear during self-healing. -- When discovery rejects an installed official plugin as unsupported or untrusted, remove the - persisted installation record and disable plugin-owned MCP servers, settings resources, and tool - policies before initialization tries to reactivate them. -- Keep Feishu's MCP bootstrap self-contained with only Node builtins in the installed entrypoint, - and use a built-in stdio warning responder when credentials are missing. -- Clear the Feishu settings page message banner on non-error MCP states so prior failures do not - linger after recovery. -- Pin the Feishu `npx` package invocation and only pass through an explicit registry override from - the environment. -- Replace Feishu's blanket MCP auto-approval with an empty default allowlist. -- Resolve settings contributions from the current official manifest instead of trusting an older - installed manifest copy, while still reusing installed file paths when those assets exist. -- Resolve plugin settings contributions from the installed plugin manifest when stored plugin - resource records are absent or no longer point to valid files. -- Materialize packaged official plugin assets on demand when a settings contribution exists but no - installed plugin directory is available yet. -- Reuse the same resolved settings contribution for plugin list serialization and for opening the - dedicated plugin settings window. -- Filter plugin-owned MCP servers out of the existing global MCP settings renderer so plugin MCP - remains a plugin-local concern while existing built-in and user-managed MCP behavior stays - unchanged. - -## Affected Areas - -- `src/main/presenter/pluginPresenter/index.ts` -- `plugins/feishu/settings/assets/index.js` -- `plugins/feishu/mcp/serve.mjs` -- `plugins/feishu/plugin.json` -- `src/renderer/src/components/mcp-config/components/McpServers.vue` -- Focused presenter and renderer regression tests - -## Test Strategy - -- Add a renderer regression test covering a disabled plugin that still shows the settings action. -- Add a main-process regression test covering plugin settings availability and opening when stored - resources are missing. -- Add a main-process regression test covering opening settings for a disabled packaged plugin. -- Add a main-process regression test covering startup self-heal for stale same-version installs. -- Add a main-process regression test covering dev-directory sync when only plugin files changed. -- Add a main-process regression test covering cleanup when discovery rejects a persisted official - plugin installation. -- Add a regression assertion that the Feishu installed MCP bootstrap does not statically import host - SDK packages. -- Add focused Feishu regression assertions for the pinned bootstrap package version, registry - override behavior, safer auto-approve defaults, and stale settings error clearing. -- Add a renderer regression test covering plugin-owned MCP servers being hidden from the global MCP - settings list. - -## Risks - -- Low to moderate. Manifest fallback must only return settings entries whose installed files exist, - or the UI could expose an unusable settings action. diff --git a/docs/issues/plugin-settings-surface-isolation/spec.md b/docs/issues/plugin-settings-surface-isolation/spec.md deleted file mode 100644 index 1d4f778d1..000000000 --- a/docs/issues/plugin-settings-surface-isolation/spec.md +++ /dev/null @@ -1,52 +0,0 @@ -# Plugin Settings Surface Isolation - -## User Need - -As a user enabling the Feishu plugin, I need an immediate settings entry on the plugin card so I can -configure the plugin, and I need the plugin-owned MCP server to stay inside the plugin experience -instead of being mixed into the existing global settings or MCP settings surfaces. - -## Acceptance Criteria - -- Plugins with a declared settings contribution expose their settings action even while disabled so - users can configure required credentials before enabling the plugin. -- Plugin settings contributions still resolve when an older installed plugin copy lags behind the - current official manifest metadata. -- In development, when a workspace plugin and an installed plugin directory share the same official - plugin id, settings metadata resolves from the workspace plugin before the stale installed copy. -- Official plugins reinstall when a same-version installed copy is stale, so outdated MCP entrypoints - cannot survive on version equality alone. -- In development, official plugin directory installs stay synchronized with workspace files even when - only non-manifest files changed. -- Discovery that rejects an official plugin as unsupported or untrusted clears its persisted - installation record and plugin-owned runtime resources before startup activation can reuse them. -- Enabling an official plugin with a declared settings contribution exposes the plugin settings - action on the Plugins settings page without depending on previously persisted resource records. -- Opening plugin settings still works when persisted plugin resource records are missing or stale, - as long as the installed plugin manifest still declares a valid settings contribution. -- Reinstalling a stale official plugin preserves plugin-local configuration such as `config.json`. -- Plugin-owned MCP entrypoints remain runnable after installation into userData and must not rely on - static imports from the workspace or app-level `node_modules`. -- The Feishu plugin settings page clears stale MCP error text whenever the Feishu MCP is not in an - error state. -- The Feishu MCP bootstrap launches a pinned upstream package version and only honors explicit - registry overrides instead of injecting a hardcoded registry fallback. -- The Feishu plugin manifest does not auto-approve every MCP tool call by default. -- Global MCP settings do not render plugin-owned MCP servers identified by `source: plugin`. -- Plugin-owned MCP runtime status remains available from plugin-specific settings/status surfaces. - -## Constraints - -- Keep plugin-owned MCP server configs in the existing MCP config store for runtime compatibility. -- Preserve the existing plugin settings window flow and plugin manifest contract. -- Packaged official plugins may need to materialize their settings assets before the plugin is - enabled so the settings window can load from a real file path. - -## Non-goals - -- Redesigning plugin installation, runtime detection, or plugin settings UX. -- Changing core MCP lifecycle behavior beyond renderer visibility for plugin-owned servers. - -## Open Questions - -None. diff --git a/docs/issues/plugin-settings-surface-isolation/tasks.md b/docs/issues/plugin-settings-surface-isolation/tasks.md deleted file mode 100644 index cacf2a539..000000000 --- a/docs/issues/plugin-settings-surface-isolation/tasks.md +++ /dev/null @@ -1,17 +0,0 @@ -# Plugin Settings Surface Isolation Tasks - -- [x] Allow disabled plugins to expose and open their settings contribution. -- [x] Add plugin settings contribution fallback in `PluginPresenter`. -- [x] Prefer current official plugin settings metadata when an installed copy is stale. -- [x] Prefer workspace official plugin directories over stale installed copies during dev discovery. -- [x] Reinstall stale same-version official plugins and preserve `config.json` during refresh. -- [x] Keep dev directory plugin installs synced even when only file contents changed. -- [x] Clear persisted plugin installation state when discovery rejects unsupported or untrusted - official plugins. -- [x] Keep Feishu installed MCP bootstrap self-contained and free of static host SDK imports. -- [x] Clear stale MCP error text from the Feishu settings page after healthy status refreshes. -- [x] Pin the Feishu MCP bootstrap package and remove the hardcoded registry fallback. -- [x] Replace Feishu's blanket MCP auto-approve default with an empty allowlist. -- [x] Hide plugin-owned MCP servers from the global MCP settings list. -- [x] Add focused regression coverage for presenter and renderer behavior. -- [x] Run focused validation and repo-required format/i18n/lint checks as feasible. diff --git a/docs/issues/plugin-skill-tool-guidance/plan.md b/docs/issues/plugin-skill-tool-guidance/plan.md deleted file mode 100644 index 0b44e88cc..000000000 --- a/docs/issues/plugin-skill-tool-guidance/plan.md +++ /dev/null @@ -1,28 +0,0 @@ -# Plugin Skill Tool Guidance Plan - -## Implementation Approach - -- Add a `skills` contribution to the Feishu plugin manifest that points at a plugin-owned agent - skill folder. -- Create a `SKILL.md` file for the Feishu plugin that frames `feishu-tools` as an MCP server tool - surface and tells the model to invoke matching tools directly for Feishu/Lark tasks. -- Keep the skill generic enough to work with whichever Feishu/Lark tools are currently exposed by - the active MCP preset, using the live tool names and descriptions as the source of truth. -- Add a focused regression assertion that the Feishu manifest and skill file stay wired together. - -## Affected Areas - -- `plugins/feishu/plugin.json` -- `plugins/feishu/skills/feishu-tools/SKILL.md` -- `test/main/presenter/pluginPresenter.test.ts` - -## Test Strategy - -- Add a source-level regression test asserting that the Feishu plugin manifest declares the plugin - skill contribution. -- Assert that the skill file includes explicit MCP routing guidance so the regression catches future - removals of the usage instructions. - -## Risks - -- Low. The change adds guidance metadata but does not alter plugin startup or runtime behavior. diff --git a/docs/issues/plugin-skill-tool-guidance/spec.md b/docs/issues/plugin-skill-tool-guidance/spec.md deleted file mode 100644 index 824fd6840..000000000 --- a/docs/issues/plugin-skill-tool-guidance/spec.md +++ /dev/null @@ -1,34 +0,0 @@ -# Plugin Skill Tool Guidance - -## User Need - -As a user who already enabled the Feishu plugin and can see its tools in DeepChat, I need the AI to -understand that the plugin exposes MCP tools for Feishu/Lark work so it invokes those tools directly -instead of asking me to classify the plugin type or explain how to call it. - -## Acceptance Criteria - -- The Feishu plugin declares an agent skill contribution in its manifest so DeepChat can register a - plugin-owned skill alongside the MCP server. -- The skill explicitly tells the model that the plugin is an MCP tool surface and that it should not - ask the user to classify the plugin as MCP, CLI, or another type. -- The skill gives direct routing guidance for common Feishu/Lark requests such as documents, - spreadsheets, knowledge content, and other supported workspace artifacts. -- The skill explains that available Feishu/Lark tools depend on the current MCP preset and that the - model should use currently exposed tool names and descriptions as the source of truth. - -## Constraints - -- Keep the change within the Feishu plugin manifest and skill assets. -- Do not redesign plugin MCP startup, settings UX, or global tool routing. -- Keep the guidance compatible with the current plugin skill registration path in `PluginPresenter`. - -## Non-goals - -- Changing Feishu MCP tool implementations. -- Rewriting DeepChat's global tool-selection prompt. -- Adding renderer UI for skill management. - -## Open Questions - -None. diff --git a/docs/issues/plugin-skill-tool-guidance/tasks.md b/docs/issues/plugin-skill-tool-guidance/tasks.md deleted file mode 100644 index 66ac35c4d..000000000 --- a/docs/issues/plugin-skill-tool-guidance/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Plugin Skill Tool Guidance Tasks - -- [x] Add a Feishu plugin `skills` contribution in `plugin.json`. -- [x] Create the Feishu plugin `SKILL.md` guidance for MCP tool usage. -- [x] Add focused regression coverage for the manifest-to-skill wiring. -- [x] Run focused validation and repo-required format/i18n/lint checks as feasible. diff --git a/docs/issues/pr-review-followups/plan.md b/docs/issues/pr-review-followups/plan.md deleted file mode 100644 index 831fddc43..000000000 --- a/docs/issues/pr-review-followups/plan.md +++ /dev/null @@ -1,13 +0,0 @@ -# Implementation Plan - -## Change - -- Wrap non-critical settings activity logging in safe guards. -- Gate skill activity recording on successful presenter results. -- Add defensive handling around recent activity loading. -- Add accessible labels for the plugin refresh action. -- Reconcile stale SDD references and small review nits. - -## Validation - -- Run format, i18n, lint, typecheck, and focused tests around settings activity, overview, MCP servers, skills settings, and data settings where touched. diff --git a/docs/issues/pr-review-followups/spec.md b/docs/issues/pr-review-followups/spec.md deleted file mode 100644 index d11b0b8ce..000000000 --- a/docs/issues/pr-review-followups/spec.md +++ /dev/null @@ -1,20 +0,0 @@ -# PR Review Followups - -## Problem - -Review feedback on the settings control center PR found a few reliability, accessibility, documentation, and test-hardening issues that should be addressed before merge. - -## Acceptance Criteria - -- Settings activity logging failures must not fail successful primary operations. -- Skill settings activity is recorded only after skill presenter operations report success. -- Settings overview recent activity loading falls back safely when IPC or storage fails. -- Icon-only settings controls have accessible names. -- SDD docs describing the settings overview agree with the implemented layout. -- Small edge-case and test-hardening review items are fixed where they are low risk. -- Large machine-translation scope for secondary locales is deferred rather than changed in this PR. - -## Non-goals - -- No broad localization rewrite for every non-source locale. -- No feature behavior changes beyond the reviewed bug fixes. diff --git a/docs/issues/pr-review-followups/tasks.md b/docs/issues/pr-review-followups/tasks.md deleted file mode 100644 index fe8b17364..000000000 --- a/docs/issues/pr-review-followups/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Add SDD artifacts. -- [x] Fix reliability and accessibility review findings. -- [x] Reconcile stale docs and low-risk nits. -- [x] Resolve intentionally deferred locale review threads. -- [x] Run validation commands. diff --git a/docs/issues/provider-validation-disabled/plan.md b/docs/issues/provider-validation-disabled/plan.md deleted file mode 100644 index 809a4679d..000000000 --- a/docs/issues/provider-validation-disabled/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# Provider Validation Disabled State Plan - -## Scope - -The regression is limited to provider settings verification controls in renderer components. - -## Implementation - -- Gate verify-key entry points on `provider.enable` in the affected settings components. -- Disable the visible verify buttons so the UI matches the runtime behavior. -- Add a focused renderer regression test around `ProviderApiConfig`, which is the shared verify - entry point for generic providers. - -## Test Strategy - -- Run the focused renderer test for `ProviderApiConfig`. -- Run repository-required formatting, i18n, and lint checks when dependencies are available. - -## Risks - -- Low. The change only blocks verification while a provider is explicitly disabled. diff --git a/docs/issues/provider-validation-disabled/spec.md b/docs/issues/provider-validation-disabled/spec.md deleted file mode 100644 index ecdc7396a..000000000 --- a/docs/issues/provider-validation-disabled/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# Provider Validation Disabled State - -## User Story - -As a user editing provider settings, I want the verify-key action to stay unavailable while the -provider is disabled so that I do not see a misleading `Provider not initialized` error. - -## Acceptance Criteria - -- Disabled providers do not open the model check flow from the generic verify-key action. -- Disabled providers do not run inline verification handlers that would surface the initialization - error. -- Enabled providers keep the existing verification behavior. - -## Non-goals - -- Redesigning the provider settings layout. -- Changing provider initialization behavior in the main process. - -## Open Questions - -None. diff --git a/docs/issues/provider-validation-disabled/tasks.md b/docs/issues/provider-validation-disabled/tasks.md deleted file mode 100644 index 3c8154140..000000000 --- a/docs/issues/provider-validation-disabled/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Provider Validation Disabled State Tasks - -- [x] Confirm the verify-key error is triggered from disabled providers. -- [x] Guard verification actions behind the provider enabled state. -- [x] Add focused renderer regression coverage for the shared verify button. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` (blocked locally: `pnpm install` - cannot fetch `https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz` in this sandbox). diff --git a/docs/issues/question-tool-prompt-optimization/plan.md b/docs/issues/question-tool-prompt-optimization/plan.md deleted file mode 100644 index 86b203a9c..000000000 --- a/docs/issues/question-tool-prompt-optimization/plan.md +++ /dev/null @@ -1,14 +0,0 @@ -# Question Tool Prompt Optimization Plan - -## Implementation Direction - -- Use [spec.md](./spec.md) as the source of requirements and acceptance criteria. -- Identify the smallest implementation slice that satisfies the documented issue fix goal. -- Keep renderer-main changes on typed contracts, typed clients, and existing presenter boundaries. -- Preserve compatibility for stored user data, settings, and exported artifacts unless the spec explicitly defines a migration. - -## Validation - -- Add or update focused Vitest coverage for changed main, renderer, or shared behavior. -- Run targeted tests for the touched subsystem before the repository quality gates. -- Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. diff --git a/docs/issues/question-tool-prompt-optimization/spec.md b/docs/issues/question-tool-prompt-optimization/spec.md deleted file mode 100644 index b84c675b8..000000000 --- a/docs/issues/question-tool-prompt-optimization/spec.md +++ /dev/null @@ -1,28 +0,0 @@ -# Question Tool Prompt Optimization - -## Summary - -This change intentionally keeps the existing `deepchat_question` runtime contract unchanged. -It only improves model-facing content so the agent is more likely to use the tool and more likely to provide valid single-question arguments. - -## Decisions - -- Keep the public tool name as `deepchat_question`. -- Keep the current single-question schema: - - `header?` - - `question` - - `options` - - `multiple?` - - `custom?` -- Do not add questionnaire support, alias fields, or runtime auto-repair. -- Improve: - - schema field descriptions - - tool definition description - - system prompt guidance - - validation error wording - -## Non-Goals - -- No new UI or remote behavior -- No state or protocol changes -- No compatibility parsing for `questions`, `allowOther`, or stringified `options` diff --git a/docs/issues/question-tool-prompt-optimization/tasks.md b/docs/issues/question-tool-prompt-optimization/tasks.md deleted file mode 100644 index 3de5278cf..000000000 --- a/docs/issues/question-tool-prompt-optimization/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Question Tool Prompt Optimization Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/issues/tool-call-path-summary/plan.md b/docs/issues/tool-call-path-summary/plan.md deleted file mode 100644 index 1cd0981df..000000000 --- a/docs/issues/tool-call-path-summary/plan.md +++ /dev/null @@ -1,17 +0,0 @@ -# Tool Call Path Summary Plan - -## Approach - -- Extend the shared tool call summary helper with optional tool context. -- Prefer tool-specific fields before falling back to the existing summary extraction. -- Pass the current tool name from the renderer tool call block and remote trace renderer. - -## Affected Paths - -- Collapsed tool call pill summaries in chat messages. -- Remote process trace lines generated from tool calls. - -## Compatibility - -- Existing helper callers remain valid because the context argument is optional. -- Missing or non-string `path` values fall back to the previous generic summary. diff --git a/docs/issues/tool-call-path-summary/spec.md b/docs/issues/tool-call-path-summary/spec.md deleted file mode 100644 index 10c96b5b5..000000000 --- a/docs/issues/tool-call-path-summary/spec.md +++ /dev/null @@ -1,25 +0,0 @@ -# Tool Call Path Summary - -## User Story - -Users should see the target file path in collapsed `read` and `write` tool call pills, even when -pagination or content fields appear before `path` in the tool arguments. - -## Acceptance Criteria - -- `read` tool calls preview `path` before `offset`, `limit`, or `base_directory`. -- `write` tool calls preview `path` before `content` or `base_directory`. -- `exec` tool calls continue to preview `command`. -- Generic tool calls keep the existing fallback summary behavior. -- Malformed JSON and missing paths continue to fall back without throwing. - -## Non-goals - -- Do not change the visible collapsed pill layout. -- Do not add new i18n strings. -- Do not change raw params shown in the expanded details panel. - -## Constraints - -- Keep the summary helper compatible with existing callers. -- Apply the same path-first behavior to renderer pills and remote trace logs. diff --git a/docs/issues/tool-call-path-summary/tasks.md b/docs/issues/tool-call-path-summary/tasks.md deleted file mode 100644 index a5e80adf8..000000000 --- a/docs/issues/tool-call-path-summary/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tool Call Path Summary Tasks - -- [x] Document the issue and intended behavior. -- [x] Add tool-aware summary extraction. -- [x] Pass tool names from renderer and remote trace callers. -- [x] Add regression tests for read/write path-first summaries. -- [x] Run targeted and required checks. diff --git a/docs/issues/tool-output-guardrails/plan.md b/docs/issues/tool-output-guardrails/plan.md deleted file mode 100644 index 3cd0e1bd8..000000000 --- a/docs/issues/tool-output-guardrails/plan.md +++ /dev/null @@ -1,30 +0,0 @@ -# Tool Output Guardrails Plan - -## Summary - -- Keep the existing single-tool offload behavior. -- Add batch fitting for tool results in the new session agent path only. -- Preserve the largest prefix of tool results that can still fit the next model call. -- Downgrade overflow tail results to the fixed context-window failure message before continuing. -- Keep terminal error fallback when even the fully downgraded batch cannot fit. - -## Implementation - -- Extend `ToolOutputGuard` with a batch fitting helper that: - - evaluates the full staged batch against the context budget - - downgrades tail items one by one to the fixed failure message - - cleans up offload files for downgraded items - - returns terminal fallback if the fully downgraded batch still does not fit -- Refactor `executeTools()` in `agentRuntimePresenter/dispatch.ts` into two phases: - - execute tools and stage candidate outputs plus side effects - - fit the staged batch, then commit final tool messages, blocks, hooks, and search persistence once -- Keep `question` and `permission` pauses on the immediate path; they are not part of staged batch fitting. -- Keep deferred permission-resume behavior unchanged. - -## Test Plan - -- Multi-`read` batch: keep prefix, downgrade overflow tail, continue next provider turn. -- Mixed `exec`/`read`: downgraded offloaded results must delete their `.offload` files. -- Search resource result in downgraded tail: no search block and no persisted search rows. -- Fully downgraded batch still too large: return terminal error. -- Preserve existing deferred single-tool resume regressions. diff --git a/docs/issues/tool-output-guardrails/spec.md b/docs/issues/tool-output-guardrails/spec.md deleted file mode 100644 index 2c58fc5f5..000000000 --- a/docs/issues/tool-output-guardrails/spec.md +++ /dev/null @@ -1,77 +0,0 @@ -# Agent 工具输出保护与错误呈现 - -> 状态: Draft -> 日期: 2025-03-08 - -## 背景 - -当前 agent 运行中出现以下问题: - -- Provider 报错会出现在主进程日志, 但 UI 未必能看到错误信息. -- `directory_tree` 无深度限制, 可能产生巨量输出, 触发 10MB 限制. -- 工具返回过大时会被直接注入到 LLM 上下文, 容易导致请求失败. -- 多个 tool call 在单次 loop 内各自不大, 但累计后仍可能挤爆上下文窗口, 尤其是 `read` 一次读取大量文件时. - -## 目标 - -- 让生成失败时的错误信息可见并可追溯. -- 给 `directory_tree` 增加深度控制, 最大不超过 3. -- 对过大的工具输出做 offload, 用小的 stub 替代进入上下文. -- 当同一轮多个 tool 结果累计超窗时, 保留能放下的前缀结果, 将尾部结果统一降级为固定失败文案并继续后续模型调用. - -## 非目标 - -- 不改动或替换 `agentPresenter/tool` 下的 `ToolRegistry`/`toolRouter`. -- 不改变搜索结果的解析逻辑. -- 不改 legacy `AgentPresenter` 链路, 本次仅覆盖新 session agent. - -## 用户故事 - -1. 作为用户, 我希望生成失败时能在 UI 直接看到原始错误文本. -2. 作为模型, 我希望能指定目录树深度, 避免一次输出过大. -3. 作为系统, 我希望工具输出过大时自动 offload, 仍可在需要时读取完整内容. -4. 作为模型, 我希望当同一批 tool 结果累计超窗时, 能明确知道哪些尾部 tool 因上下文不足而失败, 从而调整下一步策略. - -## 验收标准 - -### 错误呈现 - -- 生成失败会将消息状态置为 `error`, 并写入一个 error block. -- error block 直接显示 raw error 文本(不要求点击展开). -- `STREAM_EVENTS.ERROR` 会携带错误文本, 便于 UI 展示或通知. - -### `directory_tree` 深度控制 - -- `directory_tree` 增加 `depth` 可选参数, 默认值为 1. -- depth 最大为 3, 超出直接校验失败. -- 深度计数方式为 root=0. - - depth=0: 仅返回根目录下条目, 不展开子目录. - - depth=1: 展开一级子目录, 不包含孙级. -- 响应格式保持不变: JSON 数组 `{ name, type, children? }`. - -### 工具输出 offload - -- 当工具输出字符串长度 > 3000 字符时触发 offload. -- 完整内容写入: - - `~/.deepchat/sessions//tool_.offload` -- LLM 只收到 stub, 包含: - - 总字符数 - - 预览片段 - - 完整文件的绝对路径 -- 模型可以通过文件类工具读取上述路径. -- 文件类读取工具仅放行当前会话 `conversationId` 对应目录. -- `tool_call_response_raw` 不被改写, 避免影响搜索结果处理. - -### 同轮批量尾部降级 - -- 仅在新 session agent 链路启用. -- 同一轮多个已完成 tool call 在准备进入下一次上下文前, 必须作为一个 batch 统一做预算拟合. -- 如果所有结果都能放下, 保持原样进入上下文. -- 如果累计超窗, 系统从该 batch 的尾部开始逐个降级为固定失败文案: - - `The tool call with ID and name failed because the remaining context window is too small to continue this turn.` -- 降级的 tool 视为失败: - - assistant tool_call block 显示固定失败文案 - - 不保留 search block / search result 持久化 - - 不保留成功型 hooks -- 经过尾部降级后只要 batch 可以放进上下文, 就继续后续模型调用. -- 如果把该 batch 所有 tool 都降级为固定失败文案后仍无法放进上下文, 保持 terminal error 兜底, 结束该 turn. diff --git a/docs/issues/tool-output-guardrails/tasks.md b/docs/issues/tool-output-guardrails/tasks.md deleted file mode 100644 index e679fac0a..000000000 --- a/docs/issues/tool-output-guardrails/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Agent 工具输出保护与错误呈现 Tasks - -- [ ] Confirm [spec.md](./spec.md) has no unresolved `[NEEDS CLARIFICATION]` markers. -- [ ] Map affected contracts, presenters, renderer clients, stores, and UI components. -- [ ] Implement the smallest complete slice that satisfies the acceptance criteria. -- [ ] Add or update focused tests for the changed behavior. -- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint`. diff --git a/docs/issues/windows-exec-output-encoding/plan.md b/docs/issues/windows-exec-output-encoding/plan.md deleted file mode 100644 index 8ddb9db82..000000000 --- a/docs/issues/windows-exec-output-encoding/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# Windows Exec Output Encoding Plan - -## Approach - -- Normalize Windows shell sessions to UTF-8 before running user commands. -- Decode stdout and stderr from user-visible subprocesses as byte streams with a streaming - decoder instead of per-chunk string conversion. -- Share the command wrapping and decoder helper across exec, background exec, and skill - execution paths. - -## Affected Paths - -- `agent-filesystem.exec` detached and managed foreground execution. -- Background exec sessions. -- Skill script foreground execution. - -## Compatibility - -- Non-Windows commands are passed through unchanged. -- Existing output formatting stays the same: command output first, then `Exit Code`, timeout, - and offload metadata. diff --git a/docs/issues/windows-exec-output-encoding/spec.md b/docs/issues/windows-exec-output-encoding/spec.md deleted file mode 100644 index 366614356..000000000 --- a/docs/issues/windows-exec-output-encoding/spec.md +++ /dev/null @@ -1,26 +0,0 @@ -# Windows Exec Output Encoding - -## User Story - -Windows users can run `agent-filesystem.exec` and skill scripts against files with Chinese -names and read the command output without mojibake. - -## Acceptance Criteria - -- `agent-filesystem.exec` preserves Chinese output from PowerShell and cmd on Windows. -- Foreground and background exec paths decode output with streaming UTF-8 semantics. -- Skill script foreground output preserves Chinese text on Windows. -- Existing exit code, timeout, and output offload messages are unchanged. - -## Non-goals - -- Do not change tool call rendering in the renderer. -- Do not change plugin, hook notification, or RTK internal command output handling. -- Do not support commands that intentionally switch the console back to a non-UTF-8 code page. -- Do not add Git Bash or WSL-specific encoding setup; those shells are expected to provide their - own UTF-8 behavior. - -## Constraints - -- Do not add third-party dependencies. -- Keep user-facing command output behavior compatible outside of encoding fixes. diff --git a/docs/issues/windows-exec-output-encoding/tasks.md b/docs/issues/windows-exec-output-encoding/tasks.md deleted file mode 100644 index 102fcda25..000000000 --- a/docs/issues/windows-exec-output-encoding/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Windows Exec Output Encoding Tasks - -- [x] Document the issue and intended behavior. -- [x] Add shared shell encoding helpers. -- [x] Apply UTF-8 command wrapping to exec and skill shell invocations. -- [x] Replace direct UTF-8 chunk conversion with streaming decoding for user-visible output. -- [x] Add unit tests for Windows wrapping and split Chinese UTF-8 chunks. -- [x] Run format, i18n, lint, and targeted tests. diff --git a/docs/spec-driven-dev.md b/docs/spec-driven-dev.md index 83d8efa10..9a89f76c9 100644 --- a/docs/spec-driven-dev.md +++ b/docs/spec-driven-dev.md @@ -29,7 +29,14 @@ If a change is tiny, keep all three files short. 3. **Task Breakdown** - Small tasks that can be reviewed independently 4. **Implementation & Validation** - TDD (pragmatic), Presenter patterns, UI consistency, quality gates -Before implementation, inspect existing docs and code, choose the correct SDD folder, and resolve every `[NEEDS CLARIFICATION]` marker. Move completed or stale goal folders to `docs/archives//`. Add an archive note when historical documents reference code paths that moved or were removed. Delete documents that only describe removed code and have no reusable decision record. +Before implementation, inspect existing docs and code, choose the correct SDD folder, and resolve every `[NEEDS CLARIFICATION]` marker. Keep SDD folders active only while they are driving current work. When a goal is implemented, fold durable maintenance facts into the current project docs and delete the old goal folder. Delete stale goal folders that only describe removed code, abandoned implementation ideas, old branch plans, or one-off bug fixes with no reusable decision record. + +Retention policy: + +- Feature and architecture SDD folders stay only while the work is active. +- Completed feature/architecture SDD content should become current documentation in `README.md`, `ARCHITECTURE.md`, `FLOWS.md`, `architecture/*.md`, or `guides/*.md`. +- Bug-fix issue SDD folders older than two weeks should be removed unless they still describe an active regression. +- Long-term history should be recovered from git history, not accumulated under `docs/archives/`. ## Six Core Principles diff --git a/docs/specs/sqlite-mainline-normalization/plan.md b/docs/specs/sqlite-mainline-normalization/plan.md deleted file mode 100644 index 7ec28871e..000000000 --- a/docs/specs/sqlite-mainline-normalization/plan.md +++ /dev/null @@ -1,182 +0,0 @@ -# `agent.db` 主链路 SQLite 结构化收敛实施计划 - -## 1. 目标 - -把 `new_sessions` 与 `deepchat_*` 主链路中的热路径 JSON 依赖下沉到结构化 SQLite 表,同时保持: - -1. renderer 侧消息类型与 exporter 兼容 -2. legacy import / export 不回归 -3. 升级过程不要求用户手动迁移 - -## 2. 关键设计决策 - -### 2.1 保留头表,拆出热字段 - -`deepchat_messages` 继续作为消息头表,保留: - -- `id` -- `session_id` -- `order_seq` -- `role` -- `status` -- `is_context_edge` -- `created_at` -- `updated_at` - -同时新增结构化表承载热字段: - -- `deepchat_user_messages` -- `deepchat_user_message_files` -- `deepchat_user_message_links` -- `deepchat_assistant_blocks` -- `new_session_active_skills` -- `new_session_disabled_agent_tools` - -`deepchat_messages.content` 与 `new_sessions.active_skills` / `disabled_agent_tools` 保留为回退源和兼容字段,但不再作为主链路读写来源。 - -### 2.2 repository 边界重组 JSON - -renderer 与 exporter 仍消费 `ChatMessageRecord.content` 的 JSON 字符串形式,因此结构化内容只在 -`DeepChatMessageStore` 边界按需重组。 - -这保证: - -1. 新持久化模型不向 renderer 扩散 -2. 旧视图与导出逻辑无需整体重写 - -### 2.3 恢复链路分页化 - -- `sessions.restore(sessionId, limit?)` 返回最新一页 -- `sessions.listMessagesPage()` 继续向更老消息翻页 -- renderer `messageStore.loadMessages()` 只请求第一页 -- `ChatPage.vue` 在接近顶部时触发历史追加,并保持滚动锚点稳定 - -### 2.4 搜索索引与降级 - -搜索索引使用两层表: - -1. `deepchat_search_documents` -2. `deepchat_search_documents_fts` - -查询顺序: - -1. 先执行 FTS5 -2. FTS 不可用或执行失败时降级到 `LIKE` - -索引同步点: - -1. session create / rename / delete -2. message create / edit / delete / clear / fork / import -3. assistant message finalize / error - -### 2.5 streaming 改成 block 级增量写 - -assistant streaming 期间: - -1. `deepchat_assistant_blocks` 增量 replace -2. `deepchat_messages.status` 更新为 `pending` -3. 不再重复重写 `deepchat_messages.content` - -最终态: - -1. 进入 `sent/error` -2. 再把完整 blocks 序列化回 `deepchat_messages.content` -3. 同步搜索文档 - -### 2.6 migration + background backfill - -schema migration 在启动时只做表创建与版本升级,不阻塞 UI。 - -后台 backfill 做三件事: - -1. 从 `deepchat_messages.content` 拆 user / assistant 结构化数据 -2. 从 `new_sessions` JSON 列拆 skills / disabled tools -3. 初始化搜索文档与 FTS 索引 - -backfill 需要: - -1. 可重入 -2. 记录级 fallback 安全 -3. 与 legacy import 共用结构化写入 helper - -## 3. 公开接口变化 - -### 3.1 Shared types - -- `MessagePageCursor` -- `ChatMessagePageResult` -- `MessageStartResult` - -### 3.2 Typed routes - -- 扩展 `sessions.restore` -- 新增 `sessions.listMessagesPage` - -### 3.3 Internal ports - -- `MessageRepository.listPageBySession(...)` -- `IAgentImplementation.listMessagesPage(...)` -- `IAgentImplementation.processMessage(...) => { requestId, messageId }` - -## 4. 兼容性策略 - -### 4.1 Legacy data - -- 不删除 `conversations/messages` -- 不删除旧 `chat.db` import/export 兼容层 -- `LegacyChatImportService` 导入后立即写结构化热路径 - -### 4.2 Read fallback - -如果某条消息尚未完成结构化回填: - -1. user 消息回退到 `deepchat_messages.content` -2. assistant blocks 回退到 `deepchat_messages.content` -3. session skills / disabled tools 回退到 `new_sessions` JSON 列 - -## 5. 测试策略 - -### 5.1 Main - -- migration / backfill 幂等 -- messageStore 结构化读写与 fallback -- session restore / page cursor / sendMessage 返回值 -- searchHistory 的 FTS / LIKE 双路径 - -### 5.2 Renderer - -- 首屏只恢复一页 -- 向上翻页前插不跳动 -- streaming + optimistic message + pagination 并存 - -### 5.3 Smoke - -- 创建会话 -- 重开大会话 -- 顶部加载历史 -- 历史搜索 -- 编辑 / 重试 / 导出 -- 升级后重启 - -## 6. 风险与缓解 - -### 风险 1:backfill 半途被中断 - -缓解: - -1. 读路径保留记录级 fallback -2. 启动后 hook 可重复执行 - -### 风险 2:FTS5 在当前 SQLite 运行时不可用 - -缓解: - -1. 表创建失败时保留普通表 -2. 查询自动降级到 `LIKE` - -### 风险 3:renderer 滚动位置抖动 - -缓解: - -1. 顶部翻页前记录旧 scrollHeight 与 scrollTop -2. 消息前插后通过差值恢复锚点 diff --git a/docs/specs/sqlite-mainline-normalization/spec.md b/docs/specs/sqlite-mainline-normalization/spec.md deleted file mode 100644 index eec0b238a..000000000 --- a/docs/specs/sqlite-mainline-normalization/spec.md +++ /dev/null @@ -1,123 +0,0 @@ -# `agent.db` 主链路 SQLite 结构化收敛规格 - -## 概述 - -本规格覆盖 `agent.db` 新主链路中的 `new_sessions` 与 `deepchat_*` 数据域,目标是把仍然依赖 -JSON blob 的热路径收束为结构化 SQLite 表,同时保持 legacy `conversations/messages` 与旧 -`chat.db` 导入导出兼容层可继续工作。 - -这轮改造优先解决三个慢点: - -1. 会话恢复时一次性拉全量消息 -2. 历史搜索依赖消息 JSON 扫描 -3. assistant streaming 期间频繁整条重写 `deepchat_messages.content` - -## 范围 - -### In Scope - -- `sessions.restore` 改为“恢复最近一页消息” -- 新增 `sessions.listMessagesPage` -- 新增主链路结构化表: - - `deepchat_user_messages` - - `deepchat_user_message_files` - - `deepchat_user_message_links` - - `deepchat_assistant_blocks` - - `new_session_active_skills` - - `new_session_disabled_agent_tools` - - `deepchat_search_documents` - - `deepchat_search_documents_fts` -- renderer 聊天页改为首屏分页恢复 + 顶部继续加载历史 -- 自动 migration + 可重入 backfill -- `LegacyChatImportService` 直接写入新结构化热路径 - -### Out of Scope - -- 退役 legacy `conversations/messages` -- 物理删除 `deepchat_messages.content` -- 物理删除 `new_sessions.active_skills` / `disabled_agent_tools` -- 把 `metadata`、`subagent_meta_json`、`deepchat_pending_inputs.payload_json` 也拆成结构化表 - -## 用户故事 - -### US-1:重开大线程更快 - -作为用户,我希望重新打开历史很长的会话时,应用只恢复我当前最需要的一页消息,而不是等待全量消息回放完成。 - -### US-2:历史搜索更稳定 - -作为用户,我希望搜索历史记录时结果能更快出现,并且在 SQLite FTS 不可用时仍能自动退回普通查询。 - -### US-3:流式回答不再频繁重写整条消息 - -作为系统维护者,我希望 assistant streaming 只增量写 block 级数据,减少热路径上的整条 JSON 重写。 - -### US-4:升级不打断现有数据 - -作为已有用户,我希望升级后无需手动迁移;旧数据会被自动兼容读取并在后台逐步回填。 - -## 功能需求 - -### A. 分页恢复 - -- [x] `sessions.restore` 输入支持可选 `limit` -- [x] `sessions.restore` 输出包含 `messages`、`nextCursor`、`hasMore` -- [x] 默认仅恢复最近 `100` 条消息 -- [x] 新增 `sessions.listMessagesPage({ sessionId, cursor, limit })` -- [x] cursor 固定为 `{ orderSeq, id }` -- [x] 只支持向更老消息反向翻页 - -### B. 结构化消息存储 - -- [x] user message 文本、文件、链接拆到独立表 -- [x] assistant blocks 拆到 `deepchat_assistant_blocks` -- [x] active skills / disabled agent tools 拆到独立表 -- [x] repository 读路径优先读结构化表,缺行时按记录级别回退旧 JSON - -### C. 搜索索引 - -- [x] 新增普通搜索文档表与 FTS5 虚表 -- [x] 会话只索引标题 -- [x] 用户消息只索引可见文本 -- [x] 助手消息只索引可见内容、错误文本与可见 plan 文本 -- [x] 不索引 tool call 原始 JSON、附件元数据、未稳定的 streaming 临时块 -- [x] 查询顺序固定为 FTS5 优先、失败后回退 `LIKE` - -### D. 流式写入 - -- [x] streaming 期间只增量 upsert `deepchat_assistant_blocks` -- [x] 最终进入 `sent/error` 时才写回 `deepchat_messages.content` -- [x] tool interaction、retry、edit、fork、delete 统一经过结构化读写路径 - -### E. 自动迁移与回填 - -- [x] schema migration 自动创建新表 -- [x] 启动后后台执行一次性、可重入 backfill -- [x] backfill 覆盖消息结构化拆分、session skills/tools 拆分与搜索索引初始化 -- [x] backfill 不阻塞应用启动 - -## 验收标准 - -- [x] 恢复大线程时首屏只加载最近一页消息 -- [x] 滚动到顶部可以继续加载更老消息 -- [x] `chat.sendMessage` 不再通过全量消息回查 `requestId/messageId` -- [x] FTS5 可用时优先命中全文索引,不可用时自动回退 `LIKE` -- [x] old rows 未完成回填时,新读路径仍能通过 JSON fallback 正常工作 -- [x] legacy import 新写入数据会同步进入结构化热路径 - -## 约束 - -1. 保持 typed route / typed client 体系,不回退到 legacy transport。 -2. renderer 继续复用 `ChatMessageRecord.content` 现有 JSON 形态;结构化重组只发生在 repository 边界。 -3. migration 必须幂等,backfill 必须可重入。 -4. 只改新主链路,不动 legacy `conversations/messages` 的兼容职责。 - -## 非目标 - -1. 本期不做跨页双向游标。 -2. 本期不引入新的 UI 搜索入口。 -3. 本期不做旧 JSON 列物理清理。 - -## 开放问题 - -无。 diff --git a/docs/specs/sqlite-mainline-normalization/tasks.md b/docs/specs/sqlite-mainline-normalization/tasks.md deleted file mode 100644 index 134052ed2..000000000 --- a/docs/specs/sqlite-mainline-normalization/tasks.md +++ /dev/null @@ -1,80 +0,0 @@ -# `agent.db` 主链路 SQLite 结构化收敛任务清单 - -## T0 规格 - -- [x] 新增 `docs/specs/sqlite-mainline-normalization/spec.md` -- [x] 新增 `docs/specs/sqlite-mainline-normalization/plan.md` -- [x] 新增 `docs/specs/sqlite-mainline-normalization/tasks.md` - -## T1 共享契约与 typed routes - -- [x] 新增 `MessagePageCursor` -- [x] 新增 `ChatMessagePageResult` -- [x] 新增 `MessageStartResult` -- [x] 扩展 `sessions.restore` -- [x] 新增 `sessions.listMessagesPage` -- [x] 更新 `SessionClient` - -## T2 主链路分页恢复 - -- [x] `SessionService.restoreSession()` 改为最近一页恢复 -- [x] `AgentSessionPresenter` / `AgentRuntimePresenter` 增加分页消息读取 -- [x] `MessageRepository` 增加 `listPageBySession()` -- [x] renderer `messageStore.loadMessages()` 只拉第一页 -- [x] `ChatPage.vue` 顶部触发继续加载历史并保持滚动锚点 - -## T3 结构化 SQLite 表 - -- [x] 新增 `deepchat_user_messages` -- [x] 新增 `deepchat_user_message_files` -- [x] 新增 `deepchat_user_message_links` -- [x] 新增 `deepchat_assistant_blocks` -- [x] 新增 `new_session_active_skills` -- [x] 新增 `new_session_disabled_agent_tools` -- [x] 将新表接入 `sqlitePresenter` 与 `schemaCatalog` - -## T4 热路径读写收口 - -- [x] user message 写入同步落到结构化表 -- [x] assistant streaming 改为 block 级增量写 -- [x] finalize / error 时写回稳定 JSON + 搜索索引 -- [x] repository 读路径优先结构化,缺行回退 JSON -- [x] clone / delete / recover / update content 同步处理结构化表 - -## T5 搜索索引 - -- [x] 新增 `deepchat_search_documents` -- [x] 新增 `deepchat_search_documents_fts` -- [x] 接入 session create / rename / delete 同步 -- [x] 接入 message create / edit / delete / fork / import 同步 -- [x] `searchHistory()` 改为 FTS 优先、`LIKE` 回退 - -## T6 自动迁移与回填 - -- [x] 新增 migration 版本并创建新表 -- [x] 实现后台 normalization backfill -- [x] backfill session skills / disabled tools -- [x] backfill user / assistant 结构化消息 -- [x] backfill 搜索文档 -- [x] 启动后 hook 自动触发一次可重入 backfill - -## T7 兼容性 - -- [x] `LegacyChatImportService` 导入后直接补齐结构化热路径 -- [x] 保留 legacy `conversations/messages` 兼容职责 -- [x] 保留旧 JSON 列作为 fallback source - -## T8 测试与验证 - -- [x] 更新 renderer `ChatPage` 分页相关测试 -- [x] 更新 main `messageStore` 结构化读写测试 -- [x] 更新 `agentSessionPresenter` 搜索与 send/createSession 测试 -- [x] 运行相关 main / renderer vitest 用例 -- [ ] 运行全量 `pnpm test` - -## T9 质量检查 - -- [ ] `pnpm run format` -- [ ] `pnpm run i18n` -- [ ] `pnpm run lint` -- [x] `pnpm run typecheck`