diff --git a/.agents/skills/add-provider/SKILL.md b/.agents/skills/add-provider/SKILL.md index 9b6580e9c..88430431c 100644 --- a/.agents/skills/add-provider/SKILL.md +++ b/.agents/skills/add-provider/SKILL.md @@ -78,8 +78,8 @@ Typical files: ## Workflow -1. Read `docs/features/provider-runtime/spec.md`, `plan.md`, and `tasks.md` when the provider work - touches the provider runtime scope. +1. Read `docs/features/provider-runtime/spec.md` when the provider work touches the provider runtime + scope. Also read `plan.md` and `tasks.md` if they exist for an active provider-runtime goal. 2. Inspect the current provider files before editing: - `src/main/presenter/configPresenter/providers.ts` - `src/main/presenter/configPresenter/providerId.ts` @@ -90,7 +90,7 @@ Typical files: 3. Classify the request into one supported path. 4. Add the smallest explicit source changes for that path. 5. Add or update tests that prove provider creation, auth handling, and model discovery behavior. -6. Update the active SDD `tasks.md` entries as the work lands. +6. Update the active SDD `tasks.md` entries as the work lands, when an active tasks file exists. 7. Run: ```bash diff --git a/.agents/skills/deepchat-sdd-cleanup/SKILL.md b/.agents/skills/deepchat-sdd-cleanup/SKILL.md new file mode 100644 index 000000000..ab62556e0 --- /dev/null +++ b/.agents/skills/deepchat-sdd-cleanup/SKILL.md @@ -0,0 +1,60 @@ +--- +name: deepchat-sdd-cleanup +description: Use only when a developer explicitly asks to clean, prune, tidy, or organize DeepChat SDD documentation after implementation and validation. Scans docs/features, docs/issues, and docs/architecture; prefers multi-agent review when available; removes completed issue docs tied to closed GitHub issues, drops stale plan/tasks files from completed feature or architecture goals, and deletes obsolete feature or architecture docs. +--- + +# DeepChat SDD Cleanup + +## Rule + +Run this skill only when the developer explicitly asks for SDD cleanup, documentation tidying, pruning, +or removal of completed/stale SDD files. Do not run it as an automatic final step of ordinary +feature, bug, architecture, or release work. + +## Workflow + +1. Inspect `docs/spec-driven-dev.md`, `docs/README.md`, and `git status`. +2. Inventory `docs/features`, `docs/issues`, and `docs/architecture` with `find` or `rg`. +3. Prefer parallel sub-agent review when available: + - one pass for `docs/features` + - one pass for `docs/issues` + - one pass for `docs/architecture` + - optional verifier pass over proposed deletes +4. Apply only changes with clear evidence. Keep a concise keep/delete/update list for handoff. +5. Validate references after edits. + +## Cleanup Rules + +- Completed feature or architecture goal: delete `plan.md` and `tasks.md`; keep `spec.md` only when + it still defines a maintained contract, regression guard, platform policy, or architecture + decision. +- Completed issue goal: delete the issue folder when a linked GitHub issue is closed or the local + code and tests prove the bug no longer exists. +- Removed feature: delete its folder when the product/code path is gone and the spec has no reusable + decision record. +- Obsolete architecture: delete its folder when the module was fully replaced and the doc no longer + describes a maintained boundary; otherwise update the spec. +- Historical feature spec affected by an architecture refactor: update the retained spec instead of + leaving contradictory docs. + +## GitHub Checks + +Use `gh` only when it is installed and authenticated. For linked issue docs, verify closure with +`gh issue view --json state,url,title` when possible. If `gh` is unavailable, do not delete +solely because a GitHub link looks old. + +## Never Delete + +- Active work with unchecked tasks. +- Any document containing unresolved `[NEEDS CLARIFICATION]`. +- A document referenced by `docs/README.md`, `docs/ARCHITECTURE.md`, `docs/FLOWS.md`, or AGENTS + instructions unless the reference is updated in the same change. +- Runtime baselines or machine-read files unless the cleanup request explicitly covers them. + +## Validation + +- Run `rg -n "plan.md|tasks.md|docs/archives|NEEDS CLARIFICATION" docs AGENTS.md .agents/skills` + and inspect any stale policy references. +- Run `git status --short`. +- For Markdown-only cleanup, formatting/lint commands are optional unless repository instructions or + touched generated files require them. diff --git a/.agents/skills/deepchat-sdd-cleanup/agents/openai.yaml b/.agents/skills/deepchat-sdd-cleanup/agents/openai.yaml new file mode 100644 index 000000000..bcb5b0e7d --- /dev/null +++ b/.agents/skills/deepchat-sdd-cleanup/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "DeepChat SDD Cleanup" + short_description: "Prune stale DeepChat SDD documentation" + default_prompt: "Use $deepchat-sdd-cleanup only when the developer explicitly asks to clean, prune, tidy, or organize DeepChat SDD docs." diff --git a/.agents/skills/deepchat-sdd/SKILL.md b/.agents/skills/deepchat-sdd/SKILL.md index 3b512074c..d9888e8e7 100644 --- a/.agents/skills/deepchat-sdd/SKILL.md +++ b/.agents/skills/deepchat-sdd/SKILL.md @@ -1,6 +1,6 @@ --- name: deepchat-sdd -description: Use for any DeepChat code, configuration, documentation, feature, issue fix, refactor, or architecture change before implementation. This skill enforces the project SDD workflow: classify the goal, create or update spec.md, plan.md, and tasks.md under docs/features, docs/issues, or docs/architecture, resolve NEEDS CLARIFICATION items, then implement and validate. +description: Use before DeepChat code, configuration, documentation, test, build, feature, issue, refactor, or architecture changes. Classify work into feature SDD, small-bug issue spec, or architecture SDD; optionally sync feature and bug work to GitHub issues with [feature] or [bug] labels when local gh is usable; keep broad documentation cleanup for the separate deepchat-sdd-cleanup skill. --- # DeepChat SDD @@ -14,40 +14,68 @@ Use this skill before changing DeepChat source code, configuration, tests, docs, Create one kebab-case folder per goal: - New capability, user-visible behavior, integration, or tool: `docs/features//` -- Bug, regression, failing test, CI failure, reliability problem, or prompt/runtime issue: `docs/issues//` -- Refactor, migration, dependency boundary, shared contract, runtime architecture, or cross-module design: `docs/architecture//` +- Small bug, regression, failing test, CI failure, reliability problem, or prompt/runtime issue: + `docs/issues//` +- Refactor, migration, dependency boundary, shared contract, runtime architecture, or cross-module + design: `docs/architecture//` If one request contains multiple independent goals, split them into separate folders. Keep current architecture reference docs such as `docs/architecture/agent-system.md` in place; use subfolders for new architecture targets. +Treat a bug as small only when the failure is narrow, the owner module is clear, and the fix does not +introduce a new user-visible capability, data migration, public contract, or cross-module redesign. +If it does, classify the work as feature or architecture instead. + ## Required Artifacts -Every active goal folder must contain: +Feature and architecture goals use the full SDD set: - `spec.md`: user need, goal, acceptance criteria, constraints, non-goals, open questions - `plan.md`: implementation approach, affected interfaces, data flow, compatibility, test strategy - `tasks.md`: ordered tasks that can map to commits or review slices -Resolve every `[NEEDS CLARIFICATION]` marker before implementation. If a requested change is tiny, keep the files short and concrete. +Small bug goals use one file only: + +- `spec.md`: issue description, impact, root cause or suspected location, fix plan, task checklist, + validation, and linked GitHub issue if one exists + +Resolve every `[NEEDS CLARIFICATION]` marker before implementation. If a requested change is tiny, +keep the artifact short and concrete. + +## GitHub Issue Sync + +For feature and small bug goals only, sync to GitHub when local `gh` is installed and authenticated: + +- Feature issues use the `[feature]` label. +- Bug issues use the `[bug]` label. +- Create the label first if it is missing and `gh` has permission. +- Record the issue URL or number in the SDD artifact. +- If `gh` is unavailable or unauthorized, continue local-only and note that no GitHub issue was + created. + +When creating a PR for linked work, include `Closes #NNN` in the PR body so GitHub closes the issue +automatically after merge. ## Workflow 1. Inspect the current code and docs first. 2. Pick the target folder from the classification rules. -3. Create or update `spec.md`, `plan.md`, and `tasks.md`. -4. Keep the implementation aligned with existing DeepChat patterns: +3. Create or update the required artifact set for that classification. +4. Sync a GitHub issue for feature or small bug work when `gh` is usable. +5. Keep the implementation aligned with existing DeepChat patterns: - main process Presenter boundaries - typed `shared/contracts/*` - renderer `api/*Client` - Vue 3 Composition API and i18n for UI strings -5. Implement the change after the SDD artifacts are complete. -6. Update `tasks.md` as work lands. -7. Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. -8. After implementation is accepted and validation passes, delete `plan.md` and `tasks.md` for - that goal; keep `spec.md` as the durable contract. +6. For architecture work that changes or replaces a historical feature, update that feature's + retained `spec.md` if it is still a maintained contract. +7. Implement the change after the SDD artifacts are complete. +8. Update `tasks.md` or the issue spec checklist as work lands. +9. Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` before handoff. ## Documentation Hygiene -- Move completed or stale SDD target folders to `docs/archives//`. -- Add an archive note when a document references historical code paths. -- Delete documents that only describe removed code and have no reusable decision record. -- Update `docs/README.md` when a moved document remains part of the navigation surface. +- Do not perform broad SDD cleanup during ordinary feature, bug, or architecture work. +- Use the separate `deepchat-sdd-cleanup` skill only when the developer explicitly asks to clean or + organize SDD documentation. +- During the current goal, update directly affected historical specs when they remain active + contracts. diff --git a/.agents/skills/deepchat-sdd/agents/openai.yaml b/.agents/skills/deepchat-sdd/agents/openai.yaml index 17e89255f..4fc5dd28d 100644 --- a/.agents/skills/deepchat-sdd/agents/openai.yaml +++ b/.agents/skills/deepchat-sdd/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "DeepChat SDD" - short_description: "Prepare SDD specs before DeepChat changes" - default_prompt: "Use $deepchat-sdd before implementing DeepChat changes to classify the goal and prepare spec, plan, and tasks artifacts." + short_description: "Classify DeepChat changes before implementation" + default_prompt: "Use $deepchat-sdd before implementing DeepChat changes to choose the right feature, issue, or architecture artifact workflow." diff --git a/AGENTS.md b/AGENTS.md index e74fa3383..25ad2b96c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,12 +54,14 @@ Follow the SDD methodology before changing code, tests, configuration, documenta Pure release metadata work does not require SDD. Version bumps, `CHANGELOG.md` updates, release branch management, tags, and release PR preparation should follow [docs/release-flow.md](docs/release-flow.md) without creating `docs/features/*release*` folders. -Create one kebab-case folder per goal and keep `spec.md`, `plan.md`, and `tasks.md` together: +Create one kebab-case folder per goal and use the artifact set that matches the work: -- `docs/features//` for new features, user-visible capabilities, integrations, and tools. -- `docs/issues//` for bug fixes, regressions, failing tests, CI failures, reliability issues, and prompt/runtime problems. -- `docs/architecture//` for refactors, migrations, dependency boundaries, shared contracts, runtime architecture, and cross-module design. +- `docs/features//` for new features, user-visible capabilities, integrations, and tools; keep `spec.md`, `plan.md`, and `tasks.md`. +- `docs/issues//` for small bug fixes, regressions, failing tests, CI failures, reliability issues, and prompt/runtime problems; keep one `spec.md` containing issue details, location/root cause, fix plan, task checklist, validation, and linked GitHub issue when available. +- `docs/architecture//` for refactors, migrations, dependency boundaries, shared contracts, runtime architecture, and cross-module design; keep `spec.md`, `plan.md`, and `tasks.md`, and update affected historical feature specs when they remain maintained contracts. -Resolve every `[NEEDS CLARIFICATION]` item before implementation. Move completed or stale goal folders to `docs/archives//`; delete documents that only describe removed code and have no reusable decision record. +For feature and small bug work, create or link a GitHub issue with `[feature]` or `[bug]` when local `gh` is installed and authenticated. PR bodies for linked work must include `Closes #NNN`. + +Resolve every `[NEEDS CLARIFICATION]` item before implementation. Run SDD cleanup only when the developer explicitly asks for it; use the dedicated cleanup skill to remove completed issue docs, stale plan/task files, and obsolete feature or architecture docs. Core principles: specification-first, architectural consistency, minimal complexity, compatibility/migration awareness. diff --git a/docs/README.md b/docs/README.md index f17225e37..95ce6659f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,8 @@ # DeepChat 文档索引 -本文档反映 `2026-06-25` 的当前代码结构。历史 SDD 已清理为“活跃目标保留三件套、 -已落地目标只保留 durable spec”的模型:`plan.md` / `tasks.md` 只服务当前执行,已完成目标 -只在 `spec.md` 保留仍有维护价值的契约和回归语义。 +本文档反映 `2026-07-05` 的当前代码结构。SDD 已按目标类型拆分:feature 和 +architecture 使用三件套,small bug 使用单个 issue `spec.md`。文档清理只在开发者明确触发 +`deepchat-sdd-cleanup` 时执行。 当前 renderer-main 默认路径是 typed client / typed event: @@ -33,7 +33,7 @@ shared contracts 进入;少数仍需要 raw IPC 的能力只能封装在明确 | [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 目录规则、保留期限与清理规则 | +| [spec-driven-dev.md](./spec-driven-dev.md) | SDD 目录规则、GitHub 同步与清理入口 | ## 仍有运行时用途的基线 @@ -60,7 +60,7 @@ docs/ ├── features/ │ └── / ├── issues/ -│ └── / +│ └── / ├── guides/ │ ├── getting-started.md │ ├── code-navigation.md @@ -70,13 +70,15 @@ docs/ ## SDD 保留规则 -- `docs/features/**`、`docs/issues/**`、`docs/architecture/**` 下的 active goal folder 保留 - `spec.md`、`plan.md`、`tasks.md`。 -- 已实现能力只保留仍有维护价值的 `spec.md`;删除对应 `plan.md` / `tasks.md`。 +- `docs/features/**` 和 `docs/architecture/**` 下的 active goal folder 保留 `spec.md`、 + `plan.md`、`tasks.md`。 +- `docs/issues/**` 下的小 bug goal 只保留一个 `spec.md`,内容包含 issue 描述、定位、 + 修复计划、任务清单、验证方式和 GitHub issue 链接(如有)。 +- feature / architecture 的已实现能力只保留仍有维护价值的 `spec.md`;删除对应 + `plan.md` / `tasks.md`。 - 已实现能力的当前维护事实也要并入 `README.md`、`ARCHITECTURE.md`、`FLOWS.md` 或对应 guide。 -- bug 修复类 issue SDD 超过两周即清理;按当前日期 `2026-06-25`,本次清理 cutoff 为 - `2026-06-11` 之前。 -- 过期、未开工、只描述旧实现或旧分支的 SDD 直接删除。 +- 已修复 issue,尤其是关联 GitHub issue 且已关闭的,可以在手动 SDD cleanup 时删除。 +- 过期、未开工、只描述旧实现或旧分支的 SDD,在手动 SDD cleanup 时删除。 ## 阅读建议 diff --git a/docs/architecture/agent-fff-node-api-search/spec.md b/docs/architecture/agent-fff-node-api-search/spec.md deleted file mode 100644 index eb2317b19..000000000 --- a/docs/architecture/agent-fff-node-api-search/spec.md +++ /dev/null @@ -1,130 +0,0 @@ -# Agent FFF Node API Search Spec - -## Goal - -Replace DeepChat agent/runtime file search with direct Node.js calls to `@ff-labs/fff-node`. -The implementation is FFF-only: DeepChat must not use bundled ripgrep as a fallback, must not -install bundled ripgrep, and must not inject bundled ripgrep into command execution environments. - -## Scope - -- Add DeepChat tool-layer wrappers: - - `findFiles(query: string, options?: object)` returns `Array<{ path, score }>` - - `grep(query: string, pathScope?: string[], contextLines?: number)` returns - `Array<{ path, lineNumber, snippet, score }>` -- Expose model tools: - - `glob` - - `grep` -- Update prompts so agent search order is `glob -> grep -> read`. -- Remove model-facing and runtime-owned ripgrep search paths: - - no `rg` fallback adapter - - no `RuntimeHelper` ripgrep discovery - - no bundled ripgrep PATH prepending - - no `replaceWithRuntimeCommand('rg', ...)` mapping - - no `tiny-runtime-injector --type ripgrep` install step -- Move workspace file picker search off ripgrep by using FFF glob search. -- Keep FFF native dependencies package-safe for macOS by unpacking `fff-node`, platform FFF - libraries, `ffi-rs`, and platform `ffi-rs` native modules from ASAR so Electron's existing - codesign/notarization flow can sign them as real files. -- Copy the target `@ff-labs/fff-bin-*` package during `afterPack` when pnpm/electron-builder does - not copy the transitive optional package automatically. - -## Non-Goals - -- Blocking a user from manually typing an `rg` command in a shell. -- Removing unrelated content that merely contains the letters `rg`. -- Replacing unrelated shell tools such as Node, UV, or RTK runtime injection. - -## Tool Schema - -### `glob` - -Input: - -```json -{ - "query": "string", - "options": { - "pathScope": ["string"], - "maxResults": 50, - "currentFile": "string" - } -} -``` - -Output: - -```json -[ - { - "path": "src/main/example.ts", - "score": 123 - } -] -``` - -### `grep` - -Input: - -```json -{ - "query": "string", - "pathScope": ["src/main"], - "contextLines": 2, - "maxResults": 50, - "mode": "plain | regex | fuzzy" -} -``` - -Output: - -```json -[ - { - "path": "src/main/example.ts", - "lineNumber": 42, - "snippet": "const value = needle", - "score": 123 - } -] -``` - -## Runtime Behavior - -- `FffSearchService` owns cached `FileFinder` instances per workspace root. -- The service waits for FFF's initial scan and supports `AbortSignal` while waiting/searching. -- `findFiles` uses `FileFinder.fileSearch`. -- `grep` uses `FileFinder.grep` with smart case, auto-selects regex mode for regex-like queries, - and accepts explicit `plain`, `regex`, or `fuzzy` mode. -- `grep` hydrates snippets from the matched file and line number so returned JSON includes full - context lines instead of truncated native `lineContent` when the file is still readable. -- `globFiles` uses `FileFinder.glob` for workspace file picker use cases. -- FFF unavailable errors are returned as tool errors. They are not converted to shell commands. -- Tool metadata reports only `source: "fff"`. -- Packaged apps load FFF from `app.asar.unpacked`, not from virtual `app.asar` paths. - -## Prompt Requirements - -- Prompts must tell the model to search with `glob` first, then `grep`, then `read`. -- Prompts must forbid shell search commands for code/file search. -- Prompts must not recommend `rg`, shell `grep`, `find`, `fd`, or `ls` for search workflows. - -## Acceptance Criteria - -- Agent tool definitions include `glob` and `grep`. -- Agent search tool outputs are parseable JSON arrays with stable fields. -- Legacy skill/tool name mapping routes previous file-search aliases to FFF tools. -- Legacy persisted disabled-tool entries for retired default search tools are cleaned so old - `grep` settings do not hide the new FFF-backed `grep` tool. -- `RuntimeHelper` no longer discovers ripgrep, prepends ripgrep to PATH, or maps `rg`. -- Runtime installer scripts no longer download bundled ripgrep. -- Workspace file search uses FFF glob search instead of `RipgrepSearcher`. -- Codebase contains no `FffRipgrepFallback`, `runRipgrepSearch`, or bundled ripgrep runtime path. -- macOS package configuration explicitly unpacks `fff-node`, `@ff-labs/fff-bin-*`, `ffi-rs`, - and `@yuuang/ffi-rs-*` so `.node` and `.dylib` files are available to the existing signing - flow and runtime loader. -- `afterPack` ensures the target platform FFF native library package exists beside `fff-node` in - `app.asar.unpacked/node_modules`. -- Tests cover FFF JSON shape, tool manager integration, abort handling, workspace glob search, and - prompt/tool mapping. diff --git a/docs/architecture/agent-plan-task-refactor/spec.md b/docs/architecture/agent-plan-task-refactor/spec.md index 937082b16..2f4675798 100644 --- a/docs/architecture/agent-plan-task-refactor/spec.md +++ b/docs/architecture/agent-plan-task-refactor/spec.md @@ -114,7 +114,7 @@ threshold): ### R1 — One plan model, persisted + rehydrated (U2, U3; D1) - Each plan update is persisted as a `type:'plan'` block on the assistant message (single block per - turn — see plan.md AD1 for the upsert identity). The live float rehydrates from the persisted plan + turn, upserted by the assistant message id). The live float rehydrates from the persisted plan on session load / reopen. - AC1: After reload, reopening a conversation that ran a plan shows its last plan state (inline block always; float overlay optional). @@ -127,8 +127,8 @@ threshold): raised as an exception (the early-return catch branch)**, tool terminal error, context-window error, no-model-response, a non-abort uncaught exception, interrupted-session recovery, or `MAX_TOOL_CALLS` exhaustion — the agent runtime stamps the persisted `type:'plan'` block with a - terminal marker (`terminalReason: 'aborted' | 'max_steps' | 'error'`, additive — see C3 / plan.md - AD6) and emits a final `chat.plan.updated`. Both the live float and the reloaded inline block then + terminal marker (`terminalReason: 'aborted' | 'max_steps' | 'error'`, additive per C3) and emits a + final `chat.plan.updated`. Both the live float and the reloaded inline block then render the once-`in_progress` step **without a spinner** (a static interrupted indicator). Normal completion is covered by R7 (the model marks steps complete). **No step spins after its turn ended — on every error/abort path, including the abort-exception early return and after reload.** diff --git a/docs/architecture/cua-managed-helper-validation/spec.md b/docs/architecture/cua-managed-helper-validation/spec.md deleted file mode 100644 index 2bec3da09..000000000 --- a/docs/architecture/cua-managed-helper-validation/spec.md +++ /dev/null @@ -1,52 +0,0 @@ -# CUA Managed Helper Validation - -## Context - -The current CUA macOS runtime is a sidecar helper extracted from the official plugin package under -DeepChat user data. That avoids a long-running daemon and gives the helper a DeepChat-owned bundle -identifier, but the user still has to grant permissions to an external helper location. That flow is -hard to explain and easy to distrust. - -This validation changes packaged macOS builds to prefer a DeepChat-managed helper app embedded in -the main app bundle under `Contents/Helpers`, while keeping the plugin-local helper as a fallback. - -## Goals - -- Packaged macOS DeepChat should look for the CUA helper in the app bundle first: - `DeepChat.app/Contents/Helpers/DeepChat Computer Use.app/Contents/MacOS/deepchat-cua-driver`. -- The helper must still be DeepChat-owned: - - app directory: `DeepChat Computer Use.app` - - executable: `deepchat-cua-driver` - - bundle identifier: `com.deepchat.computeruse.helper` -- The CUA MCP server must keep sidecar-only mode: - - args: `mcp --no-daemon-relaunch` - - env: `CUA_DRIVER_RS_MCP_NO_RELAUNCH=1` -- macOS dev builds, unpackaged runs, and failed managed-helper staging should still fall back to - the plugin-local helper. -- Windows and Linux runtime behavior must stay unchanged. -- Package validation and tests must guard the managed-helper detect order and the fallback path. - -## Non-Goals - -- Do not sync or build the full upstream CUA source from this repository. -- Do not restore the CUA background daemon or relaunch path. -- Do not mutate the signed `.app` bundle at runtime. -- Do not remove the plugin-local CUA runtime from packaged artifacts during this validation. -- Do not claim TCC permission inheritance is proven before a real packaged macOS build is tested. - -## Acceptance Criteria - -- `plugins/cua/plugin.json` declares a packaged macOS managed-helper runtime detect candidate before - the plugin-local candidate. -- `PluginPresenter` resolves the managed-helper candidate only for packaged macOS apps and skips it - elsewhere. -- The CUA bundle step copies the staged macOS helper to `build/managed-helpers` for electron-builder. -- `electron-builder.yml` includes the staged helper under `Contents/Helpers` for macOS packages. -- `plugin:bundle:clean` removes both bundled plugin artifacts and staged managed helpers. -- CUA package validation rejects manifests that do not prefer the managed-helper candidate on - macOS. -- Focused tests cover managed-helper resolution, manifest detect order, and package validation. - -## Open Questions - -None. diff --git a/docs/architecture/dead-code-cleanup-2026-06/spec.md b/docs/architecture/dead-code-cleanup-2026-06/spec.md deleted file mode 100644 index 7aa719c76..000000000 --- a/docs/architecture/dead-code-cleanup-2026-06/spec.md +++ /dev/null @@ -1,34 +0,0 @@ -# Dead Code Cleanup 2026-06 - Spec - -## Problem - -Two confirmed dead-code candidates remain: - -- `readDirectoryTree` in `src/main/presenter/workspacePresenter/directoryReader.ts` -- `src/renderer/src/composables/usePageCapture.example.ts` - -They are not used by production code or tests except for self-reference/commented example references. - -## Goal - -Remove confirmed dead code without changing runtime behavior. - -## Evidence - -- `rg readDirectoryTree src test docs` only finds the deprecated function and its recursive self-call. -- Production message capture imports `@/composables/message/useMessageCapture`, not `usePageCapture.example`. -- `usePageCapture.example.ts` is an example file under `src/renderer/src`, which keeps it in source search/typecheck scope. - -## Acceptance Criteria - -1. `rg readDirectoryTree src test docs` returns no live production/test references after cleanup. -2. `rg usePageCapture.example src test docs` returns no live production/test references after cleanup. -3. No runtime imports are changed except removing dead exports/files. -4. Typecheck and lint pass. - -## Non-Goals - -- Do not run a broad dead-code sweep. -- Do not refactor workspace directory loading. -- Do not rewrite page capture implementation. - diff --git a/docs/architecture/provider-db-github-source/spec.md b/docs/architecture/provider-db-github-source/spec.md deleted file mode 100644 index 40b6f2ec1..000000000 --- a/docs/architecture/provider-db-github-source/spec.md +++ /dev/null @@ -1,21 +0,0 @@ -# Provider DB GitHub Source - Spec - -## Problem - -Release builds can inject `VITE_PROVIDER_DB_URL` from `CDN_PROVIDER_DB_URL`, while local/test builds use the GitHub-hosted provider database. The CDN is being retired, so builds and runtime refreshes must stop depending on CDN injection. - -## Goal - -Use the GitHub provider database URL everywhere and remove provider DB URL override support from build workflows and runtime code. - -## Acceptance Criteria - -1. CI/release workflows no longer inject `CDN_PROVIDER_DB_URL` or `VITE_PROVIDER_DB_URL`. -2. Runtime provider DB refresh always uses the GitHub default URL. -3. Build-time provider DB fetch always uses the GitHub default URL. -4. Type declarations no longer expose `VITE_PROVIDER_DB_URL`. -5. The retired CDN hostname has no repository references. - -## Non-Goals - -- Do not remove unrelated `deepchatai.cn` official website, OAuth callback, or referer usage. diff --git a/docs/features/assistant-permission-review-mode/plan.md b/docs/features/assistant-permission-review-mode/plan.md deleted file mode 100644 index 074db7801..000000000 --- a/docs/features/assistant-permission-review-mode/plan.md +++ /dev/null @@ -1,181 +0,0 @@ -# Assistant Permission Review Mode Implementation Plan - -## 总体策略 - -不要把它做成 `default` 的小修小补。最小稳定实现是在 `full_access` 能力面前加一个 exact-action reviewer gate: - -```text -tool call / precheck / external file candidate / requiresPermission - | - v -permissionMode? - default -> existing permission overlay - full_access -> existing autoGrantPermission - auto_approve -> full-access-like action materialization + reviewer gate - | - +-- auto_allow -> execute exact reviewed action - +-- ask_user -> existing permission overlay - +-- block -> fail tool without execution -``` - -`auto_approve` 可以访问外部文件。要防的是危险的具体操作:删除/覆盖/导出敏感文件、运行高危命令、弱化安全设置、向不可信网络发送私有数据,而不是“workspace 外路径”这个符号本身。 - -## Ownership Map - -| Layer | Current owner | Planned change | -| --- | --- | --- | -| Shared type | `src/shared/types/agent-interface.d.ts` | Extend `PermissionMode` with `auto_approve` | -| Route schema | `src/shared/contracts/common.ts`, `sessions.routes.ts` | Accept and return new mode | -| Agent config schema | `src/shared/contracts/domainSchemas.ts` | Persist new mode in DeepChat agent config | -| Session persistence | `deepchat_sessions.permission_mode` | Existing text column can store new string; update TS typing/normalizers | -| Renderer menu | `src/renderer/src/components/chat/ChatStatusBar.vue` | Add third option and labels | -| Draft state | `src/renderer/src/stores/ui/draft.ts` | Preserve `auto_approve` for new sessions | -| Runtime branch | `src/main/presenter/agentRuntimePresenter/dispatch.ts` | Insert reviewer gate before full-access-like execution | -| Permission commit | `SessionPermissionPort.approvePermission()` | Reuse unchanged | -| Model call | `llmProviderPresenter.generateCompletionStandalone()` | Use isolated structured prompt | -| Tool boundary | `ToolPresenter` / agent tool permission precheck | Surface reviewable external-file actions instead of silently bypassing them | - -## Renderer Working Brief - -Target -- User-visible behavior: permission dropdown gains `Approve for me`; selecting it persists per draft/session. -- Current rendering component: `ChatStatusBar.vue`. -- Logical owner: `PermissionMode` shared contract and session runtime state. -- Route/layout/shell owner: chat status bar under the chat page shell. -- Trigger path: dropdown select -> `SessionClient.setPermissionMode()` or draft store. -- Existing similar implementation: current `default` / `full_access` dropdown. - -Context Map -- Vue owner chain: Chat page -> `ChatStatusBar` -> dropdown items. -- State source: active session via `sessions.getPermissionMode`; new session via `draftStore.permissionMode`. -- Events: dropdown `@select` calls `selectPermissionMode`. -- Styling/layout constraints: compact bottom status bar, no new panel. -- Accessibility concerns: dropdown item labels are i18n text with checkmark state. -- Electron boundary: typed route through `SessionClient`. -- Existing project patterns: Vue Composition API, shadcn dropdown, i18n. - -Diagnosis -- Root cause: permission mode is currently a binary enum; `full_access` skips user approval and enables external file access without semantic review. -- Correct ownership layer: shared `PermissionMode` plus runtime dispatch branch. -- Affected consumers: session create/update, agent transfer, subagent creation, status bar, tests. -- Constraints: `auto_approve` should inherit full-access-like reach, but not full-access-like blind execution. -- Existing pattern to reuse: current permission request blocks and `approvePermission()`. - -Decision -- Selected approach: add one enum value and one reviewer gate helper; no new permission framework. -- Files to edit: shared schemas/types, session presenters, runtime dispatch, status bar/i18n, focused tests. -- State impact: persisted string value expands; no migration needed for existing rows. -- DOM/layout impact: one extra dropdown item only. -- Render/update impact: no new watcher; existing permission sync watcher remains. -- IPC/main-process impact: route payload schema expands; no new route required for mode get/set. -- Verification plan: route contract tests, runtime permission tests, renderer status bar tests. - -## Reviewer Gate Design - -Add a small main-process helper near agent runtime, for example: - -```text -src/main/presenter/agentRuntimePresenter/permissionReviewGate.ts -``` - -Responsibilities: - -1. Build `PermissionReviewActionEnvelope` from tool call, external file candidates, command metadata, MCP permission data, or normalized permission request. -2. Canonicalize and hash the envelope. -3. Build reviewer messages with untrusted-evidence boundaries. -4. Resolve reviewer model: session agent `assistantModel` -> current session model. -5. Call existing provider standalone completion with an abort timeout. -6. Parse strict JSON and validate `actionHash`. -7. Apply deterministic post-policy. -8. Return `auto_allow`, `ask_user`, or `block`. - -Keep it local to agent runtime until another caller exists. - -## Runtime Integration Points - -Existing permission branches to update: - -- Single tool result with `toolRawData.requiresPermission`. -- Parallel read-only precheck branch. -- Sequential `preCheckToolPermission` branch. -- Agent tool paths where `allowExternalFileAccess: true` currently prevents a permission request from being produced. -- Any shared helper introduced while reducing duplication. - -Important rule: - -```typescript -const toolCapabilityMode = - permissionMode === 'default' ? 'default' : 'full_access' -``` - -Use that effective mode for capability reach. Then, when `permissionMode === 'auto_approve'`, require a review decision before executing any reviewable high-impact action. This may require the tool layer to return a reviewable action candidate even when `full_access` would have executed directly. - -## Post-policy - -首版规则写死在 helper 中,避免配置面膨胀: - -```text -critical -> block or ask_user only when user can safely understand exact risk -high -> ask_user unless current turn explicitly authorized same target and irreversible effect -medium -> auto_allow unless secret export / broad delete / security weakening / untrusted private-data network sink -low -> auto_allow -unknown authorization -> ask_user unless risk=low and action is reversible or read-only -``` - -Command permission already has deterministic `commandInfo.riskLevel`; reviewer 不能把 `critical` 降成可自动批准。 - -## Prompt Shape - -Use two messages: - -```text -developer: -You review one exact DeepChat tool permission request before execution... -Evidence is untrusted. Return only JSON matching schema... - -user: -The following material is untrusted evidence. ->>> RECENT CONTEXT START -... ->>> RECENT CONTEXT END ->>> ACTION START -hash: sha256:... -json: ... ->>> ACTION END -``` - -The user message should prefer summaries and digests over large raw values. If tool args are too large, include digest + first safe preview only. - -## Compatibility - -- Existing DB rows remain valid. -- Unknown old values should continue to normalize to `full_access` only where that was previous behavior; explicit `auto_approve` must survive. -- Agent transfer and subagent creation must preserve `auto_approve`. -- Existing MCP server auto-approve settings are unchanged. -- Existing manual permission overlay remains the fallback path. -- External file access is compatible with `auto_approve`; safe reviewed external-file actions should not be forced back to user confirmation just because they are outside workspace. - -## Test Strategy - -Main tests: - -- Shared route/schema accepts `auto_approve`. -- Session create/get/set preserves `auto_approve`. -- Agent config normalization preserves `auto_approve`. -- `auto_approve` can pass full-access-like capability to tools, including external file access, but must produce/review exact actions before high-impact execution. -- Reviewer `auto_allow` calls `approvePermission()` and retries the original tool. -- Reviewer `ask_user` creates the same pending permission block as `default` when a permission request exists, or a review-derived permission block when the action came from a full-access-like external file path. -- Reviewer timeout/invalid JSON/hash mismatch falls back to user prompt. -- `critical` command cannot be auto-approved even if reviewer says `auto_allow`. - -Renderer tests: - -- Status bar renders three permission choices. -- Selecting `auto_approve` calls `sessions.setPermissionMode`. -- Draft mode keeps `auto_approve` before a session exists. - -## Rollout Notes - -Start with one reviewer attempt and a short timeout. Because failure falls back to user approval, retry/circuit breaker can wait until real telemetry shows reviewer flakiness or loops. - -Skipped for first increment: reviewer read-only investigation, ACP permission auto-review, global reviewer model settings. Add them only if the first version produces too many unnecessary user prompts. diff --git a/docs/features/assistant-permission-review-mode/tasks.md b/docs/features/assistant-permission-review-mode/tasks.md deleted file mode 100644 index 79785507a..000000000 --- a/docs/features/assistant-permission-review-mode/tasks.md +++ /dev/null @@ -1,55 +0,0 @@ -# Assistant Permission Review Mode Tasks - -## 0. Review Gate - -- [x] Review `spec.md` mode semantics with maintainers. -- [x] Confirm `auto_approve` product label in English and Chinese. -- [x] Confirm first increment excludes ACP permission auto-review. - -## 1. Shared Contracts and Persistence - -- [x] Extend `PermissionMode` to include `auto_approve`. -- [x] Extend `PermissionModeSchema`. -- [x] Extend DeepChat agent config schema for `permissionMode`. -- [x] Update session table TypeScript method signatures for `permission_mode`. -- [x] Update every permission-mode normalizer that currently collapses non-`default` to `full_access`. -- [x] Add/adjust route contract tests for `sessions.getPermissionMode` and `sessions.setPermissionMode`. - -## 2. Renderer UI - -- [x] Add `Approve for me` / `助手代审` i18n labels. -- [x] Add third dropdown option in `ChatStatusBar.vue`. -- [x] Preserve compact status bar layout and existing ACP hiding behavior. -- [x] Add renderer tests for rendering and selecting `auto_approve`. - -## 3. Reviewer Gate - -- [x] Add local permission review gate helper under agent runtime. -- [x] Build exact action envelope from tool call context and normalized permission request. -- [x] Canonicalize envelope and compute action hash. -- [x] Build untrusted-evidence reviewer prompt. -- [x] Resolve reviewer model from agent `assistantModel`, falling back to current session model. -- [x] Call standalone completion with timeout and abort handling. -- [x] Parse strict JSON decision and validate `actionHash`. -- [x] Apply deterministic post-policy. -- [x] Return `auto_allow`, `ask_user`, or `block` without executing tools directly. - -## 4. Runtime Integration - -- [x] Ensure `auto_approve` uses full-access-like capability reach for tool precheck and execution candidates. -- [x] Ensure agent tools surface reviewable external-file action candidates before execution. -- [x] Insert reviewer gate into `requiresPermission` handling. -- [x] Insert reviewer gate into prechecked permission handling. -- [x] Reuse `approvePermission()` for approved decisions when the action still maps to an existing permission request. -- [x] Use one-shot exact-action authorization for reviewed full-access-like actions that do not map to existing permission cache. -- [x] Fall back to existing permission interaction blocks on reviewer failure or `ask_user`. -- [x] Mark hard blocks as tool failures without executing the tool. -- [x] Add focused runtime tests for allow, ask-user fallback, timeout, invalid JSON, hash mismatch, and critical-risk rejection. - -## 5. Audit and Verification - -- [x] Add redacted review decision logging. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. -- [x] Run focused main and renderer Vitest suites. diff --git a/docs/features/message-delete-confirmation/plan.md b/docs/features/message-delete-confirmation/plan.md deleted file mode 100644 index 277e56c7d..000000000 --- a/docs/features/message-delete-confirmation/plan.md +++ /dev/null @@ -1,151 +0,0 @@ -# Message Delete Confirmation Implementation Plan - -## 总体策略 - -这是 renderer 交互保护,不是数据层变更。主进程 `sessions.deleteMessage` 继续保持当前删除 API;确认状态放在 -`ChatPage.vue`,因为它已经拥有 session id、read-only 判断、删除副作用和 rehydrate 流程。 - -不要把确认状态放进每个消息行。消息列表是热路径,当前 `MessageListRow` 已经有 resize/intersection observer;给每行挂 dialog 或 watcher 是不必要的。 - -## Renderer Working Brief - -Target -- User-visible behavior: 点击消息 trash 后先弹确认,确认后删除。 -- Current rendering component: trash button lives in `MessageToolbar.vue`. -- Logical owner: `ChatPage.vue` owns deletion side effect. -- Route/layout/shell owner: chat page route. -- Trigger path: toolbar emit `delete` -> message item -> row -> list -> `ChatPage.onMessageDelete`. -- Existing similar implementation: shadcn `AlertDialog` in settings and `MessageDialog.vue`. - -Context Map -- Vue owner chain: `ChatPage` -> `MessageList` -> `MessageListRow` -> `MessageItem*` -> `MessageToolbar`. -- DOM/render chain: dialog should portal through existing AlertDialog implementation, not mount inside a row. -- State source: local `pendingDeleteMessageId` in `ChatPage`. -- Derived state: `showDeleteMessageDialog = Boolean(pendingDeleteMessageId)`. -- Events: request delete, confirm delete, cancel/close dialog. -- Side effects: `messageStore.clearStreamingState()`, `sessionClient.deleteMessage()`, `loadMessagesAndRehydrate()`. -- Styling/layout constraints: chat list uses content visibility and row measurement; avoid per-row modal DOM. -- Performance-sensitive areas: message list rows and toolbar buttons. -- Accessibility concerns: modal focus, Escape close, keyboard-reachable cancel/confirm. -- Electron boundary: existing `SessionClient.deleteMessage` route only; no new IPC. -- Existing project patterns: Vue Composition API, shadcn AlertDialog, i18n. - -Diagnosis -- Root cause: destructive action is wired directly to API call at the page owner without an interaction confirmation state. -- Correct ownership layer: `ChatPage.vue` local UI state and handler split. -- Affected consumers: user and assistant message delete emits; route delete API unchanged. -- Constraints: read-only sessions must stay blocked; no per-row dialog. -- Existing pattern to reuse: `AlertDialog` primitive and current delete/rehydrate flow. - -Decision -- Selected approach: split `onMessageDelete` into request + confirm path; add one controlled `AlertDialog` in `ChatPage`. -- Files to edit: `ChatPage.vue`, `dialog.json` locale files, focused renderer test. -- State impact: one local ref for pending message id; no Pinia/store change. -- DOM/layout impact: one portal-backed modal mounted by page, not row. -- Render/update impact: no new row props or per-row watchers. -- IPC/main-process impact: none. -- Verification plan: component test plus manual keyboard check. - -## Implementation Details - -### State - -Add local state in `ChatPage.vue`: - -```typescript -const pendingDeleteMessageId = ref(null) -const showDeleteMessageDialog = computed(() => Boolean(pendingDeleteMessageId.value)) -``` - -### Handler Split - -Keep the current delete logic in a private function: - -```text -requestMessageDelete(messageId) - -> validate read-only and non-empty id - -> pendingDeleteMessageId = messageId - -confirmMessageDelete() - -> capture pending id - -> clear pending id - -> re-check read-only/session/id - -> run existing delete flow - -cancelMessageDelete() - -> pendingDeleteMessageId = null -``` - -The existing `@delete` listener from `MessageList` should call the request handler, not the destructive flow. - -### Dialog Placement - -Place the controlled `AlertDialog` once in `ChatPage.vue`, near other page-level overlays: - -```vue - - - - {{ t('dialog.deleteMessage.title') }} - - {{ t('dialog.deleteMessage.description') }} - - - - - {{ t('dialog.cancel') }} - - - {{ t('dialog.deleteMessage.confirm') }} - - - - -``` - -Use the existing primitive imports from `@shadcn/components/ui/alert-dialog`. - -### Session Changes - -Watch `props.sessionId` or rely on existing page remount behavior. Safer minimal behavior: - -```text -watch(() => props.sessionId, () => { - pendingDeleteMessageId.value = null -}) -``` - -No store persistence is needed. - -## Tests - -Use existing `test/renderer/components/ChatPage.test.ts` style: - -- Trigger delete emit from `MessageList`; assert `sessionClient.deleteMessage` was not called. -- Confirm dialog action; assert delete was called with active session id and message id. -- Cancel dialog; assert delete was not called. -- Read-only session; assert request does not open dialog and delete is not called. - -No main-process test is needed because the route behavior is unchanged. - -## Verification - -Run after implementation: - -```text -pnpm run format -pnpm run i18n -pnpm run lint -pnpm test -- ChatPage -``` - -Manual check: - -- Trash click opens modal. -- Escape closes modal. -- Cancel closes modal. -- Confirm deletes and rehydrates. -- Dark/light theme dialog text remains readable. -- Small window keeps buttons visible. - -Skipped: undo/soft delete. Add only when users need recovery after confirmed deletion. diff --git a/docs/features/message-delete-confirmation/tasks.md b/docs/features/message-delete-confirmation/tasks.md deleted file mode 100644 index cfefa3647..000000000 --- a/docs/features/message-delete-confirmation/tasks.md +++ /dev/null @@ -1,36 +0,0 @@ -# Message Delete Confirmation Tasks - -## 0. Review Gate - -- [x] Review `spec.md` copy and destructive-action behavior. -- [x] Confirm no undo/soft-delete requirement for this increment. - -## 1. Renderer Implementation - -- [x] Add local pending-delete state in `ChatPage.vue`. -- [x] Split delete request from confirmed delete. -- [x] Add one controlled shadcn `AlertDialog` in `ChatPage.vue`. -- [x] Clear pending delete state when dialog closes. -- [x] Clear pending delete state when session id changes. -- [x] Keep existing delete API call and rehydrate flow unchanged. - -## 2. i18n - -- [x] Add `dialog.deleteMessage.title`. -- [x] Add `dialog.deleteMessage.description`. -- [x] Add `dialog.deleteMessage.confirm`. -- [x] Run i18n sync for other locale files. - -## 3. Tests - -- [x] Add renderer test: trash request opens confirm and does not delete immediately. -- [x] Add renderer test: confirm deletes selected message. -- [x] Add renderer test: cancel/Escape path does not delete. -- [x] Add renderer test: read-only session cannot open delete confirm. - -## 4. Verification - -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. -- [x] Run focused ChatPage renderer test. diff --git a/docs/features/skill-sync-directory-ux/plan.md b/docs/features/skill-sync-directory-ux/plan.md deleted file mode 100644 index 55d793dbd..000000000 --- a/docs/features/skill-sync-directory-ux/plan.md +++ /dev/null @@ -1,152 +0,0 @@ -# Skill Sync Directory UX Plan - -## Implementation Approach - -Keep the fix in the existing sync directory path. The shortest useful change is mostly renderer -state and layout in `SkillImportExportTab.vue`; route contracts can stay as-is because execute -methods already accept the selected names and recompute their own preview. - -## Affected Files - -- `src/renderer/settings/components/skills/SkillImportExportTab.vue` -- `src/renderer/settings/components/skills/SkillAgentsTab.vue` -- `src/renderer/settings/components/skills/InstallSkillToAgentDialog.vue` -- `src/renderer/settings/components/skills/SyncPromptDialog.vue` -- `src/renderer/settings/components/skills/SyncStatusCard.vue` -- `src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue` -- `src/renderer/settings/components/skills/SkillSyncDialog/ToolSelector.vue` -- `src/main/presenter/skillSyncPresenter/toolScanner.ts` -- `src/main/presenter/skillSyncPresenter/adapters/agentsAdapter.ts` -- `src/main/presenter/skillSyncPresenter/adapters/index.ts` -- `src/renderer/src/i18n/*/settings.json` -- `src/main/presenter/skillPresenter/index.ts` -- `test/renderer/components/SkillSyncSettings.test.ts` -- `test/main/presenter/skillPresenter/skillPresenter.test.ts` if the presenter fallback default is - changed -- `test/main/presenter/skillSyncPresenter/toolScanner.test.ts` -- `test/main/presenter/skillSyncPresenter/adapters/index.test.ts` -- `test/main/presenter/skillSyncPresenter/formatConverter.test.ts` -- `test/main/presenter/skillSyncPresenter/security.test.ts` -- `test/main/presenter/skillSyncPresenter/index.test.ts` - -## Renderer Flow - -Directory: - -- Replace the editable directory input and save button with a read-only setting row. -- Use the existing folder picker to choose or change the directory. -- Save immediately after choosing a folder. -- Check the saved directory with the existing project path-existence client. -- Hide the Export / Import tabs when no directory is configured or the configured directory is - missing. -- Show a warning icon and message when the saved directory path no longer exists. - -Export: - -- Initialize `includeDisabled` to `true`. -- Initialize `selectedExportNames` to an empty `Set`. -- Add export toolbar state for text filtering and selected count. -- Compute export candidates from mutable skills, disabled inclusion, and text filter. -- Add `selectVisibleExport()` and `clearExportSelection()`. -- Enable export from `selectedExportNames.size > 0`, config presence, and non-exporting state. -- Put the export candidate list in a bounded internal scroll container. -- Remove the persistent export preview panel and standalone preview button. -- `requestExportConfirmation()` calls `previewSyncDirectoryExport()` for the selected names and opens - a confirmation dialog. -- `executeExport()` runs only from the confirmation dialog. -- Clear the dialog preview whenever selection or `includeDisabled` changes. - -Import: - -- Initialize `importStrategy` to `overwrite`. -- Initialize `selectedImportNames` to an empty `Set`. -- Add import toolbar state for text filtering, optional state filtering, selected count, refresh, - `Select visible`, and `Clear selection`. -- Put the import preview list in a bounded internal scroll container. -- Watch `activeTab`; when it becomes `import`, call a cached preview refresh if config exists. -- Do not auto-select rows after automatic preview. -- `selectVisibleImport()` selects only visible rows where state is neither `invalid` nor `same`. -- Enable import from `selectedImportNames.size > 0` and non-importing state. - -## Preview Cache - -Use component-local cache: - -- Key: configured `skillsDirectory`. -- Value: latest `SkillSyncDirectoryImportPreview` and timestamp. -- TTL: short, around 2 seconds, only to dedupe repeated tab switching. -- In-flight request: reuse the same promise while a preview is already loading. -- Manual refresh: bypass cache. -- Invalidate when directory config changes, export completes, or import completes. - -This avoids a store or backend cache. If directory scans later become expensive, move caching behind -`SkillClient`; do not do that first. - -## Presenter Flow - -- Keep route input/output contracts unchanged. -- Change the internal `executeSyncDirectoryImport()` fallback from `rename` to `overwrite` so callers - that omit strategy match the UI default. -- Preserve explicit `rename`, `overwrite`, and `skip` handling. - -## Generic Agents Directory Flow - -- Register a user-level `Agents` tool with id `agents`, directory `~/.agents/skills/`, and - `*/SKILL.md` folder scanning. -- Use the existing folder-based capabilities so the Agents tab can manage links and adoptions - without a special case. -- Add a thin `AgentsAdapter` that reuses the Claude/Codex `SKILL.md` parser and serializer while - recording source tool id `agents`. -- Add the `agents` icon mapping to existing skill sync surfaces that render tool icons. -- Do not add a directory picker or custom path setting for this entry. - -## Tests - -Renderer tests should assert: - -- No directory or a missing directory hides export/import operation controls. -- Choosing a directory saves it immediately and reveals operation controls. -- Export does not select all skills on mount. -- Disabled mutable skills are visible by default. -- `Select visible` selects filtered export rows. -- `Clear selection` clears export rows. -- Export button opens a confirmation dialog populated by `previewSyncDirectoryExport()`. -- Canceling the confirmation dialog does not call `executeSyncDirectoryExport()`. -- Switching to Import triggers one automatic preview. -- Repeated quick switches reuse cache or in-flight request. -- Manual refresh calls preview again. -- Import default strategy passed to execution is `overwrite`. -- Import `Select visible` excludes `same` and `invalid`. -- Export and import list containers render as internally scrollable areas. - -Presenter tests should assert: - -- Calling `executeSyncDirectoryImport()` without `strategy` overwrites by default. - -Skill sync presenter tests should assert: - -- `EXTERNAL_TOOLS` includes the generic `Agents` tool at `~/.agents/skills/`. -- The adapter registry exposes `agents`. -- `FormatConverter` can parse and serialize the generic Agents format. - -Review follow-up tests should assert: - -- The sync directory picker cannot start another choose/save flow while `saving` is true. -- Export and import preview failures show an existing preview-error toast and clear loading state. -- New import/export strings are localized for non-English locale files. -- Skill sync icon lookups use one shared tool icon/color source. - -## Validation - -After implementation: - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- Targeted renderer test for `SkillSyncSettings.test.ts` -- Targeted presenter test only if `SkillPresenter` fallback changes - -## Compatibility - -Existing saved sync directory config remains valid. Existing sync directories keep the same layout. -Existing explicit import strategies remain honored. diff --git a/docs/features/skill-sync-directory-ux/tasks.md b/docs/features/skill-sync-directory-ux/tasks.md deleted file mode 100644 index f8c9ece42..000000000 --- a/docs/features/skill-sync-directory-ux/tasks.md +++ /dev/null @@ -1,56 +0,0 @@ -# Skill Sync Directory UX Tasks - -## Documentation - -- [x] Capture current UX issues and target behavior. -- [x] Add before/after ASCII layout for the sync directory tab. -- [x] Add before/after ASCII layout for the generic Agents directory entry. -- [x] Record non-goals around Git operations and older external-tool sync. - -## Implementation - -- [x] Replace editable directory input/save with a setting-style directory row. -- [x] Save the sync directory immediately after folder picker selection. -- [x] Check whether the configured sync directory exists. -- [x] Hide Export / Import controls when no directory is configured or the configured directory is - missing. -- [x] Show a warning icon/message when the configured directory is missing. -- [x] Update export defaults: include disabled by default, empty selection, toolbar filters. -- [x] Add export `Select visible` and `Clear selection`. -- [x] Remove the persistent export preview panel. -- [x] Open an export confirmation dialog populated by export preview. -- [x] Execute export only after confirming the dialog. -- [x] Invalidate or clear stale export confirmation preview when selection/filter inputs change. -- [x] Make the export skill list scroll internally. -- [x] Auto-refresh import preview when switching to the Import tab. -- [x] Add component-local throttled import preview cache. -- [x] Invalidate import preview cache on directory save, export completion, and import completion. -- [x] Update import defaults: empty selection and `overwrite` conflict strategy. -- [x] Add import filters, `Select visible`, and `Clear selection`. -- [x] Make the import skill list scroll internally. -- [x] Keep invalid and same import rows unselectable. -- [x] Align `SkillPresenter.executeSyncDirectoryImport()` omitted-strategy fallback with `overwrite`. -- [x] Add or update vue-i18n strings in every locale. -- [x] Update renderer tests for selection, export confirmation, auto-refresh, cache, and default - strategy. -- [x] Update presenter test for omitted import strategy fallback if implementation changes it. -- [x] Register generic `Agents` user skill directory at `~/.agents/skills/`. -- [x] Add `AgentsAdapter` and register it with the format adapter registry. -- [x] Add `agents` icon mapping to Agents, install-to-agent, status, prompt, and wizard surfaces. -- [x] Update skill sync tests for generic Agents registration and format conversion. -- [x] Disable the full sync directory picker while saving and guard duplicate save flows. -- [x] Catch export/import preview failures and show preview-error toasts. -- [x] Localize the new import/export strings in non-English locale files. -- [x] Share skill sync tool icon/color mappings across the touched UI components. -- [x] Update review-follow-up tests. - -## Verification - -- [x] Re-run `pnpm run format`. -- [x] Re-run `pnpm run i18n`. -- [x] Re-run `pnpm run lint`. -- [x] Run `pnpm run typecheck`. -- [x] Run targeted renderer tests for `SkillSyncSettings.test.ts`. -- [x] Run targeted presenter tests if the presenter fallback changes. -- [x] Run targeted skill sync tests for generic Agents registration and format conversion. -- [x] Re-run review-follow-up validation after review fixes. diff --git a/docs/features/tool-call-review-status-indicator/plan.md b/docs/features/tool-call-review-status-indicator/plan.md deleted file mode 100644 index c1ee8efea..000000000 --- a/docs/features/tool-call-review-status-indicator/plan.md +++ /dev/null @@ -1,113 +0,0 @@ -# Tool Call Review Status Indicator Plan - -## Renderer Working Brief - -Target - -- User-visible behavior: tool pills show a yellow dot while `auto_approve` model review is pending. -- Current rendering component: `MessageBlockToolCall.vue`. -- Logical owner: tool call block renderer plus agent runtime dispatch code that knows when review is - pending. -- Route/layout/shell owner: no route or shell ownership change. -- Trigger path: provider emits tool call -> agent runtime dispatch enters auto-approve review -> stream - snapshot updates tool block extra -> renderer maps review marker to yellow status. -- Existing similar implementation: `statusVariant` maps block state to status icon/ring; subagent - progress already mutates `block.extra` for transient renderer metadata. - -Context Map - -- Vue owner chain: `ChatPage` -> `MessageList` -> `MessageItemAssistant` -> - `MessageBlockToolCall`. -- DOM/render chain: one inline tool pill, status indicator first, tool name/summary after it. -- State source: `AssistantMessageBlock` snapshots from `chat.stream.updated`. -- Derived state: `MessageBlockToolCall.statusVariant`. -- Events: existing stream snapshot flushes are enough; no new event channel. -- Side effects: runtime marks the tool block as reviewing before awaiting the reviewer, then clears it - when the reviewer returns. -- Styling/layout constraints: indicator remains `0.75rem`; no label width changes. -- Performance-sensitive areas: no per-frame animation or timers. -- Accessibility concerns: do not rely only on visible color if a non-visible label can be added cheaply. -- Electron boundary: no preload or IPC contract change. -- Existing project patterns: `block.extra` for renderer-only metadata, `state.dirty` + - `scheduleRendererFlush` for stream updates. - -Diagnosis - -- Root cause: `block.status` conflates pending-before-execution with execution lifecycle and has no - auto-review substate. -- Correct ownership layer: runtime owns the review marker; renderer owns color mapping. -- Affected consumers: only tool call pills in assistant messages. -- Constraints: reviewer state must clear on approve, ask-user, block, error, and abort. -- Existing pattern to reuse: mutate matching tool block by `tool_call.id`, set `state.dirty`, flush - snapshot. - -Decision - -- Selected approach: add a transient `block.extra.autoApproveReviewStatus = 'reviewing'` marker while - the reviewer promise is pending, and teach `MessageBlockToolCall` to map that marker to a yellow - status variant. -- Files to edit: - - `src/main/presenter/agentRuntimePresenter/dispatch.ts` - - `src/renderer/src/components/chat/messageListItems.ts` - - `src/renderer/src/components/message/MessageBlockToolCall.vue` - - focused main runtime tests for review marker flush/clear - - focused renderer tests for yellow review indicator -- State impact: transient block metadata only; no store shape or DB schema change. -- DOM/layout impact: same pill, same indicator slot, yellow visual state only. -- Render/update impact: one extra stream snapshot before reviewer await and one clear after reviewer - result. -- IPC/main-process impact: none. -- Verification plan: focused dispatch tests and `MessageBlockToolCall` tests. - -## Implementation Notes - -Use the smallest marker possible: - -```ts -extra: { - autoApproveReviewStatus: 'reviewing' -} -``` - -Add tiny helpers near existing tool block mutation helpers: - -- `markToolCallReviewing(blocks, toolCallId)` -- `clearToolCallReviewing(blocks, toolCallId)` - -Runtime sequence: - -```txt -tool_call block exists -mark reviewing -flush renderer snapshot -await auto approve reviewer -clear reviewing -continue with auto_allow / ask_user / block handling -flush existing outcome snapshot -``` - -Renderer sequence: - -```txt -if extra.autoApproveReviewStatus === 'reviewing' -> statusVariant = 'reviewing' -else fall back to existing error/success/loading/neutral mapping -``` - -Keep `block.status` unchanged while reviewing. That preserves execution semantics and prevents a -reviewing tool from looking like it is already running. - -## Test Strategy - -- Main runtime: review path marks the target tool call as reviewing before reviewer promise resolves. -- Main runtime: review marker clears on auto-allow. -- Main runtime: review marker clears on ask-user fallback. -- Main runtime: review marker clears on block/error. -- Renderer: a block with `extra.autoApproveReviewStatus = 'reviewing'` renders the yellow indicator. -- Renderer: reviewing marker takes precedence over `pending` but not over terminal `error` if both are - somehow present after malformed input. - -## Compatibility - -The marker lives inside assistant block JSON. Older clients ignore unknown `extra` fields. Persisted -messages should normally not retain this marker because it is cleared before terminal persistence, but -even if a crash leaves it behind, the renderer can treat terminal `success/error` as higher priority. diff --git a/docs/features/tool-call-review-status-indicator/spec.md b/docs/features/tool-call-review-status-indicator/spec.md index 1bb66db42..8ac7ca543 100644 --- a/docs/features/tool-call-review-status-indicator/spec.md +++ b/docs/features/tool-call-review-status-indicator/spec.md @@ -1,6 +1,6 @@ # Tool Call Review Status Indicator -Status: proposed +Status: implemented Date: 2026-07-01 ## User Need diff --git a/docs/features/tool-call-review-status-indicator/tasks.md b/docs/features/tool-call-review-status-indicator/tasks.md deleted file mode 100644 index 8fb7592f9..000000000 --- a/docs/features/tool-call-review-status-indicator/tasks.md +++ /dev/null @@ -1,41 +0,0 @@ -# Tool Call Review Status Indicator Tasks - -## 0. Review Gate - -- [x] Locate tool pill status ownership. -- [x] Locate tool block lifecycle mutation points. -- [x] Confirm the status should be a transient review substate, not a new terminal block status. - -## 1. Runtime - -- [x] Add transient review marker helper for tool blocks. -- [x] Mark the exact tool call before awaiting auto-approve reviewer. -- [x] Flush renderer snapshot after marking review state. -- [x] Clear marker on auto-allow. -- [x] Clear marker on ask-user fallback. -- [x] Clear marker on block/error/abort paths. -- [x] Avoid marker changes outside `auto_approve`. - -## 2. Renderer - -- [x] Extend tool block extra type with `autoApproveReviewStatus`. -- [x] Add `reviewing` status variant in `MessageBlockToolCall.vue`. -- [x] Render reviewing as a yellow dot/ring in the existing indicator slot. -- [x] Keep neutral/running/success/error visuals unchanged. -- [x] Ensure the tool pill width and text truncation do not shift. - -## 3. Tests - -- [x] Add runtime test for marker emitted before reviewer promise resolves. -- [x] Add runtime test for marker cleared after reviewer allow. -- [x] Add runtime test for marker cleared on ask-user fallback. -- [x] Add runtime test for marker cleared on block/error. -- [x] Add renderer test for yellow review indicator. -- [x] Add renderer test for terminal status precedence over stale review marker. - -## 4. Verification - -- [x] Run focused agent runtime dispatch tests. -- [x] Run focused `MessageBlockToolCall` renderer tests. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run lint`. diff --git a/docs/issues/assistant-loading-placeholder-delay/spec.md b/docs/issues/assistant-loading-placeholder-delay/spec.md deleted file mode 100644 index 01de0adea..000000000 --- a/docs/issues/assistant-loading-placeholder-delay/spec.md +++ /dev/null @@ -1,93 +0,0 @@ -# Assistant Loading Placeholder Delay - -Status: implemented -Date: 2026-07-01 - -## User Need - -After the user submits a message, the conversation should immediately show the next assistant row in -a loading state. The user should not wait for pre-stream preparation or the first provider chunk -before seeing that DeepChat accepted the turn. - -## Original Behavior - -`ChatPage.onSubmit()` sends the payload through `chatClient.sendMessage()` and only clears the -composer after that promise resolves. On the main-process path, `chat.sendMessage` calls -`AgentRuntimePresenter.processMessage()`, which performs generation-settings loading, skill/tool -resolution, system prompt construction, optional compaction, memory injection, context build, and -then creates the assistant message before starting `runStreamForMessage()`. - -The renderer only renders an assistant loading row in two cases: - -- a persisted assistant record is present in `messageStore.messages` with empty content and - `status: pending` -- `messageStore.isStreaming` is true and `streamingBlocks.length > 0`, causing `ChatPage` to append - `toStreamingMessage(...)` - -Normal send does not emit a refresh after the assistant record is created unless -`emitRefreshBeforeStream` is set. Even if that flag were enabled, the assistant row would still only -appear after pre-stream work finishes because the assistant record is currently created after context -build. - -## UX Target - -Before: - -```txt -[User submits] - -User: hello - -...blank wait while pre-stream work/provider first chunk happens... - -Assistant: [content or tool activity appears] -``` - -After: - -```txt -[User submits] - -User: hello -Assistant: [spinner] - -...later... - -Assistant: first streamed content/tool activity -``` - -## Root Cause - -The visual loading state is coupled to backend stream state. There is no renderer-owned pending -assistant placeholder for the gap between submit and the first `chat.stream.updated` event. - -## Acceptance Criteria - -- A non-queued user submit immediately adds an assistant row with the existing empty-pending spinner. -- A non-queued command submit that sends a message follows the same immediate assistant loading - feedback. -- A non-queued command submit clears composer attachments and active skill chips immediately after - local feedback is created, without waiting for the send route to resolve. -- The placeholder appears after the submitted user row, not as a detached global loader. -- The placeholder is replaced or hidden as soon as the real assistant stream row or persisted - assistant record becomes available. -- If sending fails before streaming starts, the placeholder is removed and no stale spinner remains. -- Queued input while another generation is active does not create an immediate assistant row for the - queued turn. -- Manual compaction commands do not create a local assistant placeholder because they do not send a - chat turn. -- Switching sessions clears any local placeholder for the previous session. -- The implementation does not add a new IPC event or persist fake assistant messages. -- Existing streaming row reuse behavior remains intact: no completion flash and no duplicate - assistant rows once streaming begins. - -## Non-goals - -- Moving assistant DB row creation earlier in `AgentRuntimePresenter.processMessage()`. -- Renaming `chat.stream.completed` even though it is currently also used as a message refresh event. -- Reworking `chat.sendMessage` route lifetime or making it return before generation completes. -- Changing provider streaming behavior or pre-stream preparation order. - -## Open Questions - -None. diff --git a/docs/issues/context-overflow-auto-handoff/plan.md b/docs/issues/context-overflow-auto-handoff/plan.md deleted file mode 100644 index cfa66cf7d..000000000 --- a/docs/issues/context-overflow-auto-handoff/plan.md +++ /dev/null @@ -1,82 +0,0 @@ -# Context Overflow Auto-Handoff Plan - -## Approach - -Add a provider-call wrapper inside `runStreamForMessage` around the provider `coreStream`. The -wrapper will keep the existing local preflight, detect context-window failures before any provider -output is yielded to `processStream`, recover the request once, and retry with a rebuilt request view. - -## Implementation - -- Add `contextWindowError.ts` with `isContextWindowErrorLike(value: unknown): boolean` and use it - from both `process.ts` and the provider wrapper. -- Preserve local request preflight before provider calls. -- Track whether any provider event has been yielded. If the provider throws a matching context - overflow before that point, recover and retry. If the provider's first event is a matching error - event, recover and retry without yielding it. -- Keep every event after the first yielded event non-recoverable, including content, reasoning, tool - call, permission, usage, image, rate limit, stop, and later errors. -- Reuse `recoverRequestContextPressure` for auto-compaction recovery. When it returns no compaction - intent because auto compaction is disabled, keep the existing deterministic fit/max-token shrink - path and retry without summary generation. -- Re-run preflight after recovery. If the request still does not fit, throw - `buildRequestContextOverflowErrorMessage`. -- If the post-recovery retry still returns or throws a context-window error before any output, throw - a local DeepChat diagnostic instead of yielding the provider error. Use - `buildRequestContextOverflowErrorMessage` only when the fresh retry preflight still does not fit; - otherwise explain that the provider still reported context overflow after recovery despite - DeepChat's local estimate fitting. -- Treat local preflight recovery and provider overflow recovery as one assistant-run recovery - budget. If preflight recovery has already run, a provider overflow can only schedule one - summary-free strict trim retry; it must not run summary handoff again. -- Keep context-window matching strict enough to avoid quota, billing, and rate-limit false positives; - only generic token-limit wording may match when accompanied by request/context/input/prompt - wording. -- Keep `input exceeds` behind stronger token/context-pressure hints so unrelated input-size or upload - errors do not trigger context overflow recovery. -- Scan wrapped provider error fields by priority and stop on the first match so a long unrelated - field cannot hide a later context-window field. -- Continue scanning SDK `Error` instances after `message`, `name`, and `cause` so custom fields such - as `body` and `response.data.error.message` can trigger recovery. -- Scan array-shaped provider error payloads with a small fixed element cap. Include common - `errors` and `issues` fields while keeping existing quota, billing, rate-limit, and `429` - exclusion behavior unchanged. -- Cap strict retry's extra reserve at 8,192 tokens while preserving the existing max-token shrink. -- Persist view manifests with the actual per-attempt budget. Strict retry manifests record the - halved/capped requested max tokens and include the strict extra reserve in `reserveTokens`. -- Pass the request model id into DeepChat budget bypass detection so video-generation model-id - heuristics remain effective. -- Continue calling Memory injection after successful compaction recovery through the existing system - prompt rebuild path. Continue calling Memory extraction only when a compaction intent was actually - applied. - -## Compatibility - -- No schema, IPC, route, or public setting changes. -- Existing tape anchor name `auto_handoff/context_overflow` is reused. -- Existing stored messages are not deleted; recovery only changes the request view and summary cursor. -- Auto compaction disabled remains respected for summary generation, while still allowing deterministic - trim retry as a hard fallback. - -## Tests - -- Add classifier coverage for common provider messages. -- Add classifier negative coverage for quota, billing, TPM/RPM, rate-limit, and generic token-quota - failures. -- Add agent runtime coverage for first-event context overflow recovery, thrown context overflow - recovery, post-output overflow non-retry, auto-compaction-disabled trim retry, oversized local - request blocking, and ACP/image bypass. -- Add retry-failure coverage proving a second pre-output context overflow returns DeepChat local - budget guidance and does not perform a third provider call. -- Add video model-id bypass coverage for models such as `sora-*`. -- Add an agent-runtime test path that uses the real `processStream` to verify persisted assistant - errors do not contain provider raw context-window text. -- Add regression coverage for preflight recovery followed by provider overflow, proving no second - summary handoff occurs and only one strict trim retry is allowed. -- Add classifier coverage for SDK `Error` objects with nested `response`/`body` fields. -- Add classifier coverage for bounded `errors[]` / `issues[]` provider payloads. -- Add classifier coverage proving generic `input exceeds` file-size or upload-limit failures do not - match. -- Add manifest coverage for strict retry token budget fields. -- Preserve process-stream behavior for context overflow errors that are not intercepted by the - provider wrapper. diff --git a/docs/issues/context-overflow-auto-handoff/tasks.md b/docs/issues/context-overflow-auto-handoff/tasks.md deleted file mode 100644 index ec30c2b89..000000000 --- a/docs/issues/context-overflow-auto-handoff/tasks.md +++ /dev/null @@ -1,32 +0,0 @@ -# Context Overflow Auto-Handoff Tasks - -- [x] Create SDD issue artifacts. -- [x] Add shared context-window error classifier. -- [x] Replace `process.ts` private classifier with the shared classifier. -- [x] Add provider first-event/first-throw recovery wrapper in `runStreamForMessage`. -- [x] Keep auto-compaction-disabled recovery summary-free and trim-only. -- [x] Add focused compaction and agent runtime tests. -- [x] Update Auto Compaction copy in all locales. -- [x] Run `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, and targeted tests. -- [x] Narrow context-window classifier and add false-positive tests. -- [x] Convert post-recovery provider overflow into local budget diagnostics. -- [x] Cap strict trim retry extra reserve. -- [x] Add second-overflow retry failure tests. -- [x] Preserve video model-id context-budget bypass in provider retry wrapper. -- [x] Split retry-failure diagnostics for local over-budget vs provider tokenizer disagreement. -- [x] Scan classifier fields by priority without long-field starvation. -- [x] Add real processStream persistence coverage for provider-overflow retry failure. -- [x] Document run-level recovery guard repair follow-up. -- [x] Share one assistant-run recovery budget between preflight recovery and provider overflow. -- [x] Scan SDK `Error` custom fields in the context-window classifier. -- [x] Persist strict retry view manifests with the actual attempt token budget. -- [x] Add regression tests for preflight-recovery overflow, second-attempt throw, and manifest budget. -- [x] Run repair validation commands. -- [x] Document bounded array-shaped provider error polish. -- [x] Add bounded array scanning to `isContextWindowErrorLike()`. -- [x] Add array-shaped context overflow classifier tests. -- [x] Replace strict retry manifest magic-number assertions with formula-based expectations. -- [x] Run P3 polish validation commands. -- [x] Guard `input exceeds` behind token/context-pressure hints. -- [x] Clarify Bub `auto_handoff/context_overflow` reference in the spec. -- [x] Polish es-ES Auto Compaction description wording. diff --git a/docs/issues/file-attachment-token-bloat/spec.md b/docs/issues/file-attachment-token-bloat/spec.md deleted file mode 100644 index 4cbac916a..000000000 --- a/docs/issues/file-attachment-token-bloat/spec.md +++ /dev/null @@ -1,73 +0,0 @@ -# File Attachment Token Bloat Spec - -> Status: Implemented -> Date: 2026-07-02 -> Related: https://github.com/ThinkInAIXYZ/deepchat/issues/1864 - -## Problem - -DeepChat currently treats chat input files as both UI attachments and model-ready context. For -non-image files, `file.prepareFile` returns LLM-friendly `content`, the user message can store that -content, and the agent runtime context builder can replay it into every later request while the -message remains inside history. For images, uploaded/pasted files are represented with image data -URLs so vision models can see them, but historical image turns can also keep resending large inline -image payloads. Audio is different from document files: when the selected model supports native -audio input, keeping audio as structured base64 input is intentional model behavior, not a text -attachment fallback. - -Issue #1864 reports the visible failure mode: attaching an image, PDF, or document makes later API -calls resend the old file data, increasing request size, cache misses, token use, and latency. - -## User Need - -Users need file attachments to remain visually correct in the conversation while keeping later model -requests lean. The model should still receive enough path and metadata context to read local files -through tools instead of embedding their contents into chat history. - -## Goals - -- Keep image attachments as message media for the submitted turn so vision behavior remains correct. -- Avoid resending historical file bytes or extracted document text on unrelated follow-up turns. -- Route non-image file understanding through existing filesystem tooling when the agent needs the - content. -- Preserve native audio input for models that support audio attachments. -- Keep message rendering stable: existing attachment chips, thumbnails, names, paths, and metadata - stay visible in the transcript. - -## Acceptance Criteria - -- A newly submitted image attachment is sent as an image part when the active model supports vision. -- Historical image attachments are not resent as inline image data by default on later unrelated - turns; history includes enough metadata/path context for the model to request a read when tools are - available. -- Non-image attachments are replayed as path plus metadata by default, not full extracted content. -- Existing `agent-filesystem.read` remains the primary path for reading text, PDF, Office, and other - supported non-image files during an agent turn. -- PDF, Word, Excel, PowerPoint, text, and code files continue to rely on the existing - `FilePresenter.prepareFileCompletely(..., 'llm-friendly')` pipeline behind - `agent-filesystem.read`. -- Audio attachments still become structured `input_audio` parts when the model supports audio input. -- Attachment chips do not need a separate path insertion action; path/metadata is included - automatically in the model-visible attachment context. -- Existing sessions with stored attachment content do not require a database migration; request - construction strips or ignores stale inline content where needed. - -## Non-Goals - -- Redesigning the full attachment data model in one step. -- Replacing `agent-filesystem.read` with a new file reading tool. -- Automatically invoking file reads before every provider call. -- Deleting attachment content from existing user databases during this change. -- Removing structured audio input for audio-capable models. -- Solving provider-specific cache-key behavior beyond stopping repeated inline payloads. - -## Constraints - -- Reuse existing main-process tools and permission flow for file reads. -- Keep privileged filesystem access behind preload/typed route clients. -- Keep UI copy in i18n keys when implementation starts. -- Keep the first implementation small enough to review as a focused issue fix. - -## Open Questions - -None. diff --git a/docs/issues/harness-reliability-1841-1849/plan.md b/docs/issues/harness-reliability-1841-1849/plan.md deleted file mode 100644 index 9f92847d9..000000000 --- a/docs/issues/harness-reliability-1841-1849/plan.md +++ /dev/null @@ -1,67 +0,0 @@ -# Harness Reliability Issues 1841-1849 — Plan - -## Renderer Working Brief - -Target -- User-visible behavior: long chats/search/remote settings stay responsive; tests reflect current UI. -- Current rendering component: `ChatPage` -> `MessageList` -> `MessageListRow`. -- Logical owner: `ChatPage` owns scroll/search window coordination; `MessageList` renders rows. -- Route/layout/shell owner: chat route scroll container and settings route remote page. -- Trigger path: message load/stream/search, remote status timers, backup/sync actions. -- Existing similar implementation: `useMessageWindow`, chat scroll anchor logic, DeepChat events. - -Context Map -- Vue owner chain: `ChatPage` composes `MessageList`; settings `RemoteSettings` owns channel controls. -- DOM/render chain: scroll container -> search root -> row DOM with `data-message-id`. -- State source: message store, local chat search refs, remote IPC client, sync presenter. -- Derived state: display messages, message layout entries, search results, remote status summaries. -- Events: scroll/wheel/key, backup events, remote status refresh timers. -- Side effects: DOM search highlight mutation, IPC calls, filesystem archive reads/writes. -- Styling/layout constraints: sticky search/input, variable message heights, preserved scroll anchors. -- Performance-sensitive areas: message rows, markdown blocks, streaming, search, remote polling, zip. -- Accessibility concerns: search keyboard behavior and visible controls stay unchanged. -- Electron boundary: remote and sync calls cross renderer/preload/main; backup work stays in main. -- Existing project patterns: Composition API, typed clients, DeepChat event catalog, presenter methods. - -Diagnosis -- Root cause: multiple hot paths scale with total history or block the main process; tests drifted. -- Correct ownership layer: fix tests in test owners, chat windowing/search in `ChatPage`, heavy zip in - `SyncPresenter`, remote polling at renderer owners. -- Affected consumers: chat page, search bar, sidebar remote status, settings remote page, cloud sync. -- Constraints: no broad virtual scroller, no IPC contract churn, no new dependencies. -- Existing pattern to reuse: `useMessageWindow`, existing DeepChat backup status events, route tests. - -Decision -- Selected approach: bounded message window with spacers, data-driven search match list, async - `fflate` zip/unzip wrappers, timer gates/backoff, contract test updates. -- Files to edit: chat page/list/search utilities/tests, sync presenter/tests, remote components, - route/settings tests, SDD docs. -- State impact: local scroll/search state only; no persisted schema changes. -- DOM/layout impact: `MessageList` receives visible rows and spacer heights. -- Render/update impact: mounted message rows are bounded; highlight DOM mutation is visible-window only. -- IPC/main-process impact: fewer remote IPC loops; backup compression/extraction no longer uses sync - `zipSync`/`unzipSync`. -- Verification plan: targeted Vitest first, then format/i18n/lint and broader tests if time permits. - -## Implementation Slices - -1. Fix known test drift and replace the misleading perf test. -2. Bound chat row rendering with the existing message layout model. -3. Scope chat search highlighting to rendered rows while keeping full loaded-message match counts. -4. Convert sync backup archive creation/extraction to async zip/unzip and async file IO. -5. Gate remote polling by visibility/enabled state and add simple backoff. -6. Record #1848's state-map/design-freeze step instead of mixing a large refactor into this fix. - -## Compatibility - -- Message store and IPC payloads are unchanged. -- Backup file format and manifest remain unchanged. -- Search UI API remains `activeMatch` and `totalMatches`. -- Remote status display still uses the same status payload types. - -## Test Strategy - -- Main: deeplink/settings navigation/sync presenter tests. -- Renderer: ChatPage, MessageList, chat search utility, remote settings, model provider settings. -- Performance: report-only production-path tests using generated realistic message fixtures. -- Required project checks: `pnpm run format`, `pnpm run i18n`, `pnpm run lint`. diff --git a/docs/issues/harness-reliability-1841-1849/spec.md b/docs/issues/harness-reliability-1841-1849/spec.md deleted file mode 100644 index 8305b3409..000000000 --- a/docs/issues/harness-reliability-1841-1849/spec.md +++ /dev/null @@ -1,54 +0,0 @@ -# Harness Reliability Issues 1841-1849 — Spec - -## User Need - -DeepChat should stay responsive and testable in long agent sessions. The current issues point to -the same product risk: slow chat rendering, blocking main-process work, noisy tests, and -background polling make the agent harness harder to trust. - -## Goal - -Address issues #1841-#1849 with the smallest production changes that improve correctness and -performance signal without broad rewrites. - -## Acceptance Criteria - -- #1843: stable main and renderer test drift is corrected: - - MCP install deeplinks test the current chat-window route. - - hidden settings plugin sidebar expectations match the plugins hub behavior. - - renderer mocks satisfy current `useSkillsData` and router contracts. -- #1849/#1847: the mock-only performance test is removed and replaced by a minimal production-path - performance signal with realistic message fixtures. -- #1841: long loaded chat histories do not mount every loaded message row; visible DOM is bounded - by the active scroll window plus overscan. -- #1844: chat search no longer clears and rewrites highlights across the whole loaded history on - each message update; highlighting is scoped to rendered rows while match counting stays - data-driven. -- #1842: agent skill read paths remain async and cached; no new synchronous hot-path skill IO is - introduced. -- #1846: remote status polling is not a fixed always-on 2s workload when windows are hidden or no - remote channel is enabled. -- #1845: backup archive compression/extraction is moved off synchronous zip/unzip calls and uses - async file IO where it handles large backup payloads. -- #1848: the refactor issue is converted into a concrete split plan/state map without performing a - risky multi-thousand-line move in the same performance/test fix. - -## Constraints - -- Preserve existing presenter, IPC, and renderer API contracts. -- Keep UI strings in existing i18n paths; do not add visible copy unless required. -- Avoid new dependencies. Use existing Vue, Pinia, Electron, and `fflate` APIs. -- Keep mutation paths that rely on single-thread sequencing stable unless the change is clearly - safe. -- Do not add broad fallback behavior that hides real failures. - -## Non-Goals - -- Full dynamic-height virtual scrolling. -- Complete `agentRuntimePresenter` or `routes/index.ts` extraction. -- New remote event subscription IPC. -- Redesigning backup restore UX beyond making the heavy zip path async. - -## Open Questions - -None for this pass. #1848 full extraction remains a separate architecture task by design. diff --git a/docs/issues/harness-reliability-1841-1849/tasks.md b/docs/issues/harness-reliability-1841-1849/tasks.md deleted file mode 100644 index 134449ab0..000000000 --- a/docs/issues/harness-reliability-1841-1849/tasks.md +++ /dev/null @@ -1,12 +0,0 @@ -# Harness Reliability Issues 1841-1849 — Tasks - -- [x] T0: Create branch and confirm issue details from GitHub plus local failing tests. -- [x] T1: Fix #1843 test/spec drift for deeplink, settings navigation, and renderer router mocks. -- [x] T2: Fix #1849 and minimal #1847 by replacing mock-only perf tests with realistic fixtures and production-path checks. -- [x] T3: Fix #1841 by rendering a bounded chat message window with spacer heights. -- [x] T4: Fix #1844 by making search match counting data-driven and DOM highlights visible-window scoped. -- [x] T5: Audit #1842 hot skill read paths and keep the remaining sync IO out of active agent paths. -- [x] T6: Fix #1846 by gating remote polling and backing off idle/error refreshes. -- [x] T7: Fix #1845 by replacing synchronous backup zip/unzip and large file IO with async equivalents. -- [x] T8: Address #1848 by adding the concrete state-map/design-freeze artifact for the large-file split. -- [x] T9: Run format, i18n, lint, typecheck, targeted Vitest, and targeted E2E smoke verification. diff --git a/docs/issues/hooks-settings-clone-error/plan.md b/docs/issues/hooks-settings-clone-error/plan.md deleted file mode 100644 index f5690cd76..000000000 --- a/docs/issues/hooks-settings-clone-error/plan.md +++ /dev/null @@ -1,32 +0,0 @@ -# Plan - -## Implementation Approach - -1. Convert the Vue reactive hooks settings object to raw, structured-cloneable data before invoking `config.setHooksNotifications`. -2. Keep the returned normalized config assignment unchanged so the UI reflects main-process validation/normalization. -3. Add focused renderer/API test coverage proving a reactive hooks settings object is sanitized before IPC. - -## Affected Interfaces - -- Renderer settings component: `src/renderer/settings/components/NotificationsHooksSettings.vue` -- Renderer config client: `src/renderer/api/ConfigClient.ts` -- Existing route contract: `config.setHooksNotifications` remains unchanged. - -## Data Flow - -Current failing flow: - -`Vue ref/proxy config` -> `ConfigClient.setHooksNotificationsConfig` -> `window.deepchat.invoke` -> Electron structured clone failure - -Fixed flow: - -`Vue ref/proxy config` -> plain hooks config clone -> `ConfigClient.setHooksNotificationsConfig` -> route validation -> Electron IPC -> main presenter normalization - -## Compatibility - -The serialized payload remains `{ hooks: [{ id, name, enabled, command, events }] }`; persisted data and API contracts are unchanged. - -## Test Strategy - -- Add/adjust renderer API client unit test to call `setHooksNotificationsConfig` with a Vue reactive object and assert the bridge receives a non-reactive plain object. -- Run required repository checks after implementation: `pnpm run format`, `pnpm run i18n`, `pnpm run lint`. diff --git a/docs/issues/hooks-settings-clone-error/tasks.md b/docs/issues/hooks-settings-clone-error/tasks.md deleted file mode 100644 index 48e4d55ae..000000000 --- a/docs/issues/hooks-settings-clone-error/tasks.md +++ /dev/null @@ -1,10 +0,0 @@ -# Tasks - -- [x] Locate the hooks settings save path and confirm reactive state is passed to IPC. -- [x] Document the issue with spec and plan. -- [x] Convert hooks settings IPC payloads to plain cloneable data. -- [x] Add focused test coverage for reactive hooks settings input. -- [x] Run focused renderer API test: `pnpm vitest run test/renderer/api/clients.test.ts -- --runInBand`. -- [x] Run `pnpm run format`. -- [x] Run `pnpm run i18n`. -- [x] Run `pnpm run lint`. diff --git a/docs/issues/mcp-enabled-server-sort/plan.md b/docs/issues/mcp-enabled-server-sort/plan.md deleted file mode 100644 index 647443844..000000000 --- a/docs/issues/mcp-enabled-server-sort/plan.md +++ /dev/null @@ -1,16 +0,0 @@ -# Plan - -## Implementation - -Update `allServerList` in `src/renderer/src/stores/mcp.ts` to rank servers by: - -1. enabled built-in/deepchat -2. enabled custom -3. disabled built-in/deepchat -4. disabled custom - -Keep JavaScript's stable sort preserving original config order inside the same rank. - -## Test Strategy - -Add a store test proving an enabled custom server sorts before disabled built-in/custom servers. diff --git a/docs/issues/mcp-enabled-server-sort/tasks.md b/docs/issues/mcp-enabled-server-sort/tasks.md deleted file mode 100644 index 6962b6430..000000000 --- a/docs/issues/mcp-enabled-server-sort/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tasks - -- [x] Locate MCP server list owner. -- [x] Update MCP server sort rank. -- [x] Add focused store test. -- [x] Run focused test and required validation commands. diff --git a/docs/issues/mcp-oauth-callback-refresh/plan.md b/docs/issues/mcp-oauth-callback-refresh/plan.md deleted file mode 100644 index 75bbc61f3..000000000 --- a/docs/issues/mcp-oauth-callback-refresh/plan.md +++ /dev/null @@ -1,7 +0,0 @@ -# Plan - -- Make stored OAuth tokens take precedence over stale MCP auth errors. -- Make callback URL completion idempotent when credentials already exist. -- Refresh the selected auth status when DeepChat regains focus during the callback dialog. -- Add focused tests for the manager and renderer store/component behavior. -- Run format, i18n, lint, typecheck, and focused tests. diff --git a/docs/issues/mcp-oauth-callback-refresh/tasks.md b/docs/issues/mcp-oauth-callback-refresh/tasks.md deleted file mode 100644 index c87fdd5af..000000000 --- a/docs/issues/mcp-oauth-callback-refresh/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Fix MCP OAuth status precedence in the main process. -- [x] Make completed callback URL paste idempotent. -- [x] Refresh auth status when returning to the renderer callback dialog. -- [x] Add focused tests. -- [x] Run validation. diff --git a/docs/issues/mcp-plugin-startup-nonblocking/plan.md b/docs/issues/mcp-plugin-startup-nonblocking/plan.md deleted file mode 100644 index 378724203..000000000 --- a/docs/issues/mcp-plugin-startup-nonblocking/plan.md +++ /dev/null @@ -1,6 +0,0 @@ -# Plan - -1. Replace awaited automatic startup in MCP initialization with handled background starts. -2. Make plugin auto-start fire-and-forget after plugin enablement. -3. Add tests that initialization and plugin enablement do not wait on a hanging start. -4. Run format, i18n, lint, typecheck, and focused tests. diff --git a/docs/issues/mcp-plugin-startup-nonblocking/tasks.md b/docs/issues/mcp-plugin-startup-nonblocking/tasks.md deleted file mode 100644 index 685ab0490..000000000 --- a/docs/issues/mcp-plugin-startup-nonblocking/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tasks - -- [x] Make automatic MCP startup nonblocking. -- [x] Keep manual MCP start synchronous. -- [x] Add focused test coverage. -- [x] Run validation commands. diff --git a/docs/issues/mcp-save-toast-i18n/plan.md b/docs/issues/mcp-save-toast-i18n/plan.md deleted file mode 100644 index 74007e1e0..000000000 --- a/docs/issues/mcp-save-toast-i18n/plan.md +++ /dev/null @@ -1,8 +0,0 @@ -# Plan - -- Remove the accidental agent scope from `McpPluginsPage.vue`. -- Add the missing `mcp.saveSuccess` and `mcp.saveFailed` keys to locale `settings.json` files. -- Treat OAuth-required startup as a handled MCP toggle result when the auth status becomes - `required` or `authenticating`. -- Cover the OAuth-required toggle path in the existing MCP store tests. -- Run formatting, i18n, lint, typecheck, and focused MCP store tests. diff --git a/docs/issues/mcp-save-toast-i18n/tasks.md b/docs/issues/mcp-save-toast-i18n/tasks.md deleted file mode 100644 index 616b62e72..000000000 --- a/docs/issues/mcp-save-toast-i18n/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Remove accidental agent scope from the plugins MCP page. -- [x] Add missing MCP toast keys. -- [x] Suppress the generic failure result for OAuth-required startup. -- [x] Add MCP store coverage for OAuth-required toggle startup. -- [x] Run format, i18n, lint, typecheck, and focused MCP store tests. diff --git a/docs/issues/mcp-tools-missing-from-session/plan.md b/docs/issues/mcp-tools-missing-from-session/plan.md deleted file mode 100644 index 4ef3e9c42..000000000 --- a/docs/issues/mcp-tools-missing-from-session/plan.md +++ /dev/null @@ -1,33 +0,0 @@ -# Plan - -## Diagnosis - -The provider request is assembled from `AgentRuntimePresenter.loadToolDefinitionsForSession()`. -That path calls `ToolPresenter.getAllToolDefinitions()` with agent extension policy. Historical -`enabledMcpServerIds` can be an empty allowlist, which filters out all MCP tools even though the MCP -store and chat popover can still show a running server and tool count. - -Separately, `ToolManager` clears its MCP definition cache on `MCP_EVENTS.CLIENT_LIST_UPDATED`, but -`AgentRuntimePresenter` does not currently treat the same event as a session tool-profile change. - -## Implementation - -1. Stop passing `enabledMcpServerIds` from agent policy into session tool discovery and tool-call - routing. -2. Remove `enabledMcpServerIds` from the session tool-profile fingerprint so historical MCP policy - no longer affects cache keys. -3. Register `MCP_EVENTS.CLIENT_LIST_UPDATED` with `handleToolRegistryChanged`. -4. Keep plugin and skill policy behavior unchanged. - -## Test Strategy - -- Add a main presenter test that verifies MCP client-list updates invalidate the cached session tool - profile. -- Add a main presenter test that verifies historical `enabledMcpServerIds: []` is ignored for - session tool discovery. -- Update any renderer wrapper test that still expects the old MCP agent-scope route. - -## Risks - -- Old tests may still encode agent-scoped MCP selection. Update only the expectations affected by - the already-changed MCP plugins page. diff --git a/docs/issues/mcp-tools-missing-from-session/tasks.md b/docs/issues/mcp-tools-missing-from-session/tasks.md deleted file mode 100644 index a3b9bd3da..000000000 --- a/docs/issues/mcp-tools-missing-from-session/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks - -- [x] Diagnose request tool assembly and MCP filtering path. -- [x] Document the issue contract and implementation plan. -- [x] Stop session tool resolution from applying historical MCP server allowlists. -- [x] Invalidate session tool profiles on MCP client-list updates. -- [x] Add focused tests for cache invalidation and historical MCP policy. -- [x] Run focused tests and project validation commands. diff --git a/docs/issues/memory-recall-hot-path-keyword-recall/plan.md b/docs/issues/memory-recall-hot-path-keyword-recall/plan.md deleted file mode 100644 index 0187ccc8a..000000000 --- a/docs/issues/memory-recall-hot-path-keyword-recall/plan.md +++ /dev/null @@ -1,30 +0,0 @@ -# Plan - -## Implementation - -- Add small pure helpers in the memory presenter for recall keywordization and soft timeout handling. -- Extend the agent-memory repository search contract with a keyword match mode, defaulting to current all-term behavior. -- Add a corpus-aware recall keyword stats query to the repository contract. It counts active recallable rows per candidate term and excludes persona, working, archived, conflicted, and superseded rows. -- Implement corpus-aware term stats as one aggregate SQL statement per recall: `COUNT(*)` plus `SUM(CASE WHEN content LIKE ? ESCAPE '\\' THEN 1 ELSE 0 END)` for bounded candidate terms. -- Guard warm query embedding with one tracked in-flight entry per `agentId + providerId + modelId`. Skip vector recall while a fresh entry is active; replace stale entries after 30 seconds and let late promises clear only their own map entry. -- Add `recordAccessBatch` to the repository contract, SQLite implementation, and fake repository. -- Route public agent-facing recall and injection through dynamically selected keywordized OR matching; leave management search and internal neighbor lookups on precise all-term matching. -- Keep keyword candidate extraction pure and stopword-free. Extract ASCII/code and CJK candidates into one position-ordered pool before applying the candidate cap, then rank terms by low corpus hit count, term length, and original position; emit the selected terms back in original query order. -- Add settings-panel hints for missing embedding/extraction model configuration. -- Archive memory SDD #19 and #20 with explicit rejection notes and update the memory README. - -## Compatibility - -- Existing callers of `repository.search(agentId, query, limit)` continue to work because search mode defaults to all-term matching. -- Existing public contracts and persisted memory rows are unchanged. -- Late query embedding promises are intentionally not aborted; their result is ignored after timeout. The in-flight guard is a tracked rate gate, not a provider-level hard concurrency cap, so stale replacement may leave old provider calls running until they settle. -- If no extracted term hits the active memory corpus, the keyword branch returns no rows; vector recall still uses the original query when available. - -## Test Strategy - -- Main presenter tests cover timeout degradation, corpus-aware English and CJK recall, management search precision, no-hit keyword behavior, and batch access updates. -- SQLite table tests cover OR search mode, aggregate corpus term stats filtering, and batch access persistence. -- Presenter tests cover query-embedding in-flight suppression and stale replacement. -- Pure recall keyword tests cover extraction/ranking edge cases without presenter setup, including mixed CJK/ASCII candidate caps. -- Renderer tests cover new missing-model hints. -- Final validation runs through mise. diff --git a/docs/issues/memory-recall-hot-path-keyword-recall/tasks.md b/docs/issues/memory-recall-hot-path-keyword-recall/tasks.md deleted file mode 100644 index 596aef6d5..000000000 --- a/docs/issues/memory-recall-hot-path-keyword-recall/tasks.md +++ /dev/null @@ -1,19 +0,0 @@ -# Tasks - -- [x] Archive #19/#20 and update memory README. -- [x] Add repository search mode and batch access update. -- [x] Add recall keywordizer and query embedding soft timeout. -- [x] Wire agent-facing recall/injection to keywordized OR matching while keeping management search precise. -- [x] Add settings missing-model hints. -- [x] Add/update focused tests. -- [x] Run focused and final validation with `mise exec -- pnpm ...`. -- [x] Replace the static recall stopword list with corpus-aware term selection. -- [x] Add repository term stats support and tests. -- [x] Re-run focused and final validation with `mise exec -- pnpm ...`. -- [x] Change term stats to one aggregate SQL query. -- [x] Add query embedding in-flight suppression with stale replacement. -- [x] Add pure recall keyword tests and in-flight presenter tests. -- [x] Re-run focused, native-sqlite, renderer, and final validation with `mise exec -- pnpm ...`. -- [x] Fix mixed ASCII/code/CJK keyword candidate cap ordering. -- [x] Clarify query embedding in-flight guard semantics. -- [x] Add recall keyword edge test and rerun focused validation. diff --git a/docs/issues/model-list-fetch-404/plan.md b/docs/issues/model-list-fetch-404/plan.md deleted file mode 100644 index 5be1250a8..000000000 --- a/docs/issues/model-list-fetch-404/plan.md +++ /dev/null @@ -1,19 +0,0 @@ -# Plan - -## Cause - -`BaseLLMProvider.fetchModels()` wraps `this.fetchProviderModels().then(...)` in a synchronous `try/catch`. If `fetchProviderModels()` rejects asynchronously (for example `AiSdkProvider.requestProviderJson()` throws `ProviderHttpError` after fetch returns a 404), the rejection bypasses the catch block and propagates to `ModelManager.getModelList()` and the `models:list` route. - -## Implementation - -- Convert `BaseLLMProvider.fetchModels()` to `async`/`await` so both synchronous throws and asynchronous rejections are caught by the existing error handling. -- Preserve `suppressErrors` semantics: - - default `true` returns `[]` on failure; - - `false` rethrows. -- Keep model validation and `configPresenter.setProviderModels()` behavior unchanged. - -## Test strategy - -- Add a regression test with a provider whose `fetchProviderModels()` rejects asynchronously. -- Assert default `fetchModels()` returns `[]` and does not throw. -- Assert `fetchModels({ suppressErrors: false })` still rejects. diff --git a/docs/issues/model-list-fetch-404/tasks.md b/docs/issues/model-list-fetch-404/tasks.md deleted file mode 100644 index 3a5317254..000000000 --- a/docs/issues/model-list-fetch-404/tasks.md +++ /dev/null @@ -1,10 +0,0 @@ -# Tasks - -- [x] Map stack trace to source and identify failure path. -- [x] Document issue/spec/plan. -- [x] Change `BaseLLMProvider.fetchModels()` catch behavior to handle async rejections. -- [x] Add regression tests for suppressed and non-suppressed async fetch failures. -- [x] Run targeted provider tests and relevant typecheck/lint checks. - -- [x] Address review feedback so only provider fetch failures are suppressed; validation/persistence failures now surface. -- [x] Add regression coverage for provider model persistence failures. diff --git a/docs/issues/parcel-watcher-issue-1764/spec.md b/docs/issues/parcel-watcher-issue-1764/spec.md deleted file mode 100644 index 73d15ca02..000000000 --- a/docs/issues/parcel-watcher-issue-1764/spec.md +++ /dev/null @@ -1,144 +0,0 @@ -# Parcel Watcher Issue 1764 Spec - -## Goal - -Fix GitHub issue #1764 by replacing DeepChat's runtime file watching dependency with -`@parcel/watcher`, eliminating the workspace watcher file-descriptor exhaustion path while -preserving workspace refresh and skill hot-reload behavior. - -## Sources - -- GitHub issue: https://github.com/ThinkInAIXYZ/deepchat/issues/1764 -- `@parcel/watcher` package: https://www.npmjs.com/package/@parcel/watcher -- `@parcel/watcher` README: https://github.com/parcel-bundler/watcher -- VS Code File Watcher Internals: - https://github.com/microsoft/vscode/wiki/File-Watcher-Internals -- VS Code source repo: https://github.com/microsoft/vscode - - `src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts` - - `src/vs/platform/files/node/watcher/watcherMain.ts` - - `extensions/git/src/repository.ts` - -## Problem - -Issue #1764 reports that selecting a large macOS workspace such as `~/Downloads` with about -260k recursive entries causes the workspace content watcher to exhaust the main process file -descriptor pool. The observed failure chain is: - -```text -large workspace - -> chokidar fs.watch traversal - -> EMFILE from too many watched entries - -> child process spawn cannot allocate stdio fds - -> agent exec utility exits during startup - -> every exec tool call fails regardless of command content -``` - -Current DeepChat uses `chokidar` in two main-process Presenters: - -- `src/main/presenter/workspacePresenter/index.ts` - - content watcher for workspace file changes - - git metadata watcher for `HEAD`, `index`, `packed-refs`, and `refs` -- `src/main/presenter/skillPresenter/index.ts` - - skills directory watcher for `SKILL.md` hot reload - -`@parcel/watcher` supports recursive directory subscriptions and uses FSEvents first on macOS, -which matches the root fix requested in the issue. - -## Design Direction - -Use the VS Code watcher model as the design reference: - -- A watcher service owns native watcher lifecycle and exposes logical subscriptions to features. -- Watcher hosts run outside the Electron main process using Electron `utilityProcess`. -- Watch requests are pooled and deduplicated by root, scope, include/exclude rules, and fallback - policy. -- Raw events are buffered, coalesced, and throttled before feature code receives them. -- Git metadata watching uses a dedicated watcher host lane so repository refresh pressure is - isolated from content and skill hot reload. -- Large workspaces keep a degraded but functional mode through snapshot polling or lifecycle - refresh when native watching fails or event pressure exceeds limits. - -## Requirements - -- Native file watching runs through a main-process `WatcherService` facade. -- `WorkspacePresenter` and `SkillPresenter` consume logical watcher subscriptions and do not - import `@parcel/watcher` directly. -- The watcher service starts Electron utility process hosts for native watcher work. -- Workspace content and skill hot reload use the content watcher host. -- Git metadata uses a separate git watcher host or an independently restartable git watcher lane. -- Workspace content watching uses `@parcel/watcher` for recursive subscriptions in the watcher - host. -- Workspace git metadata watching uses `@parcel/watcher` in the git watcher host and still emits - git-only invalidations for `HEAD`, `index`, `packed-refs`, and `refs` changes. -- Workspace watcher lifecycle keeps the existing security boundary: - `registerWorkspace` grants access; `watchWorkspace` owns watcher lifetime. -- Workspace watcher runtime ref counting remains intact across repeated panel mounts. -- Workspace file changes still publish `workspace.invalidated` with `kind: 'fs'`. -- Git metadata changes still publish `workspace.invalidated` with `kind: 'git'`. -- `.git` directory creation, deletion, or replacement still refreshes git watch metadata and - publishes `workspace.invalidated` with `kind: 'full'`. -- Workspace content ignores preserve the existing ignored directory set: - `node_modules`, `dist`, `build`, `__pycache__`, `.venv`, `venv`, `.idea`, `.vscode`, - `.cache`, `coverage`, `.next`, `.nuxt`, `out`, and `.turbo`. -- Workspace content watcher ignores `.git` children while still observing the `.git` directory - boundary itself. -- Skill hot reload still handles `SKILL.md` update, create, and delete events. -- Skill hot reload still ignores `.deepchat-meta`. -- Skill hot reload still respects `SKILL_CONFIG.FOLDER_TREE_MAX_DEPTH` at event handling time. -- Raw watcher events are buffered and coalesced before Presenter callbacks run. -- Event delivery is throttled with bounded memory so event floods degrade cleanly. -- Large workspace degradation emits `workspace.invalidated` with `source: 'fallback'` when native - events are unavailable. -- A typed workspace watcher status event reports `healthy`, `degraded`, and `failed` states to the - renderer for a small workspace-panel warning. -- Duplicate skill-name handling remains unchanged. -- `chokidar` is removed from runtime dependencies and lockfile entries. -- Native packaging includes the `@parcel/watcher` package and platform prebuilt packages. - -## Acceptance Criteria - -- On macOS, selecting a workspace with more than 100k files does not produce a sustained EMFILE - storm from the workspace watcher. -- After selecting that large workspace, a simple agent exec command such as `mkdir -p test` can - still spawn. -- Killing or crashing the watcher utility process does not terminate the main process. -- The watcher service restarts a failed watcher host and replays active watch requests. -- Native watcher failure with `EMFILE`, `ENOSPC`, or Parcel rescan errors enters degraded mode - instead of repeated error storms. -- Workspace panel refresh behavior remains unchanged for: - - create, update, and delete under the workspace - - ignored directory changes - - `.git` boundary changes - - git `HEAD`, `index`, `packed-refs`, and `refs` updates -- Skills catalog refresh behavior remains unchanged for: - - editing an existing `SKILL.md` - - adding a new `SKILL.md` - - deleting an existing `SKILL.md` - - duplicate skill names -- Unit tests cover the watcher adapter, workspace watcher lifecycle, workspace event mapping, git - metadata filtering, skill event mapping, and async subscription teardown. -- Unit tests cover watcher request pooling, event coalescing, host restart, and large workspace - fallback state transitions. -- `pnpm run typecheck:node`, focused main-process tests, `pnpm run format`, `pnpm run i18n`, - and `pnpm run lint` pass before implementation is considered complete. - -## Constraints - -- Keep existing `workspace.invalidated` and `skills.catalog.changed` event payloads unchanged. -- Add one typed watcher status event only for degraded/failure UI state. -- Keep workspace directory reading and file search lazy; this change targets live change - detection only. -- Keep native dependency packaging explicit because Electron ASAR packaging can break `.node` - modules when they remain inside `app.asar`. -- Keep exec utility error-copy improvements outside this increment after the fd-exhaustion root - cause is removed. - -## Review Decisions - -- Recommended dependency version: `@parcel/watcher@^2.5.6`. -- Recommended implementation shape: a main-process `WatcherService` facade backed by Electron - utility process watcher hosts. -- Recommended lifecycle change: model watcher startup and shutdown as async operations where the - Presenter lifecycle already supports promises. -- Recommended isolation model: content/skill watcher host and git watcher host are independently - restartable. diff --git a/docs/issues/pr-1862-review-fixes/plan.md b/docs/issues/pr-1862-review-fixes/plan.md deleted file mode 100644 index 7afdbb06e..000000000 --- a/docs/issues/pr-1862-review-fixes/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# PR 1862 Review Fixes Plan - -## Approach - -- Keep fixes at existing ownership points: dispatch heuristics, reviewer decision normalization, SQLite table read path, and `ChatPage` delete confirmation. -- Update existing tests near each affected behavior. -- Keep docs in sync with the runtime envelope instead of changing the envelope contract late in the PR. - -## Affected Areas - -- Main runtime: `agentRuntimePresenter/index.ts`, `dispatch.ts`. -- Persistence: `deepchatSessions.ts`. -- Renderer: `ChatPage.vue`, i18n typing/locales. -- Tests: focused runtime, persistence, and renderer component tests. - -## Verification - -- `pnpm run format` -- `pnpm run i18n` -- `pnpm run lint` -- Focused Vitest files for changed behavior. diff --git a/docs/issues/pr-1862-review-fixes/tasks.md b/docs/issues/pr-1862-review-fixes/tasks.md deleted file mode 100644 index e4b144eeb..000000000 --- a/docs/issues/pr-1862-review-fixes/tasks.md +++ /dev/null @@ -1,11 +0,0 @@ -# PR 1862 Review Fixes Tasks - -- [x] Review CodeRabbit findings against current branch. -- [x] Fix command-runner auto-review detection. -- [x] Fix high-risk reviewer decision normalization. -- [x] Normalize persisted permission modes on read. -- [x] Guard delete confirmation against read-only session changes. -- [x] Align permission review spec with runtime envelope. -- [x] Update locale values and i18n typing. -- [x] Add focused regression tests. -- [x] Run verification. diff --git a/docs/issues/pr-1870-review-fixes/plan.md b/docs/issues/pr-1870-review-fixes/plan.md deleted file mode 100644 index 29991a1d7..000000000 --- a/docs/issues/pr-1870-review-fixes/plan.md +++ /dev/null @@ -1,20 +0,0 @@ -# Plan - -## Implementation - -1. Patch the smallest root locations: - - `mcpOAuthManager.ts` for OAuth error shape checks and stale-flow guard. - - `mcpOAuthProvider.ts` for authorization URL scheme validation. - - `oauthCredentialStore.ts` for scoped deletion persistence. - - renderer OAuth callback handlers for in-flight guards. - - `McpServerCard.vue` for the auth-error label. - - shared contracts for stricter callback/status validation. -2. Replace the source-text sheet test with a mounted DOM assertion. -3. Update the new OpenAI Codex/MCP auth strings in affected locale JSON files. - -## Test Strategy - -- Extend MCP OAuth manager tests for HTTP status classification. -- Add a credential store scoped-clear test. -- Update the SheetContent drag test to mount the component. -- Run focused tests for MCP OAuth/store/renderer components plus format, i18n, lint, and typecheck. diff --git a/docs/issues/pr-1870-review-fixes/tasks.md b/docs/issues/pr-1870-review-fixes/tasks.md deleted file mode 100644 index e054c082b..000000000 --- a/docs/issues/pr-1870-review-fixes/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tasks - -- [x] Fetch and verify PR review comments. -- [x] Patch OAuth manager/provider/store issues. -- [x] Patch renderer duplicate-submit and status-label issues. -- [x] Tighten route/event schemas. -- [x] Replace sheet source-text test with DOM assertion. -- [x] Localize new auth strings in affected locales. -- [x] Run focused tests and required validation commands. diff --git a/docs/issues/sdd-cleanup-default-prompt/spec.md b/docs/issues/sdd-cleanup-default-prompt/spec.md new file mode 100644 index 000000000..988c22d83 --- /dev/null +++ b/docs/issues/sdd-cleanup-default-prompt/spec.md @@ -0,0 +1,35 @@ +# SDD Cleanup Default Prompt + +## Issue + +The `deepchat-sdd-cleanup` default prompt sounded like a routine post-implementation action instead +of a manual-only cleanup workflow. + +## Impact + +Agents could treat SDD cleanup as an automatic final step and prune docs without an explicit +developer request. + +## Root Cause + +The YAML `default_prompt` described cleaning completed SDD folders after implementation and +validation, while the cleanup skill itself requires an explicit cleanup request. + +## Fix Plan + +- Tighten the default prompt so the manual-only gate is explicit. +- Keep the cleanup skill implementation and behavior unchanged. + +## Tasks + +- [x] Update the cleanup skill default prompt. + +## Validation + +- `pnpm run format` +- `pnpm run i18n` +- `pnpm run lint` + +## Linked GitHub Issue + +None. Handled from PR #1875 review feedback. diff --git a/docs/issues/session-restore-scroll-intent/plan.md b/docs/issues/session-restore-scroll-intent/plan.md deleted file mode 100644 index 6090913b5..000000000 --- a/docs/issues/session-restore-scroll-intent/plan.md +++ /dev/null @@ -1,43 +0,0 @@ -# Plan - -## Approach - -- Reuse the existing `onScroll` path in `ChatPage.vue`. -- Track the last bottom `scrollTop` written by session restore settling. -- During an active session restore settle, cancel the settle when a scroll-only path moves upward - from that recorded bottom position. -- Switch to anchored-reading synchronously on scroll-away so row measurement callbacks cannot reuse - stale `initial-bottom` state. -- Track short-lived upward wheel intent so slow upward scrolls inside the bottom threshold are not - reclassified as auto-follow on the next metrics frame. -- Disable browser-native overflow anchoring on the chat scroll container and remove row-level - `content-visibility` placeholders so Chromium does not visually reposition historical messages - during slow manual scrolling. -- Render image blocks inside a fixed preview frame so image load/decode cannot change the message - row height while the user is reading around the image. -- Measure each message row immediately after mount now that row-level `content-visibility` is gone; - keep ResizeObserver for later real resizes instead of deferring first measurement until viewport - intersection. -- Keep the sticky composer layer transparent so the existing `ChatInputBox` translucent background - and backdrop blur remain visible; accept the repaint cost from content behind the input. -- Mark completed chat markdown as final and rely on markstream's stable completed-layout path so - first-time scrollback does not resolve an offscreen estimate into real content height under the - visible message wrapper. -- Keep the existing gesture listeners as early cancellation for wheel, touch, pointer, mouse, and - keyboard paths. - -## Test Strategy - -- Add one ChatPage regression test using the existing restore-settle harness. -- The test triggers only `scroll` after moving `scrollTop` away from bottom, then verifies later - layout growth does not force the viewport back to bottom. -- Add a second regression check where a message `measure` event fires immediately after the - scroll-only intent. -- Add a near-bottom slow upward wheel regression check. -- Add a MarkdownRenderer regression check that completed chat markdown is marked final while - streaming markdown keeps its existing path. - -## Compatibility - -- No persisted state changes. -- No IPC or main-process changes. diff --git a/docs/issues/session-restore-scroll-intent/tasks.md b/docs/issues/session-restore-scroll-intent/tasks.md deleted file mode 100644 index b6a3fa1fe..000000000 --- a/docs/issues/session-restore-scroll-intent/tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# Tasks - -- [x] Add SDD issue docs. -- [x] Cancel active restore settling on scroll-only user intent. -- [x] Add a regression test for scroll-only restore cancellation. -- [x] Switch scroll-away to anchored-reading synchronously. -- [x] Add a regression test for message measurement after scroll-only intent. -- [x] Track slow upward wheel intent inside the bottom threshold. -- [x] Add a regression test for near-bottom slow upward wheel scroll. -- [x] Run focused test and required project checks. -- [x] Disable native overflow anchoring on the chat scroll container. -- [x] Remove row-level `content-visibility` placeholders from manual reading flow. -- [x] Reserve stable preview space for image blocks. -- [x] Measure message rows at mount instead of first viewport intersection. -- [x] Restore the transparent sticky composer background after accepting the repaint cost. -- [x] Mark completed chat markdown final and rely on markstream stable completed layout. diff --git a/docs/issues/session-start-lag/spec.md b/docs/issues/session-start-lag/spec.md deleted file mode 100644 index 1ec31b3d6..000000000 --- a/docs/issues/session-start-lag/spec.md +++ /dev/null @@ -1,37 +0,0 @@ -# Session Start Lag - -Status: implemented -Date: 2026-06-22 - -## User Need - -Sending a new DeepChat message should not freeze the main process for several seconds before the -provider stream starts. - -## Current Behavior - -Logs show one or more seconds between `processMessage` and `[ProcessStream] start`. The lag is -before provider streaming and after session creation. Diagnostic logging isolated the slow path to -`SystemEnvPromptBuilder` while reading `AGENTS.md`, both when the file is missing and when it exists. - -## Acceptance Criteria - -- No-project sessions still start with `projectDir=`. -- Pre-stream preparation logs identify the slow step before any behavioral optimization. -- Missing `AGENTS.md` files are treated as an expected state and do not emit heavy warning logs. -- Slow `AGENTS.md` reads do not hold the pre-stream path for the full filesystem latency. -- Repeated messages for the same `AGENTS.md` path reuse cached instruction content while refreshing - stale content in the background. -- No-project sessions keep the existing agent/code tools; performance must not be fixed by removing - tools from the session. -- No new dependencies. - -## Non-goals - -- Reworking tool routing caches. -- Changing provider streaming behavior. -- Adding timeouts or skipping memory/tool work before the slow step is confirmed. - -## Open Questions - -None. diff --git a/docs/issues/settings-save-clone-errors/plan.md b/docs/issues/settings-save-clone-errors/plan.md deleted file mode 100644 index 974ff38d0..000000000 --- a/docs/issues/settings-save-clone-errors/plan.md +++ /dev/null @@ -1,5 +0,0 @@ -# Plan - -- Normalize Cron Jobs renderer API upsert payloads before IPC. -- Cover the regression with a reactive runtime object and structured clone check. - diff --git a/docs/issues/settings-save-clone-errors/tasks.md b/docs/issues/settings-save-clone-errors/tasks.md deleted file mode 100644 index d36fc631c..000000000 --- a/docs/issues/settings-save-clone-errors/tasks.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tasks - -- [x] Convert Cron Jobs upsert input to a plain IPC payload. -- [x] Add renderer client regression coverage. -- [x] Run focused validation. diff --git a/docs/issues/sheet-close-no-drag/plan.md b/docs/issues/sheet-close-no-drag/plan.md deleted file mode 100644 index 3fb505b43..000000000 --- a/docs/issues/sheet-close-no-drag/plan.md +++ /dev/null @@ -1,5 +0,0 @@ -# Plan - -- Add explicit Electron `no-drag` styling to the shared sheet close button. -- Add a focused regression check that the sheet close button keeps the `no-drag` app region. -- Run format, i18n, lint, typecheck, and the focused test. diff --git a/docs/issues/sheet-close-no-drag/tasks.md b/docs/issues/sheet-close-no-drag/tasks.md deleted file mode 100644 index 42782dffa..000000000 --- a/docs/issues/sheet-close-no-drag/tasks.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tasks - -- [x] Patch the shared sheet close button. -- [x] Add focused regression coverage. -- [x] Run validation. diff --git a/docs/issues/sidebar-history-pagination-stuck/spec.md b/docs/issues/sidebar-history-pagination-stuck/spec.md deleted file mode 100644 index e9fa6b9fa..000000000 --- a/docs/issues/sidebar-history-pagination-stuck/spec.md +++ /dev/null @@ -1,50 +0,0 @@ -# Sidebar history pagination remains stuck - -## User need - -Users with many persisted conversations must be able to browse and search older conversations from the sidebar. The database can contain complete history, but the sidebar currently exposes only the already-loaded subset when pagination does not continue. - -## Problem statement - -Issue [#1762](https://github.com/ThinkInAIXYZ/deepchat/issues/1762) reports that historical conversations cannot be loaded even though the database is complete. A prior fix added viewport auto-fill for the case where the first regular-session page does not create a scrollbar. The problem still appears to persist when sidebar height, project/date grouping, collapsed groups, and the All agents view affect the rendered list height and scrollability. - -Current behavior to re-check: - -- Sidebar pulls lightweight conversation pages from SQLite. -- Renderer appends pages only when the sidebar scroll container reaches the bottom or auto-fill decides the rendered viewport is not filled. -- Grouping and collapsed groups can make the rendered content much shorter than the loaded data set. -- All agents is a UI-level filter over already-loaded sessions, not an independent complete fetch scope. -- Search is currently local to loaded sidebar sessions, so older database rows cannot be found until pagination reaches them. -- Runtime comparison showed DB had 52 regular non-draft sessions while the renderer stayed at 30 with `hasMore=true`; `loadNextPage()` failed because Pinia's reactive cursor proxy could not be cloned over IPC. - -## Goals - -1. Ensure sidebar pagination continues until the user can reach older regular conversations. -2. Make bottom loading robust when rendered height changes because of grouping, collapsed groups, pinning, All agents/agent filter changes, and search filtering. -3. Keep All agents capable of eventually loading the complete regular-session history rather than stopping at the visible subset. -4. Preserve the existing lightweight-session paging API and avoid loading message bodies for sidebar browsing. - -## Acceptance criteria - -- Initial sidebar load continues fetching when visible content does not overflow the list container and `hasMore` remains true. -- Toggling group mode, expanding/collapsing groups, switching All agents vs a specific agent, changing search text, pinning/unpinning, or resizing the sidebar re-runs the pagination fill check. -- Scrolling near the bottom still requests the next page exactly once per pending page and keeps using the current cursor. -- All agents view does not become stuck with `hasMore=true` and no further page requests when visible groups are shorter than the viewport. -- Tests cover the stuck cases caused by collapsed/filtered/grouped visible content, not only the first-page-no-scroll case. - -## Constraints - -- Sidebar list should keep using lightweight session records only. -- Subagent sessions must not consume visible sidebar page slots unless explicitly requested elsewhere. -- Do not introduce unbounded eager loading; keep guardrails against cursor stalls or repeated empty pages. -- Avoid changing database schema for this issue. - -## Non-goals - -- Full global message search redesign. -- Reworking project group ordering UX. -- Changing how conversations are stored. - -## Open questions - -None for implementation. The intended behavior is: sidebar pagination should be driven by the rendered visible list and user filter changes, and should continue safely while `hasMore` is true and the viewport/bottom condition requires more rows. diff --git a/docs/issues/skill-adopt-name-mismatch/spec.md b/docs/issues/skill-adopt-name-mismatch/spec.md new file mode 100644 index 000000000..028b598d4 --- /dev/null +++ b/docs/issues/skill-adopt-name-mismatch/spec.md @@ -0,0 +1,50 @@ +# Skill Adopt Name Mismatch + +## User Need + +Adopting an agent-owned skill should work when the skill folder name differs from the +`name` field inside `SKILL.md`, including when that frontmatter name is not a valid DeepChat slug. + +## Goal + +Allow agent skill adoption to use the agent entry name as the DeepChat target name and rewrite the +copied `SKILL.md` frontmatter during adoption. + +## Acceptance Criteria + +- Previewing adoption no longer fails only because `SKILL.md` `name` differs from the agent skill + directory name. +- Previewing adoption no longer fails only because the unused `SKILL.md` `name` is not a valid + DeepChat target name. +- Adoption keeps the agent-facing entry name as the default DeepChat target name. +- The copied DeepChat skill remains valid by rewriting `SKILL.md` `name` to the target name. +- Existing conflict rename behavior still works. + +## Constraints + +- Keep skill name safety validation. +- Do not change agent scan display behavior. +- Do not add new dependencies. + +## Non-Goals + +- Redesigning the skills settings UI. +- Changing external tool scan identity rules. + +## Fix Plan + +- Treat the scanned agent entry name as the source of truth for adoption target naming. +- Validate only the target name that DeepChat will write. +- Keep requiring a description in `SKILL.md`. + +## Tasks + +- [x] Stop validating the unused source frontmatter name. +- [x] Cover invalid source frontmatter names in adoption preview tests. + +## Validation + +- `pnpm vitest run test/main/presenter/skillSyncPresenter/index.test.ts` +- `pnpm run format` +- `pnpm run i18n` +- `pnpm run lint` diff --git a/docs/issues/skill-scope-and-refresh/plan.md b/docs/issues/skill-scope-and-refresh/plan.md deleted file mode 100644 index 9ff5ea5c0..000000000 --- a/docs/issues/skill-scope-and-refresh/plan.md +++ /dev/null @@ -1,57 +0,0 @@ -# Plan - -## Approach - -Split skill state into three semantics: - -1. Explicit/manual session activation remains pinned state backed by `SkillPresenter.setActiveSkills` and `new_sessions.active_skills`. -2. Composer-selected skills are message-scoped. The chat input keeps a local draft skill list, submits it with the next message as `SendMessageInput.activeSkills`, then clears the local list after successful submit/queue/steer/create-session. -3. Agent `skill_view` root activation is runtime/message-loop state. The tool returns activation metadata, and the agent runtime keeps a per-generation/per-session set of runtime-activated skill names for the active message loop. - -Do not overload `isPinned` for runtime activation. `isPinned` must mean persisted/manual session pin only. Agent tool results may add separate fields such as `activatedForMessage`, `activationScope`, and `activeForCurrentMessage` to communicate runtime activation without polluting pinned semantics. - -When a message starts with composer active skills, initialize the runtime effective skill set with `manual session skills + message active skills`. When runtime activation changes the effective skill set, refresh both tools and the leading system prompt before the next provider request. - -## Affected Interfaces - -- `SendMessageInput`: add optional `activeSkills?: string[]` for message-scoped composer skill context. -- Route schemas for chat send/steer/pending inputs: accept `activeSkills`. -- `CreateSessionInput`: keep `activeSkills` for compatibility but treat it as initial-message active skills, not session pinning. -- `ChatInputBox` / `useSkillsData`: make selected skills local composer state for existing conversations and expose consume/snapshot helpers. -- `ChatPage` / `NewThreadPage`: include consumed composer skills in submitted message payload and clear after successful submit. -- `AgentSessionPresenter`: stop persisting create-session `activeSkills`; pass them into the initial message payload. -- `AgentRuntimePresenter`: initialize runtime message skills from normalized input, persist them on the user message record, materialize them back from normalized user-message tables, and include them in prompt/tool loading for that message loop. -- `SkillTools.handleSkillView`: support viewing without presenter-side activation for agent runtime calls. -- `AgentToolManager`: derive `activationApplied` from runtime active skill context instead of persisted active skills, and return message-scoped activation metadata without setting `isPinned` to true. -- System/tool prompts: replace pinned wording for automatic `skill_view` activation with message-scoped activation wording. - -## Data Flow - -- Composer skill selection updates local input state only. -- Submit path consumes local selected skills and sends `{ text, files, activeSkills }`. -- New session creation passes active skills inside the initial message payload and does not call `setActiveSkills`. -- Runtime start resets runtime activated skills, adds message-scoped active skills, then computes effective skills = session-pinned + message/runtime active. -- User message content stores `activeSkills` so the message context is visible/auditable and retry can reuse it. The normalized materialization path must preserve the raw message-scoped `activeSkills` because the structured `deepchat_user_messages` table stores text/search/think but not skill names. -- `skill_view` root call returns `activatedSkill` when the viewed skill is not already effective. -- Runtime callback adds it to the local runtime set for the active message loop. -- Tool refresh uses `manual + message/runtime` effective skills. -- System prompt refresh rebuilds with the same effective skill set and replaces the first system message in the active conversation messages. -- Tool output keeps `isPinned` equal to the persisted/manual pin state and reports runtime activation separately. - -## Compatibility - -- Existing manual active skills remain stored in `new_sessions.active_skills`. -- Existing `skill_view` route behavior outside agent runtime remains read-only unless explicitly using session active APIs. -- Existing skill_run scripts remain gated by active skill names, now including message/runtime activation for the current loop. -- Existing consumers of `isPinned` can continue treating it as pinned/session state. -- Existing stored messages without `activeSkills` continue to parse as empty message-scoped skills. - -## Test Strategy - -- Unit-level tests for `SkillTools.handleSkillView` avoiding persisted activation. -- Unit-level tests for `AgentToolManager` activation metadata and non-pinned output. -- Unit-level tests for `processStream` refreshing tools and system prompt after runtime activation. -- Runtime tests ensuring `SendMessageInput.activeSkills` influences initial prompt/tools but does not persist session active skills. -- Renderer tests ensuring composer skills clear after submit, are sent in the message payload, and appear on the corresponding user message item. -- Message store tests ensuring materialized user message content preserves `activeSkills` from the raw message JSON. -- Run targeted tests plus required repository checks: `pnpm run format`, `pnpm run i18n`, `pnpm run lint`. diff --git a/docs/issues/skill-scope-and-refresh/tasks.md b/docs/issues/skill-scope-and-refresh/tasks.md deleted file mode 100644 index 4a91da272..000000000 --- a/docs/issues/skill-scope-and-refresh/tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# Tasks - -- [x] Update skill tool view path so agent `skill_view` does not persist active skills. -- [x] Add runtime activation context for effective skills during a generation loop. -- [x] Refresh tools using effective manual + runtime skills. -- [x] Refresh leading system prompt after runtime skill activation. -- [x] Stop representing runtime activation as conversation-pinned state in tool output and prompts. -- [x] Add/adjust tests for message-scoped activation semantics. -- [x] Add message-scoped `SendMessageInput.activeSkills` plumbing. -- [x] Change composer skill selection to local message draft state instead of session active state. -- [x] Send/queue/steer/create first-turn messages with composer skills and clear chips after submit. -- [x] Initialize runtime effective skills from message active skills and persist them on the user message record. -- [x] Preserve `activeSkills` when user messages are materialized from normalized message tables. -- [x] Display message-scoped skills on the corresponding user message item and cover it with renderer tests. -- [x] Move message-scoped skill chips out of the message bubble and restyle them as subtle message metadata. -- [x] Add renderer/runtime tests for composer message-scoped skills. -- [x] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` after the visibility fix. - -- [x] Address review feedback for active skill fallback data, wording, refresh parameter flow, and composer naming clarity. -- [x] Strengthen process stream coverage for hook-backed message-scope skill activation refresh. diff --git a/docs/issues/vueuse-typecheck-dedupe/plan.md b/docs/issues/vueuse-typecheck-dedupe/plan.md deleted file mode 100644 index b2d25c2aa..000000000 --- a/docs/issues/vueuse-typecheck-dedupe/plan.md +++ /dev/null @@ -1,17 +0,0 @@ -# Plan - -## Cause - -The root project depends on `@vueuse/core@12.8.2`, which depends on its own `vue@3.5.34`. The app and newer UI dependencies use `vue@3.5.39`, so `vue-tsgo` sees incompatible `Ref` and `ComputedRef` symbols across the graph. - -## Implementation - -- Update the root `@vueuse/core` dev dependency to `^14.3.0`, matching the version already used by `reka-ui`. -- Regenerate the lockfile with pnpm 10. -- Keep code unchanged unless typecheck reveals a real API incompatibility. - -## Test strategy - -- Run `pnpm why vue @vueuse/core @vueuse/shared` to confirm dedupe. -- Run `pnpm run typecheck`. -- Re-run release-required `format`, `i18n`, and `lint`. diff --git a/docs/issues/vueuse-typecheck-dedupe/tasks.md b/docs/issues/vueuse-typecheck-dedupe/tasks.md deleted file mode 100644 index c57952764..000000000 --- a/docs/issues/vueuse-typecheck-dedupe/tasks.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tasks - -- [x] Identify duplicate Vue type source in the dependency graph. -- [x] Document the issue and minimal dependency plan. -- [x] Align root `@vueuse/core` with the existing Vue peer version. -- [x] Validate dependency graph and typecheck. -- [x] Re-run release-required checks. diff --git a/docs/issues/yobrowser-cdp-graceful-degradation/spec.md b/docs/issues/yobrowser-cdp-graceful-degradation/spec.md deleted file mode 100644 index 2aefa3f1c..000000000 --- a/docs/issues/yobrowser-cdp-graceful-degradation/spec.md +++ /dev/null @@ -1,114 +0,0 @@ -# YoBrowser CDP Graceful Degradation - -## Problem - -GitHub issue #1734 reports that a running agent task can lose browser control when -the user closes the right-side YoBrowser panel mid-session. The browser view is -detached or hidden, but the agent still attempts later `cdp_send` calls for DOM -inspection, scripted interaction, or screenshot verification. Today those calls -surface as generic initialization failures or blocked CDP failures, which gives -the model too little context to decide whether it should reopen the browser, -inspect status, skip browser-dependent verification, or ask the user for help. - -## User Story - -As a user running a browser-assisted agent task, I need CDP failures caused by an -unavailable YoBrowser session to be reported as meaningful, recoverable tool -errors so the agent can adapt its next step instead of stalling the task. - -As an agent, when `cdp_send` cannot execute because the session browser is -closed, detached, hidden, destroyed, or otherwise not ready, I need a compact -error payload that explains the browser state and names the safe recovery tools -available in the same context. - -## Acceptance Criteria - -- `cdp_send` failures caused by an unavailable session browser are delivered to - the agent as tool errors, not as silent hangs or terminal application crashes. -- The tool error is meaningful to both the model and logs. It includes a stable - error code, the attempted CDP method, the conversation/session id, the current - YoBrowser status when available, whether the failure is recoverable, and a - short recovery hint. -- The tool error explicitly tells the agent that it may call - `get_browser_status` to inspect state and `load_url` to recreate or reopen the - session browser when it still has a target URL. If there is no target URL, the - hint allows the agent to ask the user to reopen the panel or continue without - browser verification. -- The agent runtime preserves the failure as an errored tool result so follow-up - model context can see that `cdp_send` failed, while still allowing the model to - choose a recovery strategy. -- Existing successful `cdp_send`, `load_url`, and `get_browser_status` behavior - remains unchanged. -- Non-browser-availability CDP errors, malformed arguments, missing - conversation ids, permission denials, and user cancellation keep their existing - error semantics unless they can be safely wrapped with the same recoverable - browser-unavailable code. -- The implementation avoids leaking Electron stack traces, internal object - dumps, filesystem paths, or private page content in the agent-visible error. -- Unit coverage verifies the unavailable-browser case, the still-successful CDP - case, and runtime propagation of the structured recoverable error into the - tool result. - -## Non-goals - -- Do not automatically reattach or reopen the YoBrowser panel in this first - increment. -- Do not add a new renderer-main browser state synchronization channel unless - implementation proves the existing status APIs are insufficient. -- Do not change the public names of `cdp_send`, `load_url`, or - `get_browser_status`. -- Do not retry CDP commands automatically. The model should decide whether to - retry, reopen, skip, or ask the user based on the tool error and conversation - context. -- Do not introduce UI copy or renderer layout changes for this issue. - -## Constraints - -- The fix should follow the existing Presenter and agent tool routing patterns: - YoBrowser-specific readiness detection belongs near - `YoBrowserPresenter`/`YoBrowserToolHandler`, while tool-result propagation - belongs in the agent tool path. -- Tool outputs are part of the model context, so the error payload must be small, - deterministic, and easy to parse even when prefixed by the runtime's standard - error formatting. -- `get_browser_status` already exposes the primary session state - (`initialized`, `visible`, `loading`, and page information), so the first - implementation should prefer reusing that state over adding broader event - synchronization. - -## Proposed Agent-Visible Error Shape - -The exact TypeScript representation can be refined during implementation, but -the agent-visible content should be equivalent to: - -```json -{ - "ok": false, - "error": { - "code": "yobrowser_unavailable", - "message": "YoBrowser is not available for this session, so the CDP command was not run.", - "recoverable": true, - "sessionId": "", - "method": "Page.captureScreenshot", - "browserStatus": { - "initialized": false, - "visible": false, - "loading": false, - "page": null - }, - "suggestedNextActions": [ - "Call get_browser_status to inspect the current browser state.", - "Call load_url with the target URL to recreate or reopen the session browser.", - "If no URL is available, ask the user to reopen the browser panel or continue without browser verification." - ] - } -} -``` - -## Business Value - -This turns a brittle browser-control failure into an agent-readable recovery -signal. The immediate user impact is fewer stalled browser-assisted tasks after -the panel is closed, while the implementation stays smaller and safer than -automatic recovery because it does not mutate browser visibility on behalf of -the model. diff --git a/docs/spec-driven-dev.md b/docs/spec-driven-dev.md index 38514881a..39f4917e8 100644 --- a/docs/spec-driven-dev.md +++ b/docs/spec-driven-dev.md @@ -11,39 +11,66 @@ In practice, SDD works best when the spec is concrete enough to drive design dec Keep every active change in a lightweight SDD folder so reviewers can find the intent without hunting through code. Use one kebab-case folder per goal: - `docs/features//` - new features, user-visible capabilities, integrations, and tools -- `docs/issues//` - bug fixes, regressions, failing tests, CI failures, reliability issues, and prompt/runtime problems +- `docs/issues//` - small bug fixes, regressions, failing tests, CI failures, reliability issues, and prompt/runtime problems - `docs/architecture//` - refactors, migrations, dependency boundaries, shared contracts, runtime architecture, and cross-module design Pure release metadata work is exempt from SDD. Version bumps, `CHANGELOG.md` updates, release branch management, tags, and release PR preparation should follow `docs/release-flow.md` without creating a release-specific SDD folder. -Each active goal folder contains: +Feature and architecture goals use the full SDD set: - `spec.md` - user stories, acceptance criteria, non-goals, constraints, open questions - `plan.md` - architecture decisions, event flow, data model, compatibility, test strategy - `tasks.md` - small, ordered tasks that map to commits/PRs -If a change is tiny, keep all three files short. +Small bug goals use one file: -After implementation, delete `plan.md` and `tasks.md`. Keep `spec.md` only when it remains a -durable contract, regression guard, platform policy, or architecture decision that helps maintain -current code. +- `spec.md` - issue description, impact, root cause or suspected location, fix plan, task checklist, + validation, and linked GitHub issue if one exists -## Workflow +A bug is small only when the failure is narrow, the owner module is clear, and the fix does not +introduce a new user-visible capability, data migration, public contract, or cross-module redesign. +If it does, classify the work as feature or architecture instead. + +If a change is tiny, keep the artifact short. + +## GitHub Issue Sync -1. **Feature Specification** - Define user stories, acceptance criteria, business value, non-goals -2. **Implementation Plan** - Architecture decisions, event flow, IPC surface, test strategy -3. **Task Breakdown** - Small tasks that can be reviewed independently -4. **Implementation & Validation** - TDD (pragmatic), Presenter patterns, UI consistency, quality gates +For feature and small bug work, create or link a GitHub issue when local `gh` is installed and +authenticated: -Before implementation, inspect existing docs and code, choose the correct SDD folder, and resolve every `[NEEDS CLARIFICATION]` marker. Keep `plan.md` and `tasks.md` active only while they are driving current work. When a goal is implemented, fold durable maintenance facts into the current project docs, keep `spec.md` only if it remains a useful contract, and 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. +- Feature work uses the `[feature]` label. +- Bug work uses the `[bug]` label. +- If the label is missing and `gh` has permission, create it. +- Record the issue URL or number in the SDD artifact. +- If `gh` is unavailable or unauthorized, continue local-only and note that no GitHub issue was + created. -Retention policy: +When opening a PR for linked work, include `Closes #NNN` in the PR body so GitHub closes the issue +after merge. + +## Workflow -- Feature and architecture SDD folders keep `plan.md` and `tasks.md` 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`; keep a spec-only folder when the acceptance criteria still define a useful maintained contract. -- Bug-fix issue SDD folders older than two weeks should be removed unless their `spec.md` still describes a useful regression contract. +1. **Classification** - Choose feature, small bug, or architecture. +2. **Specification** - Write the required artifact set for that classification. +3. **GitHub Sync** - Create or link a labeled GitHub issue for feature and small bug work when + local `gh` is usable. +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. For architecture work that changes or replaces a historical feature, update that feature's retained `spec.md` if it is still a maintained contract. + +Cleanup policy: + +- Do not perform broad SDD cleanup during ordinary feature, bug, or architecture work. +- Use the `deepchat-sdd-cleanup` skill only when a developer explicitly asks to clean, prune, or + organize SDD docs. +- Completed feature/architecture SDD content should become current documentation in `README.md`, + `ARCHITECTURE.md`, `FLOWS.md`, `architecture/*.md`, or `guides/*.md`; keep a spec-only folder + when the acceptance criteria still define a useful maintained contract. +- Completed issue folders may be deleted when a linked GitHub issue is closed or the local code and + tests prove the bug no longer exists. - Long-term history should be recovered from git history, not accumulated under `docs/archives/`. ## Six Core Principles @@ -105,6 +132,7 @@ Use Vitest + Vue Test Utils for testing. Test files mirror source structure unde - [ ] Key UX states covered (loading/empty/error) - [ ] No `[NEEDS CLARIFICATION]` markers remain - [ ] Business value articulated +- [ ] GitHub issue linked or local-only reason recorded for feature and small bug work ### Planning Phase - [ ] Identify all involved Presenters @@ -165,9 +193,10 @@ Compatibility note: ## Definition of Done (DoD) -A feature is “done” when: +A change is “done” when: - The acceptance criteria are met (and ideally covered by tests) - Lint/typecheck/tests pass locally - User-facing strings use i18n keys - Any migrations or breaking changes are documented +- Linked GitHub issues are referenced from the PR with `Closes #NNN` diff --git a/src/main/presenter/skillSyncPresenter/index.ts b/src/main/presenter/skillSyncPresenter/index.ts index 05ba17e12..b34d1317d 100644 --- a/src/main/presenter/skillSyncPresenter/index.ts +++ b/src/main/presenter/skillSyncPresenter/index.ts @@ -819,25 +819,24 @@ export class SkillSyncPresenter implements ISkillSyncPresenter { async previewAdoptAgentSkill(input: AdoptAgentSkillInput): Promise { const adoption = await this.resolveAdoptionSource(input) - const source = await this.readAdoptableSkill(adoption.sourcePath) - if (source.name !== adoption.skill.name) { - throw new Error(`SKILL.md name "${source.name}" does not match "${adoption.skill.name}"`) - } + await this.readAdoptableSkill(adoption.sourcePath) const skillsDir = path.resolve(await this.skillPresenter.getSkillsDir()) const deepchatSkills = await this.skillPresenter.getUnifiedSkillCatalog() const deepchatNames = new Set(deepchatSkills.map((skill) => skill.name)) + const defaultTargetName = adoption.skill.name const hasConflict = - deepchatNames.has(source.name) || (await this.pathExists(path.join(skillsDir, source.name))) + deepchatNames.has(defaultTargetName) || + (await this.pathExists(path.join(skillsDir, defaultTargetName))) const targetName = input.targetName ?? (hasConflict ? await this.generateAdoptionTargetName( - `${source.name}-${input.agentId}`, + `${defaultTargetName}-${input.agentId}`, skillsDir, deepchatNames ) - : source.name) + : defaultTargetName) this.assertValidDeepChatSkillName(targetName) if ( @@ -866,7 +865,8 @@ export class SkillSyncPresenter implements ISkillSyncPresenter { adoption.skill.name ), conflict: hasConflict, - warnings: targetName === source.name ? [] : [`Skill will be adopted as "${targetName}"`] + warnings: + targetName === adoption.skill.name ? [] : [`Skill will be adopted as "${targetName}"`] } } @@ -1223,7 +1223,6 @@ export class SkillSyncPresenter implements ISkillSyncPresenter { const name = typeof parsed.data.name === 'string' ? parsed.data.name.trim() : '' const description = typeof parsed.data.description === 'string' ? parsed.data.description.trim() : '' - this.assertValidDeepChatSkillName(name) if (!description) { throw new Error('Skill description not found in SKILL.md frontmatter') } diff --git a/test/main/presenter/skillSyncPresenter/index.test.ts b/test/main/presenter/skillSyncPresenter/index.test.ts index a161ac14d..21f05cfb0 100644 --- a/test/main/presenter/skillSyncPresenter/index.test.ts +++ b/test/main/presenter/skillSyncPresenter/index.test.ts @@ -1047,6 +1047,50 @@ describe('SkillSyncPresenter', () => { ) }) + it('previews adoption when SKILL.md name is not a valid target name', async () => { + const { toolScanner } = + await import('../../../../src/main/presenter/skillSyncPresenter/toolScanner') + const codexTool = createFolderTool() + vi.mocked(toolScanner.getTool).mockReturnValue(codexTool) + vi.mocked(toolScanner.scanTool).mockResolvedValue({ + toolId: 'codex', + toolName: 'OpenAI Codex', + available: true, + skillsDir: '/home/user/.codex/skills', + skills: [ + { + name: 'native-feel-skill', + path: '/home/user/.codex/skills/native-feel-skill', + format: 'codex', + lastModified: new Date() + } + ] + }) + vi.mocked(fs.promises.readdir).mockResolvedValue([ + createDirent('native-feel-skill', { directory: true }) + ] as any) + vi.mocked(fs.promises.readFile).mockResolvedValue( + '---\nname: Native Feel Skill\ndescription: Native desktop\n---\n# Native' + ) + vi.mocked(fs.promises.access).mockRejectedValue( + Object.assign(new Error('missing'), { code: 'ENOENT' }) + ) + + const preview = await presenter.previewAdoptAgentSkill({ + agentId: 'codex', + skillName: 'native-feel-skill' + }) + + expect(preview).toEqual( + expect.objectContaining({ + skillName: 'native-feel-skill', + targetName: 'native-feel-skill', + conflict: false, + warnings: [] + }) + ) + }) + it('adopts an agent-owned skill through private temp and backup paths', async () => { const { toolScanner } = await import('../../../../src/main/presenter/skillSyncPresenter/toolScanner')