diff --git a/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.i18n.yaml index ec2bd1c1cd..95e1524788 100644 --- a/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md: 036e2f2906ca99aaab30a2164649f9c750b4ad21 -2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md: 15a6f0dd412d142647df2722335a454a028cb798 +2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md: 1a012aeabc7f9445127d6b8edcbe2f72e62f0eba +2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md: 742d2c5ee8573c9b2bdf555c938c83dd6ea9f999 diff --git a/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md b/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md index 036e2f2906..1a012aeabc 100644 --- a/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md +++ b/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md @@ -14,7 +14,7 @@ Three packages hand-roll promise-wrapped timers that the `node:timers/promises` ## Proposal -Replace both with `import { setTimeout } from 'node:timers/promises'`: +Replace all three with `import { setTimeout } from 'node:timers/promises'`: - llm-retry: `try { await setTimeout(delayMs, undefined, { signal }); /* retry */ } catch { /* abort → fail */ }` — with a signal, the promise rejects only with the abort error, and a pre-aborted signal rejects immediately; behavior is identical, including timer clearing on abort. The empty `catch` names the abort rejection per the repo's empty-catch rule. - workflow-workerthread: `setTimeout(ms, undefined, { ref: false })` — exact semantics including not holding the event loop open. @@ -29,8 +29,8 @@ No dedicated tests pin the helpers themselves; the packages' behavior suites kee ## Acceptance criteria -- Neither package defines a promise-wrapped `setTimeout` helper; both import from `node:timers/promises`. -- `llm-retry` and `workflow-workerthread` test suites pass unchanged (behavioral parity). +- None of the three packages defines a promise-wrapped `setTimeout` helper; all import from `node:timers/promises`. +- The `llm-retry`, `workflow-workerthread`, and `pty-local` test suites pass unchanged (behavioral parity). ## Risks diff --git a/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md b/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md index 15a6f0dd41..742d2c5ee8 100644 --- a/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md +++ b/.agents/notes/proposed/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md @@ -14,7 +14,7 @@ Status: proposed ## 提案 -用 `import { setTimeout } from 'node:timers/promises'` 替换上述实现: +用 `import { setTimeout } from 'node:timers/promises'` 替换这三处实现: - llm-retry:`try { await setTimeout(delayMs, undefined, { signal }); /* retry */ } catch { /* abort → fail */ }`。传入 signal 后,该 promise 只会以 abort 错误拒绝,已提前中止的 signal 则立即拒绝;行为完全一致,包括中止时清除定时器。按仓库的空 catch 规则,这个空 `catch` 注明其吞下的是 abort 拒绝。 - workflow-workerthread:`setTimeout(ms, undefined, { ref: false })`,语义完全等价,包括不会让事件循环保持存活。 @@ -29,8 +29,8 @@ Status: proposed ## 验收标准 -- 上述包不再各自定义 promise 包装的 `setTimeout` 辅助函数,而是都从 `node:timers/promises` 导入。 -- `llm-retry` 与 `workflow-workerthread` 的测试套件原样通过(行为等价)。 +- 这三个包都不再各自定义 promise 包装的 `setTimeout` 辅助函数,而是都从 `node:timers/promises` 导入。 +- `llm-retry`、`workflow-workerthread` 与 `pty-local` 的测试套件原样通过(行为等价)。 ## 风险 diff --git a/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.i18n.yaml b/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.i18n.yaml index d950040b37..90cad79b89 100644 --- a/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.i18n.yaml +++ b/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-26-execa-for-test-subprocess-plumbing.md: 3b2ba9062a72dfe03c9e9a84fa13fe23da39302a -2026-07-26-execa-for-test-subprocess-plumbing.zh.md: 61e12233fc49ca7788882ca409d6f67f030d2475 +2026-07-26-execa-for-test-subprocess-plumbing.md: 99a86258fe4d59db6a0e144dbcee94c095f70f8f +2026-07-26-execa-for-test-subprocess-plumbing.zh.md: 525e09f07ce3e5dc61f1cadab5c11ea0790cccee diff --git a/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.md b/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.md index 3b2ba9062a..99a86258fe 100644 --- a/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.md +++ b/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.md @@ -10,7 +10,7 @@ Roughly ten e2e/smoke files re-derive the same spawn-collect-timeout choreograph Two related test-infra hand-rolls compound the case: -- `packages/support/llm-mock-server/src/cli.ts` hand-tokenizes 18 `--flag value` options (~45–60 lines of loop and value-extraction helpers) where the `node:util` `parseArgs` builtin is already the repo idiom (`cli-demo`, `acp-demo`, `verify-runtime-closure.ts`, `packages/sdk/scripts`). +- `packages/support/llm-mock-server/src/cli.ts` hand-tokenizes 17 value-taking `--flag value` options plus boolean flags (~45–60 lines of loop and value-extraction helpers) where the `node:util` `parseArgs` builtin is already the repo idiom (`cli-demo`, `acp-demo`, `verify-runtime-closure.ts`, `packages/sdk/scripts`). - `apps/web/tests/smoke-real.e2e.ts` and `apps/web/tests/scaffold.ts` carry two verbatim copies of a regex `.env` parser (~20 lines) where the `process.loadEnvFile` builtin has exactly the required no-override semantics — and the vitest e2e/snapshot/web configs already load root `.env` with it before these files run, making the copies arguably dead. - The snapshot harness hand-rolls three poll-until-deadline loops (`waitForPersistedTurnStart`/`waitForPersistedTurnEnd`/`waitForWorkspaceFile` in `packages/support/acp-snapshot/src/harness.ts`, ~55 lines) plus `waitForFile` in `crash-recovery.e2e.ts`, where `vi.waitFor`/`expect.poll` cover the shape — vitest is already a runtime dependency of `dsh-acp-snapshot`, so this adds nothing. diff --git a/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md b/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md index 61e12233fc..525e09f07c 100644 --- a/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md +++ b/.agents/notes/proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md @@ -10,7 +10,7 @@ Status: proposed 另有两处相关的测试基础设施手写代码进一步强化了替换的理由: -- `packages/support/llm-mock-server/src/cli.ts` 手工逐个切分 18 个 `--flag value` 选项(约 45–60 行的循环与取值辅助函数),而 `node:util` 内置的 `parseArgs` 早已是本仓库的惯用写法(`cli-demo`、`acp-demo`、`verify-runtime-closure.ts`、`packages/sdk/scripts`)。 +- `packages/support/llm-mock-server/src/cli.ts` 手工逐个切分 17 个带值的 `--flag value` 选项外加若干布尔标志(约 45–60 行的循环与取值辅助函数),而 `node:util` 内置的 `parseArgs` 早已是本仓库的惯用写法(`cli-demo`、`acp-demo`、`verify-runtime-closure.ts`、`packages/sdk/scripts`)。 - `apps/web/tests/smoke-real.e2e.ts` 与 `apps/web/tests/scaffold.ts` 携带两份逐字相同的正则 `.env` 解析器拷贝(约 20 行),而内置的 `process.loadEnvFile` 恰好具备所需的「不覆盖已有值」语义;并且 vitest 的 e2e/snapshot/web 配置在这些文件运行之前就已用它加载了根 `.env`,这两份拷贝几乎可以视为死代码。 - 快照 harness 手写了三个「轮询直到截止时间」的循环(`packages/support/acp-snapshot/src/harness.ts` 中的 `waitForPersistedTurnStart`/`waitForPersistedTurnEnd`/`waitForWorkspaceFile`,约 55 行),外加 `crash-recovery.e2e.ts` 中的 `waitForFile`,而 `vi.waitFor`/`expect.poll` 正好覆盖这种形态;vitest 本来就是 `dsh-acp-snapshot` 的运行时依赖,因此这不新增任何东西。 diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml index 9310becba4..8749dbd0bc 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-26-dependency-swaps-rejected-by-nih-audit.md: 6ee4ce36bcc25b206eebedd18270021e4937761f -2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: b983dfcfa12171bfe1ae9bc79936d3a5876e5e68 +2026-07-26-dependency-swaps-rejected-by-nih-audit.md: c988ca0c75e9c50686551f3be1971d736b971e2a +2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: e85161cb2ee616d388aa2a9dd065c315c60cd44a diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md index 6ee4ce36bc..c988ca0c75 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md @@ -14,7 +14,7 @@ Adopt the following dependency swaps. Rejected — per-item evidence below; a fu **Protocol and parsing:** -- **`vscode-jsonrpc` for LSP base-protocol framing/correlation** (`lsp-local`): the swappable core is ~255 of 2,112 src lines; the package cannot express the configured `maxMessageBytes` incoming-size bound (restoring it means rebuilding the deleted framing), inverts the cancel-grace teardown semantics (`raceAbort` rejects immediately then tears down; vscode-jsonrpc keeps the promise pending), errors on pre-header stdout banners real servers emit, and is CJS in an ESM-everywhere repo. The [LSP seam note](../../implemented/architecture/2026-07-15-lsp-capability-seam.md) assigns JSON-RPC ownership to `dsh-lsp-local`; this audit is the explicit on-record weighing of the dependency it lacked. +- **`vscode-jsonrpc` for LSP base-protocol framing/correlation** (`lsp-local`): the swappable core is ~255 of ~1,800 src lines; the package cannot express the configured `maxMessageBytes` incoming-size bound (restoring it means rebuilding the deleted framing), inverts the cancel-grace teardown semantics (`raceAbort` rejects immediately then tears down; vscode-jsonrpc keeps the promise pending), errors on pre-header stdout banners real servers emit, and is CJS in an ESM-everywhere repo. The [LSP seam note](../../implemented/architecture/2026-07-15-lsp-capability-seam.md) assigns JSON-RPC ownership to `dsh-lsp-local`; this audit is the explicit on-record weighing of the dependency it lacked. - **`vscode-languageserver-types` for lsp-local's wire-type subset**: ~80 type lines and ~45 guard lines, but upstream guards differ in both directions (accept `uri: undefined` the repo must reject; require `targetRange` the repo tolerates absent), and the initialize-result shapes live in `vscode-languageserver-protocol`, dragging `vscode-jsonrpc` in as a runtime dep — ~1 MB for 80 spec-exact lines. - **`json-rpc-2.0` for `dsh-jsonrpc`**: deletable correlation/dispatch is real (~100–130 lines) but the NDJSON wire must stay bit-identical for the hand-rolled Python SDK client, the package is single-maintainer, and the [GUI RPC note](../../implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md) already treats this package as a frozen narrow surface. `vscode-jsonrpc` is a worse fit still (Content-Length framing, cancellation vocabulary the protocol lacks). - **`jsonrpcclient` for the Python SDK client**: v4 builds/parses messages only — ~20 lines — while the 500 lines that matter (subprocess lifecycle, threaded reader, id correlation, bidirectional server-role responses) stay; the library is in low-maintenance mode. diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md index b983dfcfa1..e85161cb2e 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md @@ -14,7 +14,7 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门 **协议与解析:** -- **以 `vscode-jsonrpc` 承担 LSP 基础协议的分帧/关联**(`lsp-local`):可替换的核心只占 src 全部 2,112 行中的约 255 行;该包无法表达可配置的 `maxMessageBytes` 入站大小上限(要恢复它就得重建被删掉的分帧代码),反转了取消宽限期的拆除语义(`raceAbort` 立即 reject 再拆除;vscode-jsonrpc 让 promise 保持挂起),会在真实服务器输出的 header 前 stdout 横幅上报错,而且在这个 ESM 通行的仓库里它是 CJS。[LSP seam 决策](../../implemented/architecture/2026-07-15-lsp-capability-seam.md)把 JSON-RPC 的所有权划给 `dsh-lsp-local`;本次审计正是对该决策当时缺失的这项依赖权衡的明文记录。 +- **以 `vscode-jsonrpc` 承担 LSP 基础协议的分帧/关联**(`lsp-local`):可替换的核心只占 src 约 1,800 行中的约 255 行;该包无法表达可配置的 `maxMessageBytes` 入站大小上限(要恢复它就得重建被删掉的分帧代码),反转了取消宽限期的拆除语义(`raceAbort` 立即 reject 再拆除;vscode-jsonrpc 让 promise 保持挂起),会在真实服务器输出的 header 前 stdout 横幅上报错,而且在这个 ESM 通行的仓库里它是 CJS。[LSP seam 决策](../../implemented/architecture/2026-07-15-lsp-capability-seam.md)把 JSON-RPC 的所有权划给 `dsh-lsp-local`;本次审计正是对该决策当时缺失的这项依赖权衡的明文记录。 - **以 `vscode-languageserver-types` 承担 lsp-local 的协议类型子集**:约 80 行类型加约 45 行守卫,但上游守卫在两个方向上都与本仓库不一致(接受本仓库必须拒绝的 `uri: undefined`;强制要求本仓库容忍缺失的 `targetRange`),而且 initialize 结果的形状住在 `vscode-languageserver-protocol` 里,会把 `vscode-jsonrpc` 拖成运行时依赖——为 80 行严格贴合规范的代码付出约 1 MB。 - **以 `json-rpc-2.0` 替换 `dsh-jsonrpc`**:可删除的关联/分发代码确实存在(约 100–130 行),但 NDJSON 协议格式(wire format)必须与手写的 Python SDK 客户端逐位一致,该包只有单一维护者,且 [GUI RPC 决策](../../implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)已把这个包当作冻结的窄接口面对待。`vscode-jsonrpc` 更不合适(Content-Length 分帧、该协议并不具备的取消词汇)。 - **以 `jsonrpcclient` 承担 Python SDK 客户端**:v4 只做消息的构造/解析——约 20 行——而真正要紧的 500 行(子进程生命周期、线程化读取器、id 关联、双向的服务端角色应答)全都保留;该库处于低维护模式。 diff --git a/AGENTS.md b/AGENTS.md index dcb73cb3b8..f56c162b52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ DeepSeek Harness SDK is a plugin-based agent harness on vendored Cordis: **every vendor/ Vendored Cordis source — manifest + sync procedure in vendor/README.md packages/ @deepseek-ai/dsh- workspaces at packages/// core/ product API spine: session, system-prompt, tools, agent, agent-loop - llm/ LLM seam + the DeepSeek adapters (hand-rolled + pi-ai design twin) + llm/ LLM seam + DeepSeek adapters (hand-rolled + pi-ai design twin) bash/ bash executor seam + local impl + model-facing bash tools pty/ persistent PTY seam/backend/tools fs/ filesystem seam + local impl + policy gate + read/write/edit tools @@ -22,17 +22,17 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// compact/ compaction seam + basic backend context/ request-context plugins subagent/ subagent seam + spawn/fork/ACP backends + delegation tool - workflow/ workflow seam + worker-thread engine + the workflow tool - todo/ the todo_write tool + workflow/ workflow seam + worker-thread engine + workflow tool + todo/ todo_write tool plan/ plan mode as logged per-agent collaboration state guard/ loop-hygiene plugins cordis/ self-referential toolset: the agent inspects/mounts plugins in its own runtime - hooks/ Claude Code / Codex hook bridges + shared wire-protocol library + hooks/ Claude Code/Codex hook bridges + shared wire-protocol library session-persistence/ persistence seam + JSONL/SQLite backends acp/ automation-only Agent Client Protocol server ui/ TUI/JSON-RPC bridges; boot, approval, interaction plugins examples/ demo bundles (agent-spine + TUI/CLI/ACP/JSON-RPC bins) leaves load - support/ dev/test infrastructure packages + support/ dev/test infrastructure util/ zero-dependency utilities python/ Python SDK and bundled runtime (see python/README.md) native/ node-addon-landlock-run source of record (see native/README.md) @@ -60,11 +60,11 @@ pnpm run lint pnpm run duplication # cross-file TypeScript clone detection pnpm run build # tsc emits lib/types, tsdown bundles runtime pnpm run hygiene # knip + publint + workspace constraints + NodeNext consumer check -pnpm run doc-sync # all documentation gates; see the doc-sync leaf list in scripts/run-gates.ts -pnpm run website:build # VitePress build (doubles as the site's dead-link check) +pnpm run doc-sync # all documentation gates; leaf list in scripts/run-gates.ts +pnpm run website:build # VitePress build (doubles as dead-link check) pnpm run demo:headless "task" # one-shot agent (needs DEEPSEEK_API_KEY) pnpm run demo:tui # full-screen TUI coding agent (needs DEEPSEEK_API_KEY) -pnpm run demo:cordis # self-referential demo: the agent modifies its own runtime (needs key) +pnpm run demo:cordis # the agent modifies its own runtime (needs key) pnpm run demo:acp # ACP automation server (needs DEEPSEEK_API_KEY) ``` @@ -96,7 +96,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, - **Model-visible ⟺ logged**: anything that reaches a model request must be reconstructable from the session log; a new model-visible input requires a session event. - **Plugins, not loop changes**: new behavior goes on the documented extension seams; changing `agent-loop` requires updating docs/architecture.md. - **Capability seams are three packages** — interface / implementation / consumer; don't split preemptively. -- **Prefer maintained dependencies over hand-rolling** when the swap genuinely deletes owned code and tests ([policy](.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md)). +- **Prefer maintained dependencies over hand-rolling** when they genuinely delete owned code and tests ([policy](.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md)). - **Explicit > implicit at package seams**: defaulting is an explicit `resolve(request): Spec` step in the owning implementation, never a hidden `?? default` inside `run()` (the `dsh-bash` request/spec split is the template). - **No hardcoded tunables in plugins**: deployment-varying choices are validated `Config` fields changeable from cordis.yml; a `DEFAULT_*` constant or test seam is not configurability. Protocol constants, external specs, and security invariants stay fixed. - **Misconfiguration fails loud** at load when self-contained, otherwise at the earliest resolvable point; never silently skip a missing referent. diff --git a/scripts/doc-budgets.manifest.json b/scripts/doc-budgets.manifest.json index 349aa12acb..3d0ce17051 100644 --- a/scripts/doc-budgets.manifest.json +++ b/scripts/doc-budgets.manifest.json @@ -1,5 +1,5 @@ { - "AGENTS.md": 1700, + "AGENTS.md": 1680, "docs/AGENTS.md": 1150, "docs/architecture.md": 1800, "docs/cordis-primer.md": 600,