From d8dbb8235c4b49e7204cb806b3789fe502fb5b4e Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 23 Aug 2026 01:45:44 +0800 Subject: [PATCH] refactor(acp): launch automation through the dsh acp profile Replace the standalone @deepseek-ai/dsh-acp-demo application with dsh --profile acp plus ordered example patches. The shipped acp-app bundle owns only the protocol bridge; every example overlay now targets shared dsh-base rows instead of copying a complete application tree. Move launcher responsibilities into the ACP snapshot harness: it materializes profile patches, links required packages, reserves stdout for JSON-RPC, observes spawn and drain failures, and escalates process teardown deterministically. The relocated control-surface fixture and the ACP/subagent integration tests now exercise the real CLI/profile path. This commit contains authored runtime, configuration, and test changes only. Generated transcript and projection churn is deliberately left for the next commit so reviewers can inspect the migration logic without hundreds of expected-output edits. --- ...-20-remove-stdio-and-echo-agents.i18n.yaml | 4 +- ...2026-07-20-remove-stdio-and-echo-agents.md | 2 +- ...6-07-20-remove-stdio-and-echo-agents.zh.md | 2 +- docs/cookbook/extension-cookbook.i18n.yaml | 4 +- docs/cookbook/extension-cookbook.md | 2 +- docs/cookbook/extension-cookbook.zh.md | 2 +- examples/acp-agent/README.i18n.yaml | 4 +- examples/acp-agent/README.md | 4 +- examples/acp-agent/README.zh.md | 4 +- .../acp-agent/advanced.cordis.snapshot.yml | 87 +++--- examples/acp-agent/advanced.cordis.yml | 60 ++-- .../agent-instructions.cordis.snapshot.yml | 83 +++--- .../acp-agent/agent-instructions.cordis.yml | 49 ++-- ...ckground-job-admission.cordis.snapshot.yml | 75 +++-- .../background-job-admission.cordis.yml | 45 +-- .../acp-agent/both-mode.cordis.snapshot.yml | 84 +++--- examples/acp-agent/both-mode.cordis.yml | 57 ++-- .../child-question.cordis.snapshot.yml | 102 ++++--- examples/acp-agent/child-question.cordis.yml | 20 +- .../code-mode-image.cordis.snapshot.yml | 92 +++--- examples/acp-agent/code-mode-image.cordis.yml | 57 ++-- ...mode-workspace-context.cordis.snapshot.yml | 79 ++--- .../code-mode-workspace-context.cordis.yml | 52 ++-- .../acp-agent/code-mode.cordis.snapshot.yml | 84 +++--- examples/acp-agent/code-mode.cordis.yml | 58 ++-- examples/acp-agent/cordis-tools.cordis.yml | 15 +- examples/acp-agent/cordis.snapshot.yml | 103 +++---- examples/acp-agent/cordis.yml | 175 ++--------- .../acp-agent/depth-two.cordis.snapshot.yml | 107 +++---- examples/acp-agent/depth-two.cordis.yml | 17 +- examples/acp-agent/fs.cordis.snapshot.yml | 95 +++--- examples/acp-agent/fs.cordis.yml | 24 +- .../image-text-route.cordis.snapshot.yml | 77 ++--- .../acp-agent/image-text-route.cordis.yml | 50 ++-- examples/acp-agent/image.cordis.snapshot.yml | 87 +++--- examples/acp-agent/image.cordis.yml | 49 ++-- .../partial-landlock.cordis.snapshot.yml | 79 ++--- .../acp-agent/partial-landlock.cordis.yml | 18 +- .../product-subagent-both.cordis.snapshot.yml | 120 ++++---- .../product-subagent-both.cordis.yml | 95 +++--- ...product-subagent-codex.cordis.snapshot.yml | 76 +++-- .../product-subagent-codex.cordis.yml | 51 ++-- examples/acp-agent/pty.cordis.snapshot.yml | 48 ++- examples/acp-agent/pty.cordis.yml | 33 +-- examples/acp-agent/retry.cordis.snapshot.yml | 82 +++--- examples/acp-agent/retry.cordis.yml | 69 +++-- .../session-query.cordis.snapshot.yml | 68 ++++- examples/acp-agent/session-query.cordis.yml | 30 +- .../session-sandbox-root.cordis.snapshot.yml | 106 +++---- .../acp-agent/session-sandbox-root.cordis.yml | 13 +- .../session-title.cordis.snapshot.yml | 106 +++---- examples/acp-agent/session-title.cordis.yml | 27 +- ...ontinuable-inheritance.cordis.snapshot.yml | 93 +++--- ...ubagent-continuable-inheritance.cordis.yml | 11 +- ...ent-durability-failure.cordis.snapshot.yml | 93 +++--- .../subagent-durability-failure.cordis.yml | 11 +- .../subagent-report.cordis.snapshot.yml | 92 +++--- examples/acp-agent/subagent-report.cordis.yml | 10 +- ...gent-result-diagnostic.cordis.snapshot.yml | 50 ++-- .../subagent-result-diagnostic.cordis.yml | 25 +- examples/acp-agent/tests/acp.e2e.ts | 3 +- examples/acp-agent/tests/acp.snapshot.ts | 12 +- .../acp-agent/tests/control-surface.e2e.ts | 14 +- examples/acp-agent/tests/escalation.e2e.ts | 5 +- .../tests/fixtures/control-surface/cordis.yml | 26 +- .../tests/fixtures/image-offload.cordis.yml | 71 +++-- .../tests/fs-diff-bound.cordis.snapshot.yml | 97 +++--- .../acp-agent/tests/fs-diff-bound.cordis.yml | 49 ++-- .../tests/fs-search.cordis.snapshot.yml | 86 ++++-- examples/acp-agent/tests/fs-search.cordis.yml | 65 +++- examples/acp-agent/tests/goal.snapshot.ts | 3 +- examples/acp-agent/tests/hooks.e2e.ts | 3 +- .../acp-agent/tests/lsp.cordis.snapshot.yml | 67 ++--- examples/acp-agent/tests/lsp.cordis.yml | 42 ++- .../tests/persistent-pwsh.cordis.snapshot.yml | 104 +++++-- .../tests/persistent-pwsh.cordis.yml | 83 ++++-- .../acp-agent/tests/pwsh.cordis.snapshot.yml | 91 ++++-- examples/acp-agent/tests/pwsh.cordis.yml | 70 ++++- examples/acp-agent/web.cordis.snapshot.yml | 52 ++-- examples/acp-agent/web.cordis.yml | 27 +- packages/boot/app-boot/README.i18n.yaml | 4 +- packages/boot/app-boot/README.md | 2 +- packages/boot/app-boot/README.zh.md | 2 +- packages/boot/app-boot/src/index.ts | 2 +- packages/bundle/acp-app/README.i18n.yaml | 4 +- packages/bundle/acp-app/README.md | 8 +- packages/bundle/acp-app/README.zh.md | 8 +- packages/bundle/acp-app/cordis.patch.yml | 3 + packages/bundle/acp-app/tests/acp-app.spec.ts | 1 + packages/examples/acp-demo/README.i18n.yaml | 6 - packages/examples/acp-demo/README.md | 65 ---- packages/examples/acp-demo/README.zh.md | 65 ---- packages/examples/acp-demo/package.json | 79 ----- packages/examples/acp-demo/src/bin.ts | 35 --- packages/examples/acp-demo/src/index.ts | 141 --------- packages/examples/acp-demo/src/invariant.ts | 30 -- .../examples/acp-demo/tests/acp-agent.spec.ts | 277 ------------------ .../examples/acp-demo/tests/built-bin.e2e.ts | 242 --------------- .../examples/acp-demo/tests/load-path.e2e.ts | 133 --------- packages/examples/acp-demo/tsconfig.json | 51 ---- packages/examples/acp-demo/tsdown.config.ts | 19 -- .../subagent/subagent-acp/README.i18n.yaml | 4 +- packages/subagent/subagent-acp/README.md | 7 +- packages/subagent/subagent-acp/README.zh.md | 7 +- .../subagent-acp/tests/subagent-acp.e2e.ts | 34 ++- .../acp-snapshot/README.i18n.yaml | 4 +- packages/test-support/acp-snapshot/README.md | 9 +- .../test-support/acp-snapshot/README.zh.md | 9 +- .../test-support/acp-snapshot/package.json | 5 +- .../test-support/acp-snapshot/src/harness.ts | 9 +- .../test-support/acp-snapshot/src/launcher.ts | 121 +++++++- .../test-support/acp-snapshot/src/suite.ts | 13 +- .../acp-snapshot/tests/harness.spec.ts | 115 +++++++- .../test-support/acp-snapshot/tsconfig.json | 3 + .../test-support/loader-smoke/src/index.ts | 6 +- .../loader-smoke/tests/example-launch.spec.ts | 25 +- scripts/demo-code-mode.mjs | 10 +- scripts/demo-cordis.mjs | 8 +- 118 files changed, 2685 insertions(+), 3168 deletions(-) delete mode 100644 packages/examples/acp-demo/README.i18n.yaml delete mode 100644 packages/examples/acp-demo/README.md delete mode 100644 packages/examples/acp-demo/README.zh.md delete mode 100644 packages/examples/acp-demo/package.json delete mode 100644 packages/examples/acp-demo/src/bin.ts delete mode 100644 packages/examples/acp-demo/src/index.ts delete mode 100644 packages/examples/acp-demo/src/invariant.ts delete mode 100644 packages/examples/acp-demo/tests/acp-agent.spec.ts delete mode 100644 packages/examples/acp-demo/tests/built-bin.e2e.ts delete mode 100644 packages/examples/acp-demo/tests/load-path.e2e.ts delete mode 100644 packages/examples/acp-demo/tsconfig.json delete mode 100644 packages/examples/acp-demo/tsdown.config.ts diff --git a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml index 275cfc5331..e98cb973bd 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.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 .agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md -2026-07-20-remove-stdio-and-echo-agents.md: 8761c360e492d6d315e738ed93441929584d1e20 -2026-07-20-remove-stdio-and-echo-agents.zh.md: e34672ce9739ab22e76e44c305c22efb30b6a0c4 +2026-07-20-remove-stdio-and-echo-agents.md: 1d71b74fda229f7eaed502b3badba2ab39e5ea54 +2026-07-20-remove-stdio-and-echo-agents.zh.md: 8445a82d7e366bf57ea4d3ad4595a596b67f1fa8 diff --git a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md index 8761c360e4..1d71b74fda 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md +++ b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md @@ -20,7 +20,7 @@ The remaining application roles are explicit: - `@deepseek-ai/dsh-tui` owns terminal-interactive execution. It rejects non-TTY streams before Loader boot; `apps/cli/config/base.cordis.yml` plus the `tui.cordis.yml` overlay own the complete coding composition, with PTY plus terminal-snapshot coverage in `apps/cli/tests/`. - [`dsh --profile headless`](../../../../apps/cli/README.md) owns non-interactive execution. Its `headless` profile is the product composition; `examples/headless-agent` owns replay snapshots, generic real-agent suites, and an unexported keyless Loader driver. -- [`@deepseek-ai/dsh-acp-demo`](../../../../packages/examples/acp-demo/README.md) and `@deepseek-ai/dsh-sdk-jsonrpc-server` own their framed protocol integrations. +- [`dsh --profile acp`](../../../../apps/cli/README.md) and `@deepseek-ai/dsh-sdk-jsonrpc-server` own their framed protocol integrations. The SDK project model that carried the `stdio` run-interface option is deleted by the [SDK project toolchain removal](2026-08-11-remove-sdk-project-toolchain.md). Repository-facing demo documentation requires a DeepSeek API key and leads with a current runnable product. diff --git a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md index e34672ce97..8445a82d7e 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md @@ -20,7 +20,7 @@ DeepSeek Harness 在 TUI 和 Headless coding agent 之外,还提供了两个 - `@deepseek-ai/dsh-tui` 负责终端交互式执行。它会在 Loader 启动前拒绝非 TTY 流;`apps/cli/config/base.cordis.yml` 与 `tui.cordis.yml` overlay 拥有完整 coding 组装,PTY 与终端快照覆盖则位于 `apps/cli/tests/`。 - [`dsh --profile headless`](../../../../apps/cli/README.zh.md) 负责非交互式执行。其 `headless` profile 是产品组装;`examples/headless-agent` 负责回放快照、通用真实 agent 测试套件和未导出的无密钥 Loader driver。 -- [`@deepseek-ai/dsh-acp-demo`](../../../../packages/examples/acp-demo/README.zh.md) 和 `@deepseek-ai/dsh-sdk-jsonrpc-server` 负责各自的分帧协议集成。 +- [`dsh --profile acp`](../../../../apps/cli/README.zh.md) 和 `@deepseek-ai/dsh-sdk-jsonrpc-server` 负责各自的分帧协议集成。 承载 `stdio` 运行接口选项的 SDK 项目模型已由 [SDK 项目工具链移除决策](2026-08-11-remove-sdk-project-toolchain.zh.md)删除。仓库中的演示文档要求 DeepSeek API key,并优先引导到当前可运行的产品。 diff --git a/docs/cookbook/extension-cookbook.i18n.yaml b/docs/cookbook/extension-cookbook.i18n.yaml index 842bb51396..f0a5136bf2 100644 --- a/docs/cookbook/extension-cookbook.i18n.yaml +++ b/docs/cookbook/extension-cookbook.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 docs/cookbook/extension-cookbook.md -extension-cookbook.md: 9618a3522c5566636fe3e49f7eca93d1e113d51a -extension-cookbook.zh.md: 665968f0a91c05d124b9985c61bdf89c4e10cefa +extension-cookbook.md: 1081166674cb946675d8864091c50def018f9f97 +extension-cookbook.zh.md: defdda53e8f8ea849d2f236566bef48751f4ad96 diff --git a/docs/cookbook/extension-cookbook.md b/docs/cookbook/extension-cookbook.md index 9618a3522c..1081166674 100644 --- a/docs/cookbook/extension-cookbook.md +++ b/docs/cookbook/extension-cookbook.md @@ -90,7 +90,7 @@ export function apply(ctx: Context) { ## Runnable wirings -Runnable leaves load their plugin trees from `examples/*/cordis.yml`; the root `demo:*` scripts and those leaf directories are the authoritative inventory. The product `dsh` launcher owns Web and one-shot headless execution, ACP leaves use [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and JSON-RPC leaves use [`@deepseek-ai/dsh-sdk-jsonrpc-demo`](../../packages/examples/jsonrpc-demo). The headless snapshot leaf mounts [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) and JSONL persistence explicitly, then drives them through an example-owned test fixture rather than a shipped app package. +Runnable leaves contribute profile patches from `examples/*/cordis.yml`; the root `demo:*` scripts and those leaf directories are the authoritative inventory. The product `dsh` launcher owns Web, ACP, SDK, and one-shot headless execution through named profiles. The JSON-RPC leaf remains only for the temporarily held-back Python SDK runtime. The headless snapshot leaf mounts [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) and JSONL persistence explicitly, then drives them through an example-owned test fixture rather than a shipped app package. ## The feature → mechanism map diff --git a/docs/cookbook/extension-cookbook.zh.md b/docs/cookbook/extension-cookbook.zh.md index 665968f0a9..defdda53e8 100644 --- a/docs/cookbook/extension-cookbook.zh.md +++ b/docs/cookbook/extension-cookbook.zh.md @@ -92,7 +92,7 @@ export function apply(ctx: Context) { ## 可运行的组装示例 -可运行叶子从 `examples/*/cordis.yml` 加载各自的插件树;根目录的 `demo:*` 脚本和这些叶子目录是权威清单。产品 `dsh` 启动器负责 Web 和一次性 headless 执行,ACP 叶子使用 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),JSON-RPC 叶子使用 [`@deepseek-ai/dsh-sdk-jsonrpc-demo`](../../packages/examples/jsonrpc-demo)。headless 快照叶节点显式挂载 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 和 JSONL 持久化,再通过示例自有的测试 fixture(测试前置数据)驱动这些组件,而不是通过已交付的 app 包。 +可运行叶子通过 `examples/*/cordis.yml` 贡献 profile patch;根目录的 `demo:*` 脚本和这些叶子目录是权威清单。产品 `dsh` 启动器通过具名 profile 负责 Web、ACP、SDK 与一次性 headless 执行。JSON-RPC 叶子只为暂缓迁移的 Python SDK runtime 保留。headless 快照叶节点显式挂载 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 和 JSONL 持久化,再通过示例自有的测试 fixture(测试前置数据)驱动这些组件,而不是通过已交付的 app 包。 diff --git a/examples/acp-agent/README.i18n.yaml b/examples/acp-agent/README.i18n.yaml index a9b6640194..b8aa6f6acd 100644 --- a/examples/acp-agent/README.i18n.yaml +++ b/examples/acp-agent/README.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 examples/acp-agent/README.md -README.md: 61c6efafe9dde4f91385beebdfd426c57006187b -README.zh.md: c8cdd248e1e71626aac007a8a6923ae7382ea691 +README.md: 81545a8b3a631653256c6f3c6688ba97136b5db1 +README.zh.md: 12a40cde8c1e9f063b32da7080bafed68fed705c diff --git a/examples/acp-agent/README.md b/examples/acp-agent/README.md index 61c6efafe9..81545a8b3a 100644 --- a/examples/acp-agent/README.md +++ b/examples/acp-agent/README.md @@ -9,11 +9,11 @@ pnpm run demo:acp # needs DEEPSEEK_API_KEY (repo-root .env or env) pnpm run demo:code-mode # same protocol with the Code Mode tool transport ``` -The leaf loads the ACP app, DeepSeek adapter, sandboxed bash and filesystem stacks, one-shot approval policy, compaction, subagents, workflows, hooks, a derived session-query index, and repeat guard. The app creates one fresh agent per `session/new`, persists sessions to JSONL, and keeps stdout protocol-pure. Optional overlays add session queries, filesystem spill storage, Code Mode, or web fetching. +The `dsh` launcher applies the shipped `acp` profile (`dsh-base` plus [`dsh-acp-app`](../../packages/bundle/acp-app/README.md)), then this leaf's `cordis.yml` patch. The profile supplies the ACP bridge, DeepSeek adapter, sandboxed shell and filesystem stacks, approval policy, compaction, subagents, workflows, a session-query index, and repeat guard; the leaf pins demo and snapshot values and adds hook bridges. The bridge creates one fresh agent per `session/new`, persists sessions to JSONL, and keeps stdout protocol-pure. Optional patch overlays add session-query tools, spill settings, Code Mode, or web fetching. ## Protocol channel -Stdout carries only newline-delimited ACP JSON-RPC. `@deepseek-ai/dsh-acp-demo` installs no stdout logger; leaf additions must use stderr for diagnostics. +Stdout carries only newline-delimited ACP JSON-RPC. `@deepseek-ai/dsh-acp-app` and this patch install no stdout logger; added plugins must use stderr for diagnostics. The automation contract — supported methods, baseline prompt content, committed-text output, and the intentionally absent UI surfaces — lives in [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.md). diff --git a/examples/acp-agent/README.zh.md b/examples/acp-agent/README.zh.md index c8cdd248e1..12a40cde8c 100644 --- a/examples/acp-agent/README.zh.md +++ b/examples/acp-agent/README.zh.md @@ -9,11 +9,11 @@ pnpm run demo:acp # needs DEEPSEEK_API_KEY (repo-root .env or env) pnpm run demo:code-mode # same protocol with the Code Mode tool transport ``` -该叶节点加载 ACP 应用、DeepSeek 适配器、受沙箱限制的 bash 与文件系统栈、一次性批准策略、压缩(compaction)、subagent、工作流、钩子、派生会话查询索引和重复守卫。应用为每次 `session/new` 创建一个新 agent,将会话持久化到 JSONL,并保持 stdout 只含协议内容。可选 overlay 可添加会话查询、文件系统 spill 存储、Code Mode 或 Web 抓取。 +`dsh` 启动器先应用随附 `acp` profile(`dsh-base` 加 [`dsh-acp-app`](../../packages/bundle/acp-app/README.zh.md)),再应用本叶节点的 `cordis.yml` patch。profile 提供 ACP bridge、DeepSeek 适配器、受沙箱限制的 shell 与文件系统栈、批准策略、压缩(compaction)、subagent、工作流、会话查询索引和重复守卫;本叶节点固定 demo 与 snapshot 值并添加 hook bridge。bridge 为每次 `session/new` 创建一个新 agent,将会话持久化到 JSONL,并保持 stdout 只含协议内容。可选 patch overlay 可添加会话查询工具、spill 设置、Code Mode 或 Web 抓取。 ## 协议通道 -Stdout 只携带以换行分隔的 ACP JSON-RPC。`@deepseek-ai/dsh-acp-demo` 不安装 stdout logger;该叶节点新增的组件必须使用 stderr 输出诊断信息。 +Stdout 只携带以换行分隔的 ACP JSON-RPC。`@deepseek-ai/dsh-acp-app` 与本 patch 均不安装 stdout logger;新增插件必须使用 stderr 输出诊断信息。 自动化约定(支持的方法、基线提示词内容、已提交文本输出,以及有意缺少的 UI 界面)位于 [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.zh.md)。 diff --git a/examples/acp-agent/advanced.cordis.snapshot.yml b/examples/acp-agent/advanced.cordis.snapshot.yml index 63b1c20547..74a0013431 100644 --- a/examples/acp-agent/advanced.cordis.snapshot.yml +++ b/examples/acp-agent/advanced.cordis.snapshot.yml @@ -1,40 +1,51 @@ # Replay counterpart to advanced.cordis.yml; only the live model is replaced. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: 'none' - workspaceContext: - maxBytes: 65536 - tools: - mode: both - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' - - id: cordis-host-runner - name: '@deepseek-ai/dsh-cordis-host-runner' - - id: tool-cordis - name: '@deepseek-ai/dsh-tool-cordis' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: both + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + - id: cordis-host-runner + name: '@deepseek-ai/dsh-cordis-host-runner' + - id: tool-cordis + name: '@deepseek-ai/dsh-tool-cordis' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/advanced.cordis.yml b/examples/acp-agent/advanced.cordis.yml index 8ca92b6800..f84d735cde 100644 --- a/examples/acp-agent/advanced.cordis.yml +++ b/examples/acp-agent/advanced.cordis.yml @@ -1,29 +1,39 @@ # Add Code Mode and Cordis tools to the base spawn/workflow stack, exercising # all four boundaries in one ACP snapshot. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - tools: - mode: both - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + provider: deepseek-official + model: deepseek-v4-pro - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' - - id: cordis-host-runner - name: '@deepseek-ai/dsh-cordis-host-runner' - - id: tool-cordis - name: '@deepseek-ai/dsh-tool-cordis' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: both + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + - id: cordis-host-runner + name: '@deepseek-ai/dsh-cordis-host-runner' + - id: tool-cordis + name: '@deepseek-ai/dsh-tool-cordis' diff --git a/examples/acp-agent/agent-instructions.cordis.snapshot.yml b/examples/acp-agent/agent-instructions.cordis.snapshot.yml index 659bfba286..09486238ea 100644 --- a/examples/acp-agent/agent-instructions.cordis.snapshot.yml +++ b/examples/acp-agent/agent-instructions.cordis.snapshot.yml @@ -1,39 +1,46 @@ -# Keyless replay counterpart of agent-instructions.cordis.yml. Patches do not -# compose across includes, so this applies the scenario config and model swap -# directly to the live tree. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: 'none' - workspaceContext: - maxBytes: 65536 - dshHome: !!js process.cwd() + '/.dsh' - projectRootMarkers: - - .dsh-project - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. +# Keyless replay counterpart of agent-instructions.cordis.yml: apply the +# scenario values and model swap to the ACP profile. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: workspace-context-compaction - name: './tests/fixtures/workspace-context-compaction.ts' +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + dshHome: !!js process.cwd() + '/.dsh' + projectRootMarkers: + - .dsh-project + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: workspace-context-compaction + name: './tests/fixtures/workspace-context-compaction.ts' diff --git a/examples/acp-agent/agent-instructions.cordis.yml b/examples/acp-agent/agent-instructions.cordis.yml index db9fb85353..ce243b697d 100644 --- a/examples/acp-agent/agent-instructions.cordis.yml +++ b/examples/acp-agent/agent-instructions.cordis.yml @@ -1,24 +1,29 @@ -# Workspace-context snapshot overlay: keep project-root and user-global -# discovery inside the scenario's temporary cwd. The app config patch replaces -# the whole base config, so the base fields are restated verbatim. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +# Workspace-context snapshot patch: keep project-root and user-global +# discovery inside the scenario's temporary cwd. +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - dshHome: !!js process.cwd() + '/.dsh' - projectRootMarkers: - - .dsh-project - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + provider: deepseek-official + model: deepseek-v4-pro - Verify your work by running the code or tests. Keep answers brief and factual. +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + dshHome: !!js process.cwd() + '/.dsh' + projectRootMarkers: + - .dsh-project + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. diff --git a/examples/acp-agent/background-job-admission.cordis.snapshot.yml b/examples/acp-agent/background-job-admission.cordis.snapshot.yml index e5e499aa9b..a57107f7a9 100644 --- a/examples/acp-agent/background-job-admission.cordis.snapshot.yml +++ b/examples/acp-agent/background-job-admission.cordis.snapshot.yml @@ -1,36 +1,47 @@ # Keyless counterpart to background-job-admission.cordis.yml: replace the # DeepSeek adapter with replay while preserving the app's one-task admission # config and the recorded flash route. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - tasks: - maxConcurrentJobsPerOwner: 1 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: jobs + name: '@deepseek-ai/dsh-jobs-local' + config: + maxConcurrentJobsPerOwner: 1 + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/background-job-admission.cordis.yml b/examples/acp-agent/background-job-admission.cordis.yml index 0ceaaa90f8..905713ca0a 100644 --- a/examples/acp-agent/background-job-admission.cordis.yml +++ b/examples/acp-agent/background-job-admission.cordis.yml @@ -2,23 +2,32 @@ # configuring its task provider to allow one active task per exact owner. The # scenario starts a real background Bash process, observes the second producer # rejection, and cleans up the first task by its returned id. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - tasks: - maxConcurrentJobsPerOwner: 1 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + provider: deepseek-official + model: deepseek-v4-flash - Verify your work by running the code or tests. Keep answers brief and factual. +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: jobs + name: '@deepseek-ai/dsh-jobs-local' + config: + maxConcurrentJobsPerOwner: 1 diff --git a/examples/acp-agent/both-mode.cordis.snapshot.yml b/examples/acp-agent/both-mode.cordis.snapshot.yml index f541b25db8..c0d503a5cf 100644 --- a/examples/acp-agent/both-mode.cordis.snapshot.yml +++ b/examples/acp-agent/both-mode.cordis.snapshot.yml @@ -1,38 +1,48 @@ -# Keyless both mode combines the runtime/registry patch with the DeepSeek-to-replay -# swap. Include patches cannot target entries behind a nested include, so this file -# applies both overlays directly to `cordis.yml`. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: 'none' - workspaceContext: - maxBytes: 65536 - tools: - mode: both - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. +# Keyless both mode combines the runtime/registry changes with the +# DeepSeek-to-replay swap in one profile patch. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: both + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/both-mode.cordis.yml b/examples/acp-agent/both-mode.cordis.yml index 04f1761a81..e0931da3ae 100644 --- a/examples/acp-agent/both-mode.cordis.yml +++ b/examples/acp-agent/both-mode.cordis.yml @@ -1,27 +1,36 @@ # Both mode adds `ctx.codeRuntime` while keeping native tools on the wire and -# adding `run_code` plus its generated TypeScript SDK prompt. The app bin selects -# this overlay for snapshot recording and the sibling overlay for replay. A config -# patch replaces the whole app config, so unchanged base fields are restated below. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +# adding `run_code` plus its generated TypeScript SDK prompt. Recording applies +# this profile patch; replay applies its sibling patch. +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - tools: - mode: both - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + provider: deepseek-official + model: deepseek-v4-pro - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: both + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' diff --git a/examples/acp-agent/child-question.cordis.snapshot.yml b/examples/acp-agent/child-question.cordis.snapshot.yml index a9a9c7adc0..d2eb40781a 100644 --- a/examples/acp-agent/child-question.cordis.snapshot.yml +++ b/examples/acp-agent/child-question.cordis.snapshot.yml @@ -1,50 +1,60 @@ # Keyless counterpart to child-question.cordis.yml: keep the real interaction # seam, model-facing tool, and tripwire provider while replacing DeepSeek with # per-session replay. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: user-questions - name: '@deepseek-ai/dsh-user-questions' - - id: tool-ask-user - name: '@deepseek-ai/dsh-tool-ask-user' - - id: child-question-tripwire - name: './tests/fixtures/child-question-tripwire.ts' +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: tool-ask-user + name: '@deepseek-ai/dsh-tool-ask-user' + - id: child-question-tripwire + name: './tests/fixtures/child-question-tripwire.ts' + +- id: user-questions + name: '@deepseek-ai/dsh-user-questions' diff --git a/examples/acp-agent/child-question.cordis.yml b/examples/acp-agent/child-question.cordis.yml index 65d3663150..f985baa33e 100644 --- a/examples/acp-agent/child-question.cordis.yml +++ b/examples/acp-agent/child-question.cordis.yml @@ -1,14 +1,10 @@ # Snapshot-only human-interaction composition. The provider is a tripwire: the # runtime-owned child must be rejected by the seam before any UI wait begins. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: user-questions - name: '@deepseek-ai/dsh-user-questions' - - id: tool-ask-user - name: '@deepseek-ai/dsh-tool-ask-user' - - id: child-question-tripwire - name: './tests/fixtures/child-question-tripwire.ts' +- insert: + - id: tool-ask-user + name: '@deepseek-ai/dsh-tool-ask-user' + - id: child-question-tripwire + name: './tests/fixtures/child-question-tripwire.ts' + +- id: user-questions + name: '@deepseek-ai/dsh-user-questions' diff --git a/examples/acp-agent/code-mode-image.cordis.snapshot.yml b/examples/acp-agent/code-mode-image.cordis.snapshot.yml index 4f227ec19a..83c29e02d3 100644 --- a/examples/acp-agent/code-mode-image.cordis.snapshot.yml +++ b/examples/acp-agent/code-mode-image.cordis.snapshot.yml @@ -1,44 +1,56 @@ # Keyless replay combines Code Mode with the durable image store and an exact # image-capable replay route. The scenario generates its tiny PNG inside the # run_code program, then exercises read_image as a nested dispatch. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash-vision-exp - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - tools: - mode: code - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - inputModalities: [text] - - id: deepseek-v4-pro - inputModalities: [text] - - id: deepseek-v4-flash-vision-exp - inputModalities: [text, image] +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash-vision-exp + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: code + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + inputModalities: [text] + - id: deepseek-v4-pro + inputModalities: [text] + - id: deepseek-v4-flash-vision-exp + inputModalities: [text, image] + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/code-mode-image.cordis.yml b/examples/acp-agent/code-mode-image.cordis.yml index c7f3553b0b..eebb235642 100644 --- a/examples/acp-agent/code-mode-image.cordis.yml +++ b/examples/acp-agent/code-mode-image.cordis.yml @@ -1,28 +1,39 @@ # Code Mode image overlay: mounts the worker runtime and durable attachment # store so a nested read_image result can cross the generic rich-result bridge. # The live config selects the shipped vision route for manual use. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash-vision-exp - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - tools: - mode: code - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + provider: deepseek-official + model: deepseek-v4-flash-vision-exp - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: code + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml b/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml index 2f05132b93..64858173be 100644 --- a/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml +++ b/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml @@ -1,37 +1,48 @@ # Keyless replay counterpart of code-mode-workspace-context.cordis.yml. It adds # Code Mode to the default filesystem suite and swaps in replay. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: 'none' - workspaceContext: - maxBytes: 65536 - tools: - mode: code - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: code + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/code-mode-workspace-context.cordis.yml b/examples/acp-agent/code-mode-workspace-context.cordis.yml index f4f7a86d71..19376d75bb 100644 --- a/examples/acp-agent/code-mode-workspace-context.cordis.yml +++ b/examples/acp-agent/code-mode-workspace-context.cordis.yml @@ -1,25 +1,35 @@ # Code Mode agent-instructions snapshot recording overlay. The default filesystem # tools trigger nested instruction discovery after a read. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - tools: - mode: code - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + provider: deepseek-official + model: deepseek-v4-pro - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: code + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' diff --git a/examples/acp-agent/code-mode.cordis.snapshot.yml b/examples/acp-agent/code-mode.cordis.snapshot.yml index 21357aa136..7cdb2e3d5e 100644 --- a/examples/acp-agent/code-mode.cordis.snapshot.yml +++ b/examples/acp-agent/code-mode.cordis.snapshot.yml @@ -1,38 +1,48 @@ -# Keyless Code Mode combines the runtime/registry patch with the DeepSeek-to-replay -# swap. Include patches cannot target entries behind a nested include, so this file -# applies both overlays directly to `cordis.yml`. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: 'none' - workspaceContext: - maxBytes: 65536 - tools: - mode: code - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. +# Keyless Code Mode combines the runtime/registry changes with the +# DeepSeek-to-replay swap in one profile patch. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: code + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/code-mode.cordis.yml b/examples/acp-agent/code-mode.cordis.yml index 9aa14b75d4..6f5ca82571 100644 --- a/examples/acp-agent/code-mode.cordis.yml +++ b/examples/acp-agent/code-mode.cordis.yml @@ -1,28 +1,36 @@ # Code Mode adds `ctx.codeRuntime` and changes the registry to one wire tool, -# `run_code`, plus its generated TypeScript SDK prompt. The app bin selects this -# overlay for `demo:code-mode` and snapshot recording, and selects the sibling -# replay overlay for `DSH_SNAPSHOT=replay`. A config patch replaces the whole app -# config, so unchanged base fields are restated below. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +# `run_code`, plus its generated TypeScript SDK prompt. The demo and snapshot +# recorder apply this profile patch; replay applies its sibling patch. +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - tools: - mode: code - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + provider: deepseek-official + model: deepseek-v4-pro - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: code-runtime - name: '@deepseek-ai/dsh-code-runtime-worker-thread' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: tools + name: '@deepseek-ai/dsh-tools' + config: + mode: code + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: code-runtime + name: '@deepseek-ai/dsh-code-runtime-worker-thread' diff --git a/examples/acp-agent/cordis-tools.cordis.yml b/examples/acp-agent/cordis-tools.cordis.yml index d5ba060837..b17daa4f53 100644 --- a/examples/acp-agent/cordis-tools.cordis.yml +++ b/examples/acp-agent/cordis-tools.cordis.yml @@ -1,12 +1,7 @@ # Add the self-referential Cordis tools without changing the base ACP tool # presentation mode. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: cordis-host-runner - name: '@deepseek-ai/dsh-cordis-host-runner' - - id: tool-cordis - name: '@deepseek-ai/dsh-tool-cordis' +- insert: + - id: cordis-host-runner + name: '@deepseek-ai/dsh-cordis-host-runner' + - id: tool-cordis + name: '@deepseek-ai/dsh-tool-cordis' diff --git a/examples/acp-agent/cordis.snapshot.yml b/examples/acp-agent/cordis.snapshot.yml index 46eff1e869..37cca61429 100644 --- a/examples/acp-agent/cordis.snapshot.yml +++ b/examples/acp-agent/cordis.snapshot.yml @@ -1,61 +1,44 @@ -# Keyless replay includes the live `cordis.yml`, disables the key-requiring -# DeepSeek adapter, and inserts `llm-replay` to serve recorded JSONL without a key -# or network; every other app entry remains shared. It also restates the acp-agent -# config to re-pin `deepseek-v4-flash`: `cordis.yml` ships `deepseek-v4-pro`, but the -# recorded corpus (request headers, provenance, system prompt) was captured on flash, -# so replay holds the recorded model to stay reproducible without a re-record. A config -# patch replaces the whole app config, so the base fields are restated verbatim. -# With `DSH_SNAPSHOT=replay`, the app bin reads `DSH_SNAPSHOT_FILE` and optional -# `DSH_SNAPSHOT_OVERRIDE` from the harness. The one-shot patch applies at include -# load time, and stdout remains reserved for ACP JSON-RPC. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - # `name` asserts the target: a mismatch skips the patch and warns only when - # a logger exists. A renamed id leaves a stale adapter entry, but replay still - # short-circuits through `llm-replay`. - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - # Replay fixtures are raw JSONL; the whole-config patch must restate - # the compression choice or the default zstd frames hide the logs - # from the harness's harvest. - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +# Replay-only patch layered after `cordis.yml`: replace the network adapter +# with recorded JSONL, pin the recorded top-level model, and keep persistence +# raw for fixture harvesting. - Verify your work by running the code or tests. Keep answers brief and factual. - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - # Authored scenarios can fence a later parent action on the real - # child settlement edge without exposing a test-only model tool. - - id: subagent-settlement-marker - name: './tests/fixtures/subagent-settlement-marker.ts' +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? dshHomePath('sessions') + compression: none + +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + + - id: subagent-settlement-marker + name: './tests/fixtures/subagent-settlement-marker.ts' diff --git a/examples/acp-agent/cordis.yml b/examples/acp-agent/cordis.yml index bdecdec5d5..ddfb914132 100644 --- a/examples/acp-agent/cordis.yml +++ b/examples/acp-agent/cordis.yml @@ -1,20 +1,7 @@ -# ACP automation server and backend snapshot-record composition. With -# `DSH_SNAPSHOT=record`, the app bin runs the real DeepSeek adapter and the -# harness harvests its persisted log. The bin loads the gitignored root `.env` -# before this config. This tree has no stdout logger or HMR because stdout -# carries ACP JSON-RPC. +# ACP demo and snapshot-record patch over the shipped `acp` profile. The dsh +# launcher owns environment loading, plugin resolution, and process shutdown; +# stdout remains reserved for ACP JSON-RPC. -- id: deepseek-llm-api-extensions - name: '@deepseek-ai/dsh-deepseek-llm-api-extensions' - -- id: session-log-deepseek - name: '@deepseek-ai/dsh-session-log-deepseek' - -- id: plugin-package-inventory-deepseek - name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek' - -# The DeepSeek adapter. Shipped default: full thinking at max effort on every -# request; exact-model resolution materializes request defaults before logging. - id: llm-deepseek name: '@deepseek-ai/dsh-llm-deepseek' config: @@ -26,103 +13,41 @@ - id: deepseek-v4-flash-vision-exp inputModalities: [text, image] -# The default composition confines bash AND the filesystem tools to the -# workspace and asks before a wider retry. Snapshot runs select -# danger-full-access so the established scenarios remain runner-independent; -# DSH_PERMISSION_MODE provides the same explicit deployment/test override -# outside the snapshot harness. The sandbox default + fallback root live on -# ctx.sandboxPolicy; agent calls resolve both families against the session cwd. -- id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - - id: sandbox-policy name: '@deepseek-ai/dsh-sandbox-policy' config: mode: !!js "process.env.DSH_PERMISSION_MODE ?? (process.env.DSH_SNAPSHOT === undefined ? 'workspace-write' : 'danger-full-access')" workspaceRoot: !!js process.cwd() -# Managed child-process groups for the bash executor (spawn/kill/output plumbing). -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: bash - name: '@deepseek-ai/dsh-bash-sandbox' - config: - timeoutMs: 60000 - - id: approval name: '@deepseek-ai/dsh-user-approval' config: policy: !!js "(process.env.DSH_PERMISSION_MODE ?? (process.env.DSH_SNAPSHOT === undefined ? 'workspace-write' : 'danger-full-access')) === 'danger-full-access' ? 'never' : 'ask'" -# The ACP automation app: agent spine + JSONL persistence + protocol bridge. -# Persistence root: $DSH_SNAPSHOT_SESSIONS_ROOT when the snapshot harness sets it -# (so it can harvest / isolate the log), else ./.sessions for the demo. -# Snapshot modes use raw JSONL fixtures; ordinary runs keep the compressed default. -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? dshHomePath('sessions') + compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" + +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - # Keep the persona to identity and behavior; tool plugins own tool guidance. - # The loop resolves {{model}} and each ACP session's client-supplied {{cwd}}. + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: | You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. Verify your work by running the code or tests. Keep answers brief and factual. -# Replay-aware request pressure; the routed adapter supplies model capacity. -- id: token-meter - name: '@deepseek-ai/dsh-token-meter' - -# Summarize an older range after measured pressure or a canonical provider overflow. -# Ratios scale against the routed model's context window. -- id: compaction-basic - name: '@deepseek-ai/dsh-compaction-basic' +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' config: - thresholdRatio: 0.8 - retainRatio: 0.08 - maxTokens: 8192 - compactionRetries: 1 - -# Projection registry: subagent catalog identity (mode/label) folds through -# its registered units; the catalog surfaces (`list_agents`, subagent listing) -# fail loud without the capability. -- id: session-projection - name: '@deepseek-ai/dsh-session-projection' - -# Expose fresh-child `spawn` and completed-prefix `fork` through separate tool -# names so multi-child scenarios exercise both transports. These leaves follow -# the app because it provides `ctx.agents` and `ctx.tools`. -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -- id: subagent-spawn-in-process - name: '@deepseek-ai/dsh-subagent-spawn-in-process' - config: - providerName: spawn - -- id: subagent-fork-in-process - name: '@deepseek-ai/dsh-subagent-fork-in-process' - config: - providerName: fork - -# Continuable background children are selected per delegation tool. The -# separately loaded control package registers the global `send_message`; its -# list plugin registers `list_agents`, served through the sessionProjections -# registry mounted above. `report` is installed only in continuable child scopes. -- id: tool-subagent-control - name: '@deepseek-ai/dsh-tool-subagent-control' - -- id: tool-subagent-list-agents - name: '@deepseek-ai/dsh-tool-subagent-control/list-agents' - -- id: tool-subagent-report - name: '@deepseek-ai/dsh-tool-subagent-report' + maxBytes: 65536 - id: tool-subagent name: '@deepseek-ai/dsh-tool-subagent' @@ -132,10 +57,6 @@ backgroundMode: continuable maxDepth: 1 -# Fork stays one-shot because a continuable child's `report` tool and prompt -# section precede the inherited history a fork reuses; `run_in_background` is off -# as an explicit foreground-only choice even though agent-spine-demo mounts the -# generic Job runtime. See .agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md. - id: tool-subagent-fork name: '@deepseek-ai/dsh-tool-subagent' config: @@ -145,60 +66,18 @@ enableRunInBackground: false maxDepth: 1 - -# The worker-thread workflow engine fans a model-written JavaScript script's -# `agent()` calls out through the spawn backend; the adjacent tool exposes it to the model. -- id: workflow-worker-thread - name: '@deepseek-ai/dsh-workflow-worker-thread' - config: - provider: spawn - -- id: tool-workflow - name: '@deepseek-ai/dsh-tool-workflow' - -- id: tool-ralph - name: '@deepseek-ai/dsh-tool-ralph' -# `todo_write` replaces the logged whole list for later model requests. -- id: tool-todo - name: '@deepseek-ai/dsh-tool-todo' - config: - allowParallelInProgress: true - -# Identical repeat calls trigger advisory context, never a block, at the default -# thresholds [3, 5, 8]. Only the repeat-tool-reminder snapshot scenario reaches them. -- id: repeat-tool-reminder - name: '@deepseek-ai/dsh-repeat-tool-reminder' - -# The filesystem stack rides the SAME sandbox policy as bash: dsh-fs-sandbox -# replaces dsh-fs-local behind ctx.fs and fences write/edit by the effective -# mode (read-only denies, workspace-write contains to the workspace + temp -# roots, danger-full-access passes through), so read/write/edit are available -# under every mode. fs-observation-policy (read-before-edit) composes orthogonally on top. - id: fs-sandbox name: '@deepseek-ai/dsh-fs-sandbox' config: cwd: !!js process.cwd() -- id: fs-observation-policy - name: '@deepseek-ai/dsh-fs-observation-policy' +- insert: + - id: hooks-claude-code + name: '@deepseek-ai/dsh-hooks-claude-code' + config: + configPath: ./hooks.json -- id: tool-fs - name: '@deepseek-ai/dsh-tool-fs' - -# `configPath` is read once at load and resolves from the server launch cwd, not -# `session/new.cwd`; one `hooks.json` therefore applies to every session and a -# project-local file is not discovered. Missing config registers nothing. Hook -# commands still run in the session cwd. Warnings use `ctx.logger`, never stdout; -# see packages/hooks/hooks-claude-code/README.md for the deferred per-session design. -- id: hooks-claude-code - name: '@deepseek-ai/dsh-hooks-claude-code' - config: - configPath: ./hooks.json - -# Codex uses its own `codex-hooks.json` and snake_case five-event dialect; it -# cannot share Claude's file. It has the same process-level, read-once, missing-is-no-op, -# logger-only contract. Shipping both bridges lets a scenario seed and exercise either dialect. -- id: hooks-codex - name: '@deepseek-ai/dsh-hooks-codex' - config: - configPath: ./codex-hooks.json + - id: hooks-codex + name: '@deepseek-ai/dsh-hooks-codex' + config: + configPath: ./codex-hooks.json diff --git a/examples/acp-agent/depth-two.cordis.snapshot.yml b/examples/acp-agent/depth-two.cordis.snapshot.yml index e988f9be60..89ef8c1035 100644 --- a/examples/acp-agent/depth-two.cordis.snapshot.yml +++ b/examples/acp-agent/depth-two.cordis.snapshot.yml @@ -1,53 +1,60 @@ # Keyless counterpart to depth-two.cordis.yml: apply the depth patch and replace # the live adapter with per-session replay. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - id: tool-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: spawn - toolName: subagent - backgroundMode: continuable - maxDepth: 2 - # Re-pin the recorded model: cordis.yml ships deepseek-v4-pro, but this - # scenario's corpus was captured on flash. A config patch replaces the - # whole app config, so the base fields are restated verbatim. - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- id: tool-subagent + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: spawn + toolName: subagent + backgroundMode: continuable + maxDepth: 2 +# Re-pin the recorded flash model for this scenario's corpus. +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/depth-two.cordis.yml b/examples/acp-agent/depth-two.cordis.yml index 73e02d7ffc..b2d4dbe039 100644 --- a/examples/acp-agent/depth-two.cordis.yml +++ b/examples/acp-agent/depth-two.cordis.yml @@ -1,14 +1,9 @@ # Depth-limit snapshot overlay: keep the default composition and allow two # generations of spawn children before runtime enforcement rejects another. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: tool-subagent + name: '@deepseek-ai/dsh-tool-subagent' config: - path: ./cordis.yml - patches: - - id: tool-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: spawn - toolName: subagent - backgroundMode: continuable - maxDepth: 2 + provider: spawn + toolName: subagent + backgroundMode: continuable + maxDepth: 2 diff --git a/examples/acp-agent/fs.cordis.snapshot.yml b/examples/acp-agent/fs.cordis.snapshot.yml index ee922dba07..164e7229ac 100644 --- a/examples/acp-agent/fs.cordis.snapshot.yml +++ b/examples/acp-agent/fs.cordis.snapshot.yml @@ -1,45 +1,52 @@ -# Keyless filesystem snapshots apply the spill and replay overlays directly -# because include patches cannot target entries behind a nested include. The -# sandboxed filesystem stack already lives in the base cordis.yml. This file also -# re-pins the acp-agent model to `deepseek-v4-flash`: `cordis.yml` ships -# `deepseek-v4-pro`, but the recorded corpus was captured on flash, and a config -# patch replaces the whole app config, so the base fields are restated verbatim. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +# Keyless filesystem snapshots combine spill settings with replay. The +# sandboxed filesystem stack already lives in `dsh-base`; the ACP row re-pins +# `deepseek-v4-flash` for the recorded corpus. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: spill-local - name: '@deepseek-ai/dsh-spill-local' - config: - root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' - - id: spill-policy - name: '@deepseek-ai/dsh-spill-policy' - config: - maxInlineBytes: 800 - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + +- id: spill-local + name: '@deepseek-ai/dsh-spill-local' + config: + root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' + +- id: spill-policy + name: '@deepseek-ai/dsh-spill-policy' + config: + maxInlineBytes: 800 diff --git a/examples/acp-agent/fs.cordis.yml b/examples/acp-agent/fs.cordis.yml index c68e361302..940fb9bd21 100644 --- a/examples/acp-agent/fs.cordis.yml +++ b/examples/acp-agent/fs.cordis.yml @@ -1,17 +1,13 @@ # Filesystem-scenario overlay: the sandboxed filesystem stack already lives in -# the base cordis.yml, so this overlay adds only the local tool-result spill +# `dsh-base`, so this patch changes only the local tool-result spill # storage those scenarios exercise. -- id: base - name: '@deepseek-ai/cordis-plugin-include' + +- id: spill-local + name: '@deepseek-ai/dsh-spill-local' config: - path: ./cordis.yml - patches: - - insert: - - id: spill-local - name: '@deepseek-ai/dsh-spill-local' - config: - root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' - - id: spill-policy - name: '@deepseek-ai/dsh-spill-policy' - config: - maxInlineBytes: !!js process.env.DSH_SNAPSHOT && 800 || 50000 + root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' + +- id: spill-policy + name: '@deepseek-ai/dsh-spill-policy' + config: + maxInlineBytes: !!js process.env.DSH_SNAPSHOT && 800 || 50000 diff --git a/examples/acp-agent/image-text-route.cordis.snapshot.yml b/examples/acp-agent/image-text-route.cordis.snapshot.yml index bd1c0e01ea..a7ca0e7bad 100644 --- a/examples/acp-agent/image-text-route.cordis.snapshot.yml +++ b/examples/acp-agent/image-text-route.cordis.snapshot.yml @@ -2,38 +2,47 @@ # image.cordis.snapshot.yml overlay except the replay catalog leaves flash # text-only, so the strict read_image gate refuses and no image ever enters # the durable log. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - inputModalities: [text] - - id: deepseek-v4-pro - inputModalities: [text] +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + inputModalities: [text] + - id: deepseek-v4-pro + inputModalities: [text] + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/image-text-route.cordis.yml b/examples/acp-agent/image-text-route.cordis.yml index bbb5b9b4c0..c3755f632c 100644 --- a/examples/acp-agent/image-text-route.cordis.yml +++ b/examples/acp-agent/image-text-route.cordis.yml @@ -1,27 +1,31 @@ # Text-route image overlay: the attachment store registers read_image, but the # strict execution gate refuses on a route that does not declare image input, -# so a text-only deployment keeps its durable history text-clean. The app -# config is restated to re-pin `deepseek-v4-flash` (base ships pro; the -# authored fixture and the pinned header class are flash), because a config -# patch replaces the whole app config. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +# so a text-only deployment keeps its durable history text-clean. The ACP row +# re-pins `deepseek-v4-flash` for the authored fixture and header class. +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + provider: deepseek-official + model: deepseek-v4-flash - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/image.cordis.snapshot.yml b/examples/acp-agent/image.cordis.snapshot.yml index 7a355618d3..fbfe90f7ef 100644 --- a/examples/acp-agent/image.cordis.snapshot.yml +++ b/examples/acp-agent/image.cordis.snapshot.yml @@ -1,43 +1,50 @@ -# Keyless replay for the read-image success scenario. Include patches cannot -# target entries behind a nested include, so this restates the replay overlay -# directly over the base cordis.yml (the fs.cordis.snapshot.yml pattern) and -# re-pins the recorded vision model. The replay catalog declares image input, +# Keyless replay for the read-image success scenario. This profile patch swaps +# the adapter and re-pins the recorded vision model. The replay catalog declares image input, # so the strict read_image gate accepts the route and the tool result carries # the durable image block. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash-vision-exp - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - inputModalities: [text] - - id: deepseek-v4-pro - inputModalities: [text] - - id: deepseek-v4-flash-vision-exp - inputModalities: [text, image] +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash-vision-exp + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + inputModalities: [text] + - id: deepseek-v4-pro + inputModalities: [text] + - id: deepseek-v4-flash-vision-exp + inputModalities: [text, image] + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/image.cordis.yml b/examples/acp-agent/image.cordis.yml index 347b74833b..a492e4b1ac 100644 --- a/examples/acp-agent/image.cordis.yml +++ b/examples/acp-agent/image.cordis.yml @@ -1,26 +1,31 @@ # Image-scenario overlay: adds the durable attachment store the read_image tool # commits through. The store resolves its root from $DSH_HOME, which the -# snapshot harness scopes per run, so the overlay itself carries no paths. The -# app config is restated to select the shipped vision model because a config -# patch replaces the whole app config. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +# snapshot harness scopes per run, so the patch itself carries no attachment +# path. The ACP row selects the shipped vision model. +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash-vision-exp - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + provider: deepseek-official + model: deepseek-v4-flash-vision-exp - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/partial-landlock.cordis.snapshot.yml b/examples/acp-agent/partial-landlock.cordis.snapshot.yml index ce48d20ac7..766da76bff 100644 --- a/examples/acp-agent/partial-landlock.cordis.snapshot.yml +++ b/examples/acp-agent/partial-landlock.cordis.snapshot.yml @@ -1,38 +1,47 @@ # Keyless runner-classification composition: replay authored model turns and # replace the shipping provider with a deterministic process-launch stand-in. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: partial-landlock-sandbox - name: './tests/fixtures/partial-landlock-sandbox.ts' +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + disabled: true + +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: partial-landlock-sandbox + name: './tests/fixtures/partial-landlock-sandbox.ts' diff --git a/examples/acp-agent/partial-landlock.cordis.yml b/examples/acp-agent/partial-landlock.cordis.yml index 2272c657d1..973259a827 100644 --- a/examples/acp-agent/partial-landlock.cordis.yml +++ b/examples/acp-agent/partial-landlock.cordis.yml @@ -1,13 +1,9 @@ # Live counterpart for the runner-classification snapshot overlay. It replaces # only the sandbox provider; authored scenarios are skipped in record mode. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - disabled: true - - insert: - - id: partial-landlock-sandbox - name: './tests/fixtures/partial-landlock-sandbox.ts' +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + disabled: true + +- insert: + - id: partial-landlock-sandbox + name: './tests/fixtures/partial-landlock-sandbox.ts' diff --git a/examples/acp-agent/product-subagent-both.cordis.snapshot.yml b/examples/acp-agent/product-subagent-both.cordis.snapshot.yml index 3bed92ab57..e01fe79dcf 100644 --- a/examples/acp-agent/product-subagent-both.cordis.snapshot.yml +++ b/examples/acp-agent/product-subagent-both.cordis.snapshot.yml @@ -1,64 +1,60 @@ # Keyless twin of product-subagent-both.cordis.yml: preserve all four named # product tools while replacing only the external model adapter. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: subagent-codex-primary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-primary - - id: subagent-codex-secondary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-secondary - - id: subagent-claude-primary - name: '@deepseek-ai/dsh-subagent-claude-code' - config: - providerName: claude-primary - - id: subagent-claude-secondary - name: '@deepseek-ai/dsh-subagent-claude-code' - config: - providerName: claude-secondary - - id: tool-subagent-codex-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-primary - toolName: subagent_codex_primary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-codex-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-secondary - toolName: subagent_codex_secondary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-primary - toolName: subagent_claude_primary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-secondary - toolName: subagent_claude_secondary - backgroundMode: one-shot - maxDepth: provider-managed +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: subagent-codex-primary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-primary + - id: subagent-codex-secondary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-secondary + - id: subagent-claude-primary + name: '@deepseek-ai/dsh-subagent-claude-code' + config: + providerName: claude-primary + - id: subagent-claude-secondary + name: '@deepseek-ai/dsh-subagent-claude-code' + config: + providerName: claude-secondary + - id: tool-subagent-codex-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-primary + toolName: subagent_codex_primary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-codex-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-secondary + toolName: subagent_codex_secondary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-claude-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-primary + toolName: subagent_claude_primary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-claude-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-secondary + toolName: subagent_claude_secondary + backgroundMode: one-shot + maxDepth: provider-managed diff --git a/examples/acp-agent/product-subagent-both.cordis.yml b/examples/acp-agent/product-subagent-both.cordis.yml index dfde2ead48..f5f558b448 100644 --- a/examples/acp-agent/product-subagent-both.cordis.yml +++ b/examples/acp-agent/product-subagent-both.cordis.yml @@ -1,53 +1,48 @@ # Add two named Codex providers, two named Claude Code providers, and the # independent one-shot tool rows an Agent Preset may contribute. Loading the # composition starts neither product; the scenario pins all four schemas. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: subagent-codex-primary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-primary - - id: subagent-codex-secondary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-secondary - - id: subagent-claude-primary - name: '@deepseek-ai/dsh-subagent-claude-code' - config: - providerName: claude-primary - - id: subagent-claude-secondary - name: '@deepseek-ai/dsh-subagent-claude-code' - config: - providerName: claude-secondary - - id: tool-subagent-codex-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-primary - toolName: subagent_codex_primary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-codex-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-secondary - toolName: subagent_codex_secondary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-primary - toolName: subagent_claude_primary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-secondary - toolName: subagent_claude_secondary - backgroundMode: one-shot - maxDepth: provider-managed +- insert: + - id: subagent-codex-primary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-primary + - id: subagent-codex-secondary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-secondary + - id: subagent-claude-primary + name: '@deepseek-ai/dsh-subagent-claude-code' + config: + providerName: claude-primary + - id: subagent-claude-secondary + name: '@deepseek-ai/dsh-subagent-claude-code' + config: + providerName: claude-secondary + - id: tool-subagent-codex-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-primary + toolName: subagent_codex_primary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-codex-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-secondary + toolName: subagent_codex_secondary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-claude-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-primary + toolName: subagent_claude_primary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-claude-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-secondary + toolName: subagent_claude_secondary + backgroundMode: one-shot + maxDepth: provider-managed diff --git a/examples/acp-agent/product-subagent-codex.cordis.snapshot.yml b/examples/acp-agent/product-subagent-codex.cordis.snapshot.yml index 811b775087..71ac58f715 100644 --- a/examples/acp-agent/product-subagent-codex.cordis.snapshot.yml +++ b/examples/acp-agent/product-subagent-codex.cordis.snapshot.yml @@ -1,42 +1,38 @@ # Keyless twin of product-subagent-codex.cordis.yml: keep both named product # providers and tools while replacing only the external model adapter. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: subagent-codex-primary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-primary - - id: subagent-codex-secondary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-secondary - - id: tool-subagent-codex-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-primary - toolName: subagent_codex_primary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-codex-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-secondary - toolName: subagent_codex_secondary - backgroundMode: one-shot - maxDepth: provider-managed +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: subagent-codex-primary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-primary + - id: subagent-codex-secondary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-secondary + - id: tool-subagent-codex-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-primary + toolName: subagent_codex_primary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-codex-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-secondary + toolName: subagent_codex_secondary + backgroundMode: one-shot + maxDepth: provider-managed diff --git a/examples/acp-agent/product-subagent-codex.cordis.yml b/examples/acp-agent/product-subagent-codex.cordis.yml index 1ca4cf297e..8ab76d8d2f 100644 --- a/examples/acp-agent/product-subagent-codex.cordis.yml +++ b/examples/acp-agent/product-subagent-codex.cordis.yml @@ -1,31 +1,26 @@ # Add two named Codex product providers and their preset-shaped one-shot tools # to the real ACP composition. The model is told not to call them; the scenario # pins both assembled request schemas without starting Codex. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: subagent-codex-primary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-primary - - id: subagent-codex-secondary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-secondary - - id: tool-subagent-codex-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-primary - toolName: subagent_codex_primary - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-codex-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-secondary - toolName: subagent_codex_secondary - backgroundMode: one-shot - maxDepth: provider-managed +- insert: + - id: subagent-codex-primary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-primary + - id: subagent-codex-secondary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-secondary + - id: tool-subagent-codex-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-primary + toolName: subagent_codex_primary + backgroundMode: one-shot + maxDepth: provider-managed + - id: tool-subagent-codex-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-secondary + toolName: subagent_codex_secondary + backgroundMode: one-shot + maxDepth: provider-managed diff --git a/examples/acp-agent/pty.cordis.snapshot.yml b/examples/acp-agent/pty.cordis.snapshot.yml index 44678d7ced..40f487d144 100644 --- a/examples/acp-agent/pty.cordis.snapshot.yml +++ b/examples/acp-agent/pty.cordis.snapshot.yml @@ -1,27 +1,23 @@ # Keyless replay counterpart to pty.cordis.yml. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: pty - name: '@deepseek-ai/dsh-terminal' - - id: pty-snapshot-backend - name: './pty-snapshot-backend.mjs' - - id: tool-terminal - name: '@deepseek-ai/dsh-tool-terminal' - config: - maxResultBytes: 64 - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: pty + name: '@deepseek-ai/dsh-terminal' + - id: pty-snapshot-backend + name: './pty-snapshot-backend.mjs' + - id: tool-terminal + name: '@deepseek-ai/dsh-tool-terminal' + config: + maxResultBytes: 64 + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/pty.cordis.yml b/examples/acp-agent/pty.cordis.yml index 163e9ef9d8..c8ba76be5a 100644 --- a/examples/acp-agent/pty.cordis.yml +++ b/examples/acp-agent/pty.cordis.yml @@ -1,21 +1,16 @@ # Opt-in persistent PTY composition for the PTY snapshot scenario. The base # deployment already owns the shared sandbox provider and policy. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: pty - name: '@deepseek-ai/dsh-terminal' - - id: terminal-bash - name: '@deepseek-ai/dsh-terminal-bash' - config: - pollIntervalMs: 10 - exactProbeAfterMs: 20 - idleSilenceMs: 250 - handoffGraceMs: 250 - timeoutMs: 2000 - disposeGraceMs: 500 - - id: tool-terminal - name: '@deepseek-ai/dsh-tool-terminal' +- insert: + - id: pty + name: '@deepseek-ai/dsh-terminal' + - id: terminal-bash + name: '@deepseek-ai/dsh-terminal-bash' + config: + pollIntervalMs: 10 + exactProbeAfterMs: 20 + idleSilenceMs: 250 + handoffGraceMs: 250 + timeoutMs: 2000 + disposeGraceMs: 500 + - id: tool-terminal + name: '@deepseek-ai/dsh-tool-terminal' diff --git a/examples/acp-agent/retry.cordis.snapshot.yml b/examples/acp-agent/retry.cordis.snapshot.yml index c69b08fe64..5654f7df34 100644 --- a/examples/acp-agent/retry.cordis.snapshot.yml +++ b/examples/acp-agent/retry.cordis.snapshot.yml @@ -2,41 +2,49 @@ # adapter, insert `llm-replay`, and give its provider the same deterministic # 1 ms zero-jitter retry policy as the live sibling. The app patch still # restates its whole config for raw JSONL persistence and the recorded model. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - retryPolicy: - mode: normal - maxRetries: 2 - backoff: - initialDelayMs: 1 - maxDelayMs: 1 - jitterRatio: 0 - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + retryPolicy: + mode: normal + maxRetries: 2 + backoff: + initialDelayMs: 1 + maxDelayMs: 1 + jitterRatio: 0 + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/retry.cordis.yml b/examples/acp-agent/retry.cordis.yml index 2da66a3e44..86f4db6ac6 100644 --- a/examples/acp-agent/retry.cordis.yml +++ b/examples/acp-agent/retry.cordis.yml @@ -5,36 +5,43 @@ # Config patches replace whole plugin configs: the provider patch restates its # adapter fields around `retryPolicy`, while the app patch re-pins the recorded # flash model and restates its base fields. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - config: - thinking: enabled - reasoningEffort: max - retryPolicy: - mode: normal - maxRetries: 2 - backoff: - initialDelayMs: 1 - maxDelayMs: 1 - jitterRatio: 0 - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + thinking: enabled + reasoningEffort: max + retryPolicy: + mode: normal + maxRetries: 2 + backoff: + initialDelayMs: 1 + maxDelayMs: 1 + jitterRatio: 0 + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro - Verify your work by running the code or tests. Keep answers brief and factual. +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. diff --git a/examples/acp-agent/session-query.cordis.snapshot.yml b/examples/acp-agent/session-query.cordis.snapshot.yml index b7c8d77733..34c2a731a0 100644 --- a/examples/acp-agent/session-query.cordis.snapshot.yml +++ b/examples/acp-agent/session-query.cordis.snapshot.yml @@ -1,12 +1,60 @@ +# Keyless session-query snapshot patch: replay plus the filesystem spill +# settings inherited by this scenario. The ACP row re-pins the recorded flash model. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + +- id: spill-local + name: '@deepseek-ai/dsh-spill-local' + config: + root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' + +- id: spill-policy + name: '@deepseek-ai/dsh-spill-policy' + config: + maxInlineBytes: 800 + # Keyless counterpart to session-query.cordis.yml: the nested snapshot overlay # supplies replay plus deterministic private spill storage and its byte limit. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./fs.cordis.snapshot.yml - patches: - - insert: - - id: tool-session-query - name: '@deepseek-ai/dsh-tool-session-query' - - id: timeout-policy - name: '@deepseek-ai/dsh-tool-call-timeout-policy' +- insert: + - id: tool-session-query + name: '@deepseek-ai/dsh-tool-session-query' + +- id: timeout-policy + name: '@deepseek-ai/dsh-tool-call-timeout-policy' diff --git a/examples/acp-agent/session-query.cordis.yml b/examples/acp-agent/session-query.cordis.yml index c14ae79b17..ce9d627e93 100644 --- a/examples/acp-agent/session-query.cordis.yml +++ b/examples/acp-agent/session-query.cordis.yml @@ -1,12 +1,22 @@ +# Filesystem-scenario overlay: the sandboxed filesystem stack already lives in +# the base cordis.yml, so this overlay adds only the local tool-result spill +# storage those scenarios exercise. + +- id: spill-local + name: '@deepseek-ai/dsh-spill-local' + config: + root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' + +- id: spill-policy + name: '@deepseek-ai/dsh-spill-policy' + config: + maxInlineBytes: !!js process.env.DSH_SNAPSHOT && 800 || 50000 + # Explicit session-query tool opt-in for the dedicated spill scenario. The # nested filesystem overlay supplies private spill storage and its byte limit. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./fs.cordis.yml - patches: - - insert: - - id: tool-session-query - name: '@deepseek-ai/dsh-tool-session-query' - - id: timeout-policy - name: '@deepseek-ai/dsh-tool-call-timeout-policy' +- insert: + - id: tool-session-query + name: '@deepseek-ai/dsh-tool-session-query' + +- id: timeout-policy + name: '@deepseek-ai/dsh-tool-call-timeout-policy' diff --git a/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml b/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml index 55627c17fa..45265a831c 100644 --- a/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml +++ b/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml @@ -1,50 +1,58 @@ -# Keyless replay counterpart of session-sandbox-root.cordis.yml. Patches do not -# compose across nested includes, so the replay swap, the recorded model pin, -# and the deliberately distinct sandbox fallback are applied together to the -# live tree. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +# Keyless replay counterpart of session-sandbox-root.cordis.yml: the replay +# swap, recorded model pin, and distinct sandbox fallback form one profile patch. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - id: sandbox-policy - name: '@deepseek-ai/dsh-sandbox-policy' - config: - mode: workspace-write - workspaceRoot: /tmp - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- id: sandbox-policy + name: '@deepseek-ai/dsh-sandbox-policy' + config: + mode: workspace-write + workspaceRoot: /tmp + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro diff --git a/examples/acp-agent/session-sandbox-root.cordis.yml b/examples/acp-agent/session-sandbox-root.cordis.yml index fd2d712882..38adba1859 100644 --- a/examples/acp-agent/session-sandbox-root.cordis.yml +++ b/examples/acp-agent/session-sandbox-root.cordis.yml @@ -2,13 +2,8 @@ # under the user's home, while this deployment fallback deliberately points at # /tmp. A workspace-write mutation can therefore succeed only when the calling # session's cwd replaces the process-level fallback root. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: sandbox-policy + name: '@deepseek-ai/dsh-sandbox-policy' config: - path: ./cordis.yml - patches: - - id: sandbox-policy - name: '@deepseek-ai/dsh-sandbox-policy' - config: - mode: !!js "process.env.DSH_PERMISSION_MODE ?? (process.env.DSH_SNAPSHOT === undefined ? 'workspace-write' : 'danger-full-access')" - workspaceRoot: /tmp + mode: !!js "process.env.DSH_PERMISSION_MODE ?? (process.env.DSH_SNAPSHOT === undefined ? 'workspace-write' : 'danger-full-access')" + workspaceRoot: /tmp diff --git a/examples/acp-agent/session-title.cordis.snapshot.yml b/examples/acp-agent/session-title.cordis.snapshot.yml index 252c5d6503..42a710f1fe 100644 --- a/examples/acp-agent/session-title.cordis.snapshot.yml +++ b/examples/acp-agent/session-title.cordis.snapshot.yml @@ -1,53 +1,61 @@ # Keyless session-title composition. Main-agent chunks derive from session.jsonl; # the auxiliary route consumes replay.override.json with pacing so its accepted # title commits only after the main turn has closed. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay-main - name: '@deepseek-ai/dsh-llm-replay' - config: - overrideFile: ./.missing-main-replay-override.json - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: llm-replay-title - name: '@deepseek-ai/dsh-llm-replay' - config: - paceMs: 10 - providers: - - id: title-replay - name: Title replay - models: - - id: title-model - - id: session-title-provider - name: '@deepseek-ai/dsh-session-title-first-prompt-llm' - config: - targetWords: 5 - targetCjkCharacters: 10 - maxInputBytes: 4096 - maxOutputTokens: 32 - timeoutMs: 5000 - provider: title-replay - model: title-model +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- insert: + - id: llm-replay-main + name: '@deepseek-ai/dsh-llm-replay' + config: + overrideFile: ./.missing-main-replay-override.json + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: llm-replay-title + name: '@deepseek-ai/dsh-llm-replay' + config: + paceMs: 10 + providers: + - id: title-replay + name: Title replay + models: + - id: title-model + - id: session-title-provider + name: '@deepseek-ai/dsh-session-title-first-prompt-llm' + config: + targetWords: 5 + targetCjkCharacters: 10 + maxInputBytes: 4096 + maxOutputTokens: 32 + timeoutMs: 5000 + provider: title-replay + model: title-model diff --git a/examples/acp-agent/session-title.cordis.yml b/examples/acp-agent/session-title.cordis.yml index c5eb508151..6c51d51568 100644 --- a/examples/acp-agent/session-title.cordis.yml +++ b/examples/acp-agent/session-title.cordis.yml @@ -1,19 +1,14 @@ # Session-title snapshot composition: the optional first-prompt provider uses # the ordinary DeepSeek route while the ACP app and every other capability stay # identical to the base example. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: session-title-provider - name: '@deepseek-ai/dsh-session-title-first-prompt-llm' - config: - targetWords: 5 - targetCjkCharacters: 10 - maxInputBytes: 4096 - maxOutputTokens: 32 - timeoutMs: 5000 - provider: deepseek-official - model: deepseek-v4-flash +- insert: + - id: session-title-provider + name: '@deepseek-ai/dsh-session-title-first-prompt-llm' + config: + targetWords: 5 + targetCjkCharacters: 10 + maxInputBytes: 4096 + maxOutputTokens: 32 + timeoutMs: 5000 + provider: deepseek-official + model: deepseek-v4-flash diff --git a/examples/acp-agent/subagent-continuable-inheritance.cordis.snapshot.yml b/examples/acp-agent/subagent-continuable-inheritance.cordis.snapshot.yml index 43822afbc3..811fe656f5 100644 --- a/examples/acp-agent/subagent-continuable-inheritance.cordis.snapshot.yml +++ b/examples/acp-agent/subagent-continuable-inheritance.cordis.snapshot.yml @@ -1,46 +1,55 @@ # Keyless counterpart to subagent-continuable-inheritance.cordis.yml: replace # the live adapter with replay and switch the root session to read-only at # creation. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: parent-sandbox-override - name: './tests/fixtures/parent-sandbox-override.ts' +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: parent-sandbox-override + name: './tests/fixtures/parent-sandbox-override.ts' diff --git a/examples/acp-agent/subagent-continuable-inheritance.cordis.yml b/examples/acp-agent/subagent-continuable-inheritance.cordis.yml index 227982e4bf..7ca31ddacc 100644 --- a/examples/acp-agent/subagent-continuable-inheritance.cordis.yml +++ b/examples/acp-agent/subagent-continuable-inheritance.cordis.yml @@ -1,11 +1,6 @@ # Policy-inheritance overlay: the root session is switched to read-only at # creation (the UI Access switch equivalent), so a continuable background # child must inherit that override instead of the deployment default. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: parent-sandbox-override - name: './tests/fixtures/parent-sandbox-override.ts' +- insert: + - id: parent-sandbox-override + name: './tests/fixtures/parent-sandbox-override.ts' diff --git a/examples/acp-agent/subagent-durability-failure.cordis.snapshot.yml b/examples/acp-agent/subagent-durability-failure.cordis.snapshot.yml index 2fc7c8a369..5cf004c323 100644 --- a/examples/acp-agent/subagent-durability-failure.cordis.snapshot.yml +++ b/examples/acp-agent/subagent-durability-failure.cordis.snapshot.yml @@ -1,45 +1,54 @@ # Keyless counterpart to subagent-durability-failure.cordis.yml: replace the # live adapter with replay and fail the provider-owned final child checkpoint. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: subagent-durability-failure - name: './tests/fixtures/subagent-durability-failure.ts' +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: subagent-durability-failure + name: './tests/fixtures/subagent-durability-failure.ts' diff --git a/examples/acp-agent/subagent-durability-failure.cordis.yml b/examples/acp-agent/subagent-durability-failure.cordis.yml index ff5603093e..14d6d08c38 100644 --- a/examples/acp-agent/subagent-durability-failure.cordis.yml +++ b/examples/acp-agent/subagent-durability-failure.cordis.yml @@ -1,10 +1,5 @@ # Snapshot-only durability-failure overlay. The child turn's ordinary flush # succeeds; the provider-owned final confirmation fails deterministically. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: subagent-durability-failure - name: './tests/fixtures/subagent-durability-failure.ts' +- insert: + - id: subagent-durability-failure + name: './tests/fixtures/subagent-durability-failure.ts' diff --git a/examples/acp-agent/subagent-report.cordis.snapshot.yml b/examples/acp-agent/subagent-report.cordis.snapshot.yml index c18d0f3bac..c95a077220 100644 --- a/examples/acp-agent/subagent-report.cordis.snapshot.yml +++ b/examples/acp-agent/subagent-report.cordis.snapshot.yml @@ -1,46 +1,56 @@ # Keyless counterpart to subagent-report.cordis.yml: replace the live adapter # with replay and preserve its child and parent scheduling fence. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + provider: deepseek-official + model: deepseek-v4-flash - Verify your work by running the code or tests. Keep answers brief and factual. - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none -- id: report-fence - name: './tests/fixtures/subagent-report-fence.ts' +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + config: + runnerCommand: + - bash + - -c + - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" + - passthrough-runner + runnerFailureSignatures: + - 'passthrough-runner: profile rejected' + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + +- insert: + - id: report-fence + name: './tests/fixtures/subagent-report-fence.ts' diff --git a/examples/acp-agent/subagent-report.cordis.yml b/examples/acp-agent/subagent-report.cordis.yml index 038e598e65..d9e2376608 100644 --- a/examples/acp-agent/subagent-report.cordis.yml +++ b/examples/acp-agent/subagent-report.cordis.yml @@ -1,10 +1,6 @@ # Snapshot-only overlay fencing the child behind its parent's spawn turn and # holding the parent in maintenance until settlement follows the default # next-step report. The resumed parent claims both notices in causal order. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - -- id: report-fence - name: './tests/fixtures/subagent-report-fence.ts' +- insert: + - id: report-fence + name: './tests/fixtures/subagent-report-fence.ts' diff --git a/examples/acp-agent/subagent-result-diagnostic.cordis.snapshot.yml b/examples/acp-agent/subagent-result-diagnostic.cordis.snapshot.yml index 563f7d6864..85bed25228 100644 --- a/examples/acp-agent/subagent-result-diagnostic.cordis.snapshot.yml +++ b/examples/acp-agent/subagent-result-diagnostic.cordis.snapshot.yml @@ -1,29 +1,25 @@ # Keyless twin of subagent-result-diagnostic.cordis.yml: keep the same test # provider/tool and replace only the external model adapter. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro - - id: subagent-result-diagnostic - name: './tests/fixtures/subagent-result-diagnostic.ts' - - id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: snapshot-diagnostic - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: provider-managed - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + - id: subagent-result-diagnostic + name: './tests/fixtures/subagent-result-diagnostic.ts' + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: snapshot-diagnostic + toolName: subagent_codex + backgroundMode: one-shot + maxDepth: provider-managed + +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true diff --git a/examples/acp-agent/subagent-result-diagnostic.cordis.yml b/examples/acp-agent/subagent-result-diagnostic.cordis.yml index c82531c0e9..818b9e0a83 100644 --- a/examples/acp-agent/subagent-result-diagnostic.cordis.yml +++ b/examples/acp-agent/subagent-result-diagnostic.cordis.yml @@ -1,17 +1,12 @@ # Test-only product-shaped composition: mount a deterministic provider behind # the same one-shot tool schema as the public Codex example. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: subagent-result-diagnostic - name: './tests/fixtures/subagent-result-diagnostic.ts' - - id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: snapshot-diagnostic - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: provider-managed +- insert: + - id: subagent-result-diagnostic + name: './tests/fixtures/subagent-result-diagnostic.ts' + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: snapshot-diagnostic + toolName: subagent_codex + backgroundMode: one-shot + maxDepth: provider-managed diff --git a/examples/acp-agent/tests/acp.e2e.ts b/examples/acp-agent/tests/acp.e2e.ts index be8b1e6777..516d4ddb05 100644 --- a/examples/acp-agent/tests/acp.e2e.ts +++ b/examples/acp-agent/tests/acp.e2e.ts @@ -22,8 +22,9 @@ import { cleanupAcpExampleTest } from './cleanup.ts' */ const AGENT: AgentUnderTest = { - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), } const DANGER_FULL_ACCESS_ENV = { DSH_PERMISSION_MODE: 'danger-full-access' } diff --git a/examples/acp-agent/tests/acp.snapshot.ts b/examples/acp-agent/tests/acp.snapshot.ts index 0bfbe4e3ec..a43ea88750 100644 --- a/examples/acp-agent/tests/acp.snapshot.ts +++ b/examples/acp-agent/tests/acp.snapshot.ts @@ -29,12 +29,13 @@ import { OFFLOADED_IMAGE_TEXT } from '@deepseek-ai/dsh-llm' * .agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md. */ -// The dsh-acp-demo bin (the demo:acp entry), this example's cordis.yml, and +// The dsh CLI, this example's profile patch, and // the repo-root tsconfig (four levels up from examples/acp-agent/tests) — all // ABSOLUTE: the subprocess cwd is a temp dir outside the repo. const AGENT = { - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), } const EDITING_CORDIS_SKILL = fileURLToPath(new URL( @@ -42,8 +43,8 @@ const EDITING_CORDIS_SKILL = fileURLToPath(new URL( import.meta.url, )) -// The Code Mode overlay configs (include-patched variants of cordis.yml; the -// replay swap resolves each one's sibling `*cordis.snapshot.yml`). +// The Code Mode profile patches; replay selects each one's sibling +// `*cordis.snapshot.yml`. const CODE_MODE_CONFIG = fileURLToPath(new URL('../code-mode.cordis.yml', import.meta.url)) const CODE_MODE_IMAGE_CONFIG = fileURLToPath(new URL('../code-mode-image.cordis.yml', import.meta.url)) const CODE_MODE_WORKSPACE_CONTEXT_CONFIG = fileURLToPath(new URL('../code-mode-workspace-context.cordis.yml', import.meta.url)) @@ -238,6 +239,7 @@ const SCENARIOS: Scenario[] = [ recorded: false, pinsHeader: true, headerClass: 'image', + toolSchemasSource: 'text-turn', configPath: IMAGE_CONFIG, }, { @@ -247,7 +249,7 @@ const SCENARIOS: Scenario[] = [ pinsHeader: true, headerClass: 'image-text-route', systemPromptSource: 'text-turn', - toolSchemasSource: 'read-image', + toolSchemasSource: 'text-turn', configPath: IMAGE_TEXT_ROUTE_CONFIG, }, // Authored keyless replay of wide-image admission: the 2001x1 fixture sits diff --git a/examples/acp-agent/tests/control-surface.e2e.ts b/examples/acp-agent/tests/control-surface.e2e.ts index 96c44f2a1f..598a5559c2 100644 --- a/examples/acp-agent/tests/control-surface.e2e.ts +++ b/examples/acp-agent/tests/control-surface.e2e.ts @@ -1,4 +1,4 @@ -/** Generic keyless ACP v1 automation-control conformance over the real demo process. */ +/** Generic keyless ACP v1 automation-control conformance over the real dsh profile. */ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' @@ -12,13 +12,15 @@ import { } from '@deepseek-ai/dsh-acp-snapshot' import { describe, expect, it } from 'vitest' -const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url)) +const repoRoot = fileURLToPath(new URL('../../../', import.meta.url)) const agent: AgentUnderTest = { - binScript: join(repoRoot, 'packages/examples/acp-demo/src/bin.ts'), - configPath: fileURLToPath(new URL('./control-surface.cordis.yml', import.meta.url)), + binScript: join(repoRoot, 'apps/cli/src/bin.ts'), + libBinScript: join(repoRoot, 'apps/cli/lib/bin.js'), + configPath: fileURLToPath(new URL('./fixtures/control-surface/cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: join(repoRoot, 'tsconfig.json'), } -const mcpServer = fileURLToPath(new URL('../../../mcp/mcp-client/tests/fixture-server.ts', import.meta.url)) +const mcpServer = fileURLToPath(new URL('../../../packages/mcp/mcp-client/tests/fixture-server.ts', import.meta.url)) /** Find one named select value in grouped or ungrouped standard options. */ function selectValue( @@ -38,7 +40,7 @@ describe('standard ACP v1 control surface', () => { it('selects, mounts MCP, closes, restarts, resumes, and cancels through the SDK only', async () => { const cwd = await mkdtemp(join(tmpdir(), 'dsh-acp-control-')) const persistenceRoot = join(cwd, '.sessions') - const env = { DSH_CONFORMANCE_PERSISTENCE_ROOT: persistenceRoot } + const env = { DSH_CONFORMANCE_PERSISTENCE_ROOT: persistenceRoot, DSH_TELEMETRY_DISABLED: '1' } const mcpServers = [{ name: 'fixture', command: process.execPath, args: [mcpServer], env: [] }] let first: LaunchedAcpTestAgent | undefined let second: LaunchedAcpTestAgent | undefined diff --git a/examples/acp-agent/tests/escalation.e2e.ts b/examples/acp-agent/tests/escalation.e2e.ts index 831e7631b2..335eaf11bd 100644 --- a/examples/acp-agent/tests/escalation.e2e.ts +++ b/examples/acp-agent/tests/escalation.e2e.ts @@ -19,7 +19,7 @@ import { cleanupAcpExampleTest } from './cleanup.ts' /** * The default ACP composition (`cordis.yml`) end to end. * - * Keyless smoke: boot the REAL `cordis.yml` through the `dsh-acp-agent` bin as + * Keyless smoke: boot the real profile patch through `dsh --profile acp` as * an ACP subprocess and drive initialize + session/new — the real-Loader-path * guard (postmortem 0001) for THIS tree's exports, including the * sandbox executor AND the approval service. No prompt is sent, so neither the @@ -34,8 +34,9 @@ import { cleanupAcpExampleTest } from './cleanup.ts' */ const AGENT: AgentUnderTest = { - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), } diff --git a/examples/acp-agent/tests/fixtures/control-surface/cordis.yml b/examples/acp-agent/tests/fixtures/control-surface/cordis.yml index e8fd47adc5..a74e909b1b 100644 --- a/examples/acp-agent/tests/fixtures/control-surface/cordis.yml +++ b/examples/acp-agent/tests/fixtures/control-surface/cordis.yml @@ -1,15 +1,21 @@ -# Keyless generic ACP v1 control-surface conformance composition. -- id: control-surface-llm - name: './control-surface-llm.ts' +# Keyless generic ACP v1 control-surface patch over `dsh --profile acp`. -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: control-fixture model: alpha - persistenceRoot: !!js process.env.DSH_CONFORMANCE_PERSISTENCE_ROOT - persistenceCompression: none - workspaceContext: false -- id: token-meter - name: '../../../llm/token-meter/src/index.ts' +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_CONFORMANCE_PERSISTENCE_ROOT + compression: none + +- insert: + - id: control-surface-llm + name: './control-surface-llm.ts' diff --git a/examples/acp-agent/tests/fixtures/image-offload.cordis.yml b/examples/acp-agent/tests/fixtures/image-offload.cordis.yml index 320e66fe06..5d164f8e9d 100644 --- a/examples/acp-agent/tests/fixtures/image-offload.cordis.yml +++ b/examples/acp-agent/tests/fixtures/image-offload.cordis.yml @@ -1,37 +1,44 @@ # Keyless assembled-request snapshot for native DeepSeek image offload. The # local provider endpoint is supplied by the snapshot test; the real attachment # store and ACP bridge carry two uploaded images into one model request. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' config: - path: ../../cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - config: - apiKeyEnv: DSH_SNAPSHOT_API_KEY - baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL - thinking: disabled - maxRequestFilesBytes: 92 - imageOffloadByteQuantum: 1 - models: - - id: deepseek-v4-flash-vision-exp - contextWindow: 32768 - maxTokens: 1024 - inputModalities: [text, image] - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash-vision-exp - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + apiKeyEnv: DSH_SNAPSHOT_API_KEY + baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL + thinking: disabled + maxRequestFilesBytes: 92 + imageOffloadByteQuantum: 1 + models: + - id: deepseek-v4-flash-vision-exp + contextWindow: 32768 + maxTokens: 1024 + inputModalities: [text, image] - Keep answers brief and factual. - - insert: - - id: attachment-local - name: '@deepseek-ai/dsh-attachment-local' +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash-vision-exp + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Keep answers brief and factual. + +- id: attachment-local + name: '@deepseek-ai/dsh-attachment-local' diff --git a/examples/acp-agent/tests/fs-diff-bound.cordis.snapshot.yml b/examples/acp-agent/tests/fs-diff-bound.cordis.snapshot.yml index ac89b962eb..d43293c1a3 100644 --- a/examples/acp-agent/tests/fs-diff-bound.cordis.snapshot.yml +++ b/examples/acp-agent/tests/fs-diff-bound.cordis.snapshot.yml @@ -1,46 +1,53 @@ -# Keyless replay counterpart to fs-diff-bound.cordis.yml. Replay patches apply -# directly against the live cordis.yml because include patches cannot target -# entries behind a nested include; the acp-agent restatement keeps the recorded -# deepseek-v4-flash model and raw JSONL persistence for the harness's harvest. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ../cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. +# Keyless replay counterpart to fs-diff-bound.cordis.yml. The ACP and +# persistence rows keep the recorded flash model and raw JSONL harvest. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true - Verify your work by running the code or tests. Keep answers brief and factual. - - id: fs-sandbox - name: '@deepseek-ai/dsh-fs-sandbox' - config: - cwd: !!js process.cwd() - diffBasisMaxBytes: 64 - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - # Capability parity with the live adapter so replay - # reconstructs the freshly recorded request header. - - id: deepseek-v4-flash - contextWindow: 1000000 - defaultMaxTokens: 256000 - reasoningEfforts: ['off', 'low', 'high', 'max'] - defaultReasoningEffort: max - - id: deepseek-v4-pro +- id: acp + name: '@deepseek-ai/dsh-acp' + config: + provider: deepseek-official + model: deepseek-v4-flash + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: fs-sandbox + name: '@deepseek-ai/dsh-fs-sandbox' + config: + cwd: !!js process.cwd() + diffBasisMaxBytes: 64 + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + # Capability parity with the live adapter so replay + # reconstructs the freshly recorded request header. + - id: deepseek-v4-flash + contextWindow: 1000000 + defaultMaxTokens: 256000 + reasoningEfforts: ['off', 'low', 'high', 'max'] + defaultReasoningEffort: max + - id: deepseek-v4-pro diff --git a/examples/acp-agent/tests/fs-diff-bound.cordis.yml b/examples/acp-agent/tests/fs-diff-bound.cordis.yml index c82a7ce63a..bb8298a392 100644 --- a/examples/acp-agent/tests/fs-diff-bound.cordis.yml +++ b/examples/acp-agent/tests/fs-diff-bound.cordis.yml @@ -4,26 +4,33 @@ # diff. A config patch replaces the row's whole config, so `cwd` is restated # verbatim, and the acp-agent restatement re-pins `deepseek-v4-flash` to match # the recorded corpus and its pinned request headers. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: acp + name: '@deepseek-ai/dsh-acp' config: - path: ../cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + provider: deepseek-official + model: deepseek-v4-flash - Verify your work by running the code or tests. Keep answers brief and factual. - - id: fs-sandbox - name: '@deepseek-ai/dsh-fs-sandbox' - config: - cwd: !!js process.cwd() - diffBasisMaxBytes: 64 +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + persona: | + You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: fs-sandbox + name: '@deepseek-ai/dsh-fs-sandbox' + config: + cwd: !!js process.cwd() + diffBasisMaxBytes: 64 diff --git a/examples/acp-agent/tests/fs-search.cordis.snapshot.yml b/examples/acp-agent/tests/fs-search.cordis.snapshot.yml index d947694ffb..bc32c04cf2 100644 --- a/examples/acp-agent/tests/fs-search.cordis.snapshot.yml +++ b/examples/acp-agent/tests/fs-search.cordis.snapshot.yml @@ -1,35 +1,81 @@ # Minimal keyless composition: real app, bash, and search tool; replayed model. -- id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-pro +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-pro - id: subprocess name: '@deepseek-ai/dsh-subprocess-local' -- id: bash - name: '@deepseek-ai/dsh-bash-local' - -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: false - skills: - enabled: false - toolJobs: false - goals: false + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: You are a concise snapshot agent working in {{cwd}}. +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + disabled: true + +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + - id: tool-fs-search name: '@deepseek-ai/dsh-tool-fs-search' config: sampleOverCapGlobResults: true globMaxResults: 4 + +- id: spill-local + name: '@deepseek-ai/dsh-spill-local' + config: + root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' diff --git a/examples/acp-agent/tests/fs-search.cordis.yml b/examples/acp-agent/tests/fs-search.cordis.yml index 7de4f44faa..5aa2848a1b 100644 --- a/examples/acp-agent/tests/fs-search.cordis.yml +++ b/examples/acp-agent/tests/fs-search.cordis.yml @@ -8,25 +8,66 @@ - id: subprocess name: '@deepseek-ai/dsh-subprocess-local' -- id: bash - name: '@deepseek-ai/dsh-bash-local' - -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: false - skills: - enabled: false - toolJobs: false - goals: false + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: You are a concise snapshot agent working in {{cwd}}. +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + disabled: true + +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + - id: tool-fs-search name: '@deepseek-ai/dsh-tool-fs-search' config: sampleOverCapGlobResults: true globMaxResults: 4 + +- id: spill-local + name: '@deepseek-ai/dsh-spill-local' + config: + root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill' diff --git a/examples/acp-agent/tests/goal.snapshot.ts b/examples/acp-agent/tests/goal.snapshot.ts index 95d224ccb0..f2fb575ac7 100644 --- a/examples/acp-agent/tests/goal.snapshot.ts +++ b/examples/acp-agent/tests/goal.snapshot.ts @@ -24,8 +24,9 @@ const wrapupDir = join(dirname(fileURLToPath(import.meta.url)), 'goal-snapshots/ const refreshing = process.env.DSH_SNAPSHOT === 'refresh' const agent: AgentUnderTest = { - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), } diff --git a/examples/acp-agent/tests/hooks.e2e.ts b/examples/acp-agent/tests/hooks.e2e.ts index 994273a821..15783b38c4 100644 --- a/examples/acp-agent/tests/hooks.e2e.ts +++ b/examples/acp-agent/tests/hooks.e2e.ts @@ -19,8 +19,9 @@ import { cleanupAcpExampleTest } from './cleanup.ts' */ const AGENT: AgentUnderTest = { - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), } diff --git a/examples/acp-agent/tests/lsp.cordis.snapshot.yml b/examples/acp-agent/tests/lsp.cordis.snapshot.yml index 6cded4a260..47d07bd18e 100644 --- a/examples/acp-agent/tests/lsp.cordis.snapshot.yml +++ b/examples/acp-agent/tests/lsp.cordis.snapshot.yml @@ -1,36 +1,33 @@ # Keyless replay keeps the LSP composition intact and replaces only the model adapter. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ../cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: lsp - name: '@deepseek-ai/dsh-lsp' - - id: lsp-stdio - name: '@deepseek-ai/dsh-lsp-stdio' - config: - servers: - fixture: - command: !!js process.execPath - args: ['./lsp-server.mjs'] - extensionToLanguage: - '.ts': typescript - - id: timeout-policy - name: '@deepseek-ai/dsh-tool-call-timeout-policy' - - id: tool-lsp - name: '@deepseek-ai/dsh-tool-lsp' - config: - maxLocations: 1 - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: lsp + name: '@deepseek-ai/dsh-lsp' + - id: lsp-stdio + name: '@deepseek-ai/dsh-lsp-stdio' + config: + servers: + fixture: + command: !!js process.execPath + args: ['./lsp-server.mjs'] + extensionToLanguage: + '.ts': typescript + - id: tool-lsp + name: '@deepseek-ai/dsh-tool-lsp' + config: + maxLocations: 1 + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + +- id: timeout-policy + name: '@deepseek-ai/dsh-tool-call-timeout-policy' diff --git a/examples/acp-agent/tests/lsp.cordis.yml b/examples/acp-agent/tests/lsp.cordis.yml index f7d17e8cdd..49296a9632 100644 --- a/examples/acp-agent/tests/lsp.cordis.yml +++ b/examples/acp-agent/tests/lsp.cordis.yml @@ -1,25 +1,21 @@ # Exercise the model-facing LSP tool through the shipped ACP app and Loader entry path. # The scenario workspace supplies the deterministic stdio server used by this test composition. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ../cordis.yml - patches: - - insert: - - id: lsp - name: '@deepseek-ai/dsh-lsp' - - id: lsp-stdio - name: '@deepseek-ai/dsh-lsp-stdio' - config: - servers: - fixture: - command: !!js process.execPath - args: ['./lsp-server.mjs'] - extensionToLanguage: - '.ts': typescript - - id: timeout-policy - name: '@deepseek-ai/dsh-tool-call-timeout-policy' - - id: tool-lsp - name: '@deepseek-ai/dsh-tool-lsp' - config: - maxLocations: 1 +- insert: + - id: lsp + name: '@deepseek-ai/dsh-lsp' + - id: lsp-stdio + name: '@deepseek-ai/dsh-lsp-stdio' + config: + servers: + fixture: + command: !!js process.execPath + args: ['./lsp-server.mjs'] + extensionToLanguage: + '.ts': typescript + - id: tool-lsp + name: '@deepseek-ai/dsh-tool-lsp' + config: + maxLocations: 1 + +- id: timeout-policy + name: '@deepseek-ai/dsh-tool-call-timeout-policy' diff --git a/examples/acp-agent/tests/persistent-pwsh.cordis.snapshot.yml b/examples/acp-agent/tests/persistent-pwsh.cordis.snapshot.yml index 7b90b2298b..0d4848dc06 100644 --- a/examples/acp-agent/tests/persistent-pwsh.cordis.snapshot.yml +++ b/examples/acp-agent/tests/persistent-pwsh.cordis.snapshot.yml @@ -1,15 +1,21 @@ # Keyless replay counterpart to persistent-pwsh.cordis.yml. -- id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-pro +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true -- id: terminal - name: '@deepseek-ai/dsh-terminal' +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-pro + +- insert: + - id: terminal + name: '@deepseek-ai/dsh-terminal' - id: sandbox-policy name: '@deepseek-ai/dsh-sandbox-policy' @@ -20,26 +26,70 @@ - id: subprocess name: '@deepseek-ai/dsh-subprocess-local' -- id: terminal-pwsh - name: '@deepseek-ai/dsh-terminal-bash' - config: - shellDialect: pwsh - timeoutMs: 30000 +- insert: + - id: terminal-pwsh + name: '@deepseek-ai/dsh-terminal-bash' + config: + shellDialect: pwsh + timeoutMs: 30000 -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: false - skills: - enabled: false - toolBash: false - toolJobs: false - goals: false + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: You are a concise snapshot agent working in {{cwd}}. -- id: tool-pwsh-persistent - name: '@deepseek-ai/dsh-tool-pwsh-persistent' +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + disabled: true + +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: true + +- insert: + - id: tool-pwsh-persistent + name: '@deepseek-ai/dsh-tool-pwsh-persistent' diff --git a/examples/acp-agent/tests/persistent-pwsh.cordis.yml b/examples/acp-agent/tests/persistent-pwsh.cordis.yml index 0b3cd18c70..ae350b81a0 100644 --- a/examples/acp-agent/tests/persistent-pwsh.cordis.yml +++ b/examples/acp-agent/tests/persistent-pwsh.cordis.yml @@ -14,29 +14,74 @@ - id: subprocess name: '@deepseek-ai/dsh-subprocess-local' -- id: terminal - name: '@deepseek-ai/dsh-terminal' +- insert: + - id: terminal + name: '@deepseek-ai/dsh-terminal' -- id: terminal-pwsh - name: '@deepseek-ai/dsh-terminal-bash' - config: - shellDialect: pwsh - timeoutMs: 30000 +- insert: + - id: terminal-pwsh + name: '@deepseek-ai/dsh-terminal-bash' + config: + shellDialect: pwsh + timeoutMs: 30000 -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: false - skills: - enabled: false - toolBash: false - toolJobs: false - goals: false + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: You are a concise snapshot agent working in {{cwd}}. -- id: tool-pwsh-persistent - name: '@deepseek-ai/dsh-tool-pwsh-persistent' +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + disabled: true + +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: true + +- insert: + - id: tool-pwsh-persistent + name: '@deepseek-ai/dsh-tool-pwsh-persistent' diff --git a/examples/acp-agent/tests/pwsh.cordis.snapshot.yml b/examples/acp-agent/tests/pwsh.cordis.snapshot.yml index 600c475599..daea88f5a1 100644 --- a/examples/acp-agent/tests/pwsh.cordis.snapshot.yml +++ b/examples/acp-agent/tests/pwsh.cordis.snapshot.yml @@ -1,38 +1,85 @@ # Minimal keyless composition: real app, pwsh executor, and pwsh tool; replayed model. -- id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-pro +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-pro - id: subprocess name: '@deepseek-ai/dsh-subprocess-local' -- id: bash - name: '@deepseek-ai/dsh-pwsh-local' +- id: bash-sandbox + name: '@deepseek-ai/dsh-bash-sandbox' + disabled: true + +- id: pwsh-sandbox + name: '@deepseek-ai/dsh-pwsh-sandbox' + disabled: false - id: shell-env name: '@deepseek-ai/dsh-shell-env' -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: false - skills: - enabled: false - # job_output/job_kill stay mounted (the bundle's toolJobs default) so - # background pwsh runs are readable and killable. - goals: false - # The pwsh tool replaces the bundle's bash tool in this composition. - toolBash: false + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: You are a concise snapshot agent working in {{cwd}}. +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: true + - id: tool-pwsh name: '@deepseek-ai/dsh-tool-pwsh' + disabled: false diff --git a/examples/acp-agent/tests/pwsh.cordis.yml b/examples/acp-agent/tests/pwsh.cordis.yml index cb099aa66a..c76298bb06 100644 --- a/examples/acp-agent/tests/pwsh.cordis.yml +++ b/examples/acp-agent/tests/pwsh.cordis.yml @@ -8,28 +8,70 @@ - id: subprocess name: '@deepseek-ai/dsh-subprocess-local' -- id: bash - name: '@deepseek-ai/dsh-pwsh-local' +- id: bash-sandbox + name: '@deepseek-ai/dsh-bash-sandbox' + disabled: true + +- id: pwsh-sandbox + name: '@deepseek-ai/dsh-pwsh-sandbox' + disabled: false - id: shell-env name: '@deepseek-ai/dsh-shell-env' -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' +- id: acp + name: '@deepseek-ai/dsh-acp' config: provider: deepseek-official model: deepseek-v4-pro - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: false - skills: - enabled: false - # job_output/job_kill stay mounted (the bundle's toolJobs default) so - # background pwsh runs are readable and killable. - goals: false - # The pwsh tool replaces the bundle's bash tool in this composition. - toolBash: false + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: persona: You are a concise snapshot agent working in {{cwd}}. +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: true + - id: tool-pwsh name: '@deepseek-ai/dsh-tool-pwsh' + disabled: false diff --git a/examples/acp-agent/web.cordis.snapshot.yml b/examples/acp-agent/web.cordis.snapshot.yml index 3f06d6fd41..c64d81ee15 100644 --- a/examples/acp-agent/web.cordis.snapshot.yml +++ b/examples/acp-agent/web.cordis.snapshot.yml @@ -1,31 +1,29 @@ # Keyless replay counterpart to web.cordis.yml: the web stack and loopback # fixture server stay real (the tool call re-executes the actual HTTP fetch and # markdown rendering); only the model adapter is replaced by replay. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + +- insert: + - id: web-fetch-http + name: '@deepseek-ai/dsh-web-fetch-http' + - id: web-fetch-fixture + name: './web-fetch-fixture-server.mjs' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-flash + - id: deepseek-v4-pro + +- id: web + name: '@deepseek-ai/dsh-web' + +- id: tool-web + name: '@deepseek-ai/dsh-tool-web' config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: web - name: '@deepseek-ai/dsh-web' - - id: web-fetch-http - name: '@deepseek-ai/dsh-web-fetch-http' - - id: web-fetch-fixture - name: './web-fetch-fixture-server.mjs' - - id: tool-web - name: '@deepseek-ai/dsh-tool-web' - config: - search: false - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek-official - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro + search: false diff --git a/examples/acp-agent/web.cordis.yml b/examples/acp-agent/web.cordis.yml index 08a7b223ea..cce5b02a6d 100644 --- a/examples/acp-agent/web.cordis.yml +++ b/examples/acp-agent/web.cordis.yml @@ -3,19 +3,16 @@ # the pinned header carries exactly the surface under test), and the loopback # fixture server the scenario prompt fetches — deterministic content, no # external network, in recording and replay alike. -- id: base - name: '@deepseek-ai/cordis-plugin-include' +- insert: + - id: web-fetch-http + name: '@deepseek-ai/dsh-web-fetch-http' + - id: web-fetch-fixture + name: './web-fetch-fixture-server.mjs' + +- id: web + name: '@deepseek-ai/dsh-web' + +- id: tool-web + name: '@deepseek-ai/dsh-tool-web' config: - path: ./cordis.yml - patches: - - insert: - - id: web - name: '@deepseek-ai/dsh-web' - - id: web-fetch-http - name: '@deepseek-ai/dsh-web-fetch-http' - - id: web-fetch-fixture - name: './web-fetch-fixture-server.mjs' - - id: tool-web - name: '@deepseek-ai/dsh-tool-web' - config: - search: false + search: false diff --git a/packages/boot/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml index 062ad28d5e..bcdd303a0d 100644 --- a/packages/boot/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.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 packages/boot/app-boot/README.md -README.md: 791a0c31e0582b5494221397fbd8171508d5c12f -README.zh.md: 5cb6dcde7722c2c63f36873b6f402ec957dcb91a +README.md: 9965d6d57f4ec6cd9a93650bbd0096b059fb010b +README.zh.md: 436b19e4b2a05f4462f7636fccd3911b8ed41548 diff --git a/packages/boot/app-boot/README.md b/packages/boot/app-boot/README.md index 791a0c31e0..9965d6d57f 100644 --- a/packages/boot/app-boot/README.md +++ b/packages/boot/app-boot/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Shared boot glue for the app bins ([`dsh`](../../../apps/cli/README.md) and [`dsh-acp-demo`](../../examples/acp-demo/README.md)): each bin is a thin self-executing composition over these helpers, parameterized by its diagnostic prefix, so loader-failure behavior has one owner instead of drifting between published artifacts. +Shared Loader boot glue for [`dsh`](../../../apps/cli/README.md) profiles and the [temporarily packaged Python SDK runtime](../../../python/README.md). The product launcher owns profile composition and process lifecycle; the direct-config helpers remain only for that held-back runtime until its later migration. | Export | Role | |---|---| diff --git a/packages/boot/app-boot/README.zh.md b/packages/boot/app-boot/README.zh.md index 5cb6dcde77..436b19e4b2 100644 --- a/packages/boot/app-boot/README.zh.md +++ b/packages/boot/app-boot/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -供 app bin([`dsh`](../../../apps/cli/README.zh.md) 与 [`dsh-acp-demo`](../../examples/acp-demo/README.zh.md))共用的启动粘合层:每个 bin 都是在这些辅助函数之上构建的精简自执行组合,并以自身诊断前缀参数化。这样,Loader 故障行为只由一处负责,不会在已发布产物之间逐渐分化。 +供 [`dsh`](../../../apps/cli/README.zh.md) profile 与[暂时打包的 Python SDK runtime](../../../python/README.zh.md) 共用的 Loader 启动粘合层。产品启动器负责 profile 组合与进程生命周期;直接配置 helper 只为暂缓迁移的 runtime 保留,直至后续迁移。 | 导出 | 职责 | |---|---| diff --git a/packages/boot/app-boot/src/index.ts b/packages/boot/app-boot/src/index.ts index 17cff14213..ecfe695f34 100644 --- a/packages/boot/app-boot/src/index.ts +++ b/packages/boot/app-boot/src/index.ts @@ -1,5 +1,5 @@ /** - * Shared boot glue for the app bins (`dsh`, `dsh-acp-demo`): load the gitignored + * Shared boot glue for `dsh` profiles and the temporarily packaged Python SDK runtime: load the gitignored * `.env`, install the fail-loud Loader guards, resolve the config path (snapshot-aware), load the * optional user patch layers from the Harness home (`~/.dsh`), expose its path resolver to * config expressions, and drive the Cordis Loader against a leaf `cordis.yml` until the tree settles. diff --git a/packages/bundle/acp-app/README.i18n.yaml b/packages/bundle/acp-app/README.i18n.yaml index bfbeeb377f..9167d4ede3 100644 --- a/packages/bundle/acp-app/README.i18n.yaml +++ b/packages/bundle/acp-app/README.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 packages/bundle/acp-app/README.md -README.md: 5c0910dcdef2ddffe67aef29e14db371272238ee -README.zh.md: 7a70a1b34b1355095884a024b8192f6b10453143 +README.md: 15890b8d13446613bbddc1b764290470abf28d1a +README.zh.md: 5bf85d224ee07d8fa013f7cd0dd67a67a8d70dbf diff --git a/packages/bundle/acp-app/README.md b/packages/bundle/acp-app/README.md index 5c0910dcde..15890b8d13 100644 --- a/packages/bundle/acp-app/README.md +++ b/packages/bundle/acp-app/README.md @@ -4,10 +4,16 @@ English | [中文](README.zh.md) The automation-only ACP stdio application as a `dsh` profile bundle over [`dsh-base`](../base/README.md). Its patch sets the coding-agent persona and default model route, disables module HMR, mounts an app-owned zero-option command provider, and starts [`dsh-acp`](../../acp/acp/README.md) only after that provider accepts the invocation. `dsh --profile acp --help` therefore writes help and exits without claiming stdin or stdout. -The startup provider binds stdin EOF to the launcher's bounded successful shutdown. ACP connection close, SIGINT, and SIGTERM drain the bridge-owned agents and the root profile tree before exit. Stdout is reserved for newline-delimited ACP JSON-RPC frames. A deployment selects a different complete composition through profile bundles and patch files, not another app bin. +The startup provider binds stdin EOF to the launcher's bounded successful shutdown. ACP connection close, SIGINT, and SIGTERM drain the bridge-owned agents and the root profile tree before exit. Stdout is reserved for newline-delimited ACP JSON-RPC frames. The bundle disables model-generated session titles because ACP exposes no title surface; deterministic fallback titles remain durable without an auxiliary model request. A deployment selects a different complete composition through profile bundles and patch files, not another app bin. The shipped row creates sessions with `deepseek-official` and `deepseek-v4-flash`; a later patch can replace that row's complete config. The base profile owns adapters, tools, persistence, policy, settings, credentials, and the per-session workspace supplied by the ACP client. +## Standard automation workflow + +An ACP v1 SDK client initializes `dsh --profile acp`, creates a session with an absolute `cwd` and optional standard stdio/HTTP MCP declarations, chooses an advertised `model` or `reasoning_effort`, prompts while observing standard semantic updates, then calls `session/close`. Another process can use `session/list` and `session/resume` against the same profile persistence root; resume reconnects the MCP declarations supplied by that request and does not replay history. + +The complete supported method matrix, MCP trust model, update mapping, and stop reasons live in the [`dsh-acp` protocol contract](../../acp/acp/README.md#standard-acp-v1-surface). This profile adds no private method, capability, `_meta`, environment variable, or transport field. The keyless control-surface conformance test drives the real profile through the public ACP SDK. + ## Model Experience ### ACP coding-agent persona diff --git a/packages/bundle/acp-app/README.zh.md b/packages/bundle/acp-app/README.zh.md index 7a70a1b34b..5bf85d224e 100644 --- a/packages/bundle/acp-app/README.zh.md +++ b/packages/bundle/acp-app/README.zh.md @@ -4,10 +4,16 @@ 以 [`dsh-base`](../base/README.zh.md) 为基础的 automation-only ACP stdio 应用 `dsh` profile 组合包。其 patch 设置 coding agent(编程智能体)persona 与默认模型路由、禁用模块 HMR(热模块替换)、挂载应用自有的零选项命令提供方,并且只在该提供方接受调用后启动 [`dsh-acp`](../../acp/acp/README.zh.md)。因此,`dsh --profile acp --help` 会写出 help 并退出,不会占用 stdin 或 stdout。 -启动提供方把 stdin EOF 绑定到启动器的有界成功关闭。ACP 连接关闭、SIGINT 与 SIGTERM 会在退出前排空 bridge 自有 agent 以及根 profile 树。Stdout 仅保留给换行分隔的 ACP JSON-RPC frame。部署方通过 profile 组合包与 patch 文件选择另一套完整组合,而不是使用另一个 app bin。 +启动提供方把 stdin EOF 绑定到启动器的有界成功关闭。ACP 连接关闭、SIGINT 与 SIGTERM 会在退出前排空 bridge 自有 agent 以及根 profile 树。Stdout 仅保留给换行分隔的 ACP JSON-RPC frame。ACP 不提供 title 表层,因此本组合包禁用模型生成的 session title;确定性的 fallback title 仍会持久化,但不发起辅助模型请求。部署方通过 profile 组合包与 patch 文件选择另一套完整组合,而不是使用另一个 app bin。 随附配置项使用 `deepseek-official` 与 `deepseek-v4-flash` 创建 session;后续 patch 可以替换该配置项的完整 config。base profile 负责适配器、工具、持久化、策略、settings 与 credentials;ACP client 为每个 session 提供工作区。 +## 标准自动化工作流 + +ACP v1 SDK 客户端先初始化 `dsh --profile acp`,再用绝对 `cwd` 与可选的标准 stdio/HTTP MCP 声明创建 session,选择公开的 `model` 或 `reasoning_effort`,在观察标准语义更新的同时提交提示词,最后调用 `session/close`。另一个进程可以针对同一个 profile 持久化根目录使用 `session/list` 与 `session/resume`;resume 会重新连接该请求提供的 MCP 声明,但不会重放历史。 + +完整的受支持方法矩阵、MCP 信任模型、更新映射与停止原因见 [`dsh-acp` 协议约定](../../acp/acp/README.zh.md#standard-acp-v1-surface)。该 profile 不增加私有方法、能力、`_meta`、环境变量或传输字段。免密钥控制面一致性测试通过公开 ACP SDK 驱动真实 profile。 + ## 模型体验 ### ACP coding-agent persona diff --git a/packages/bundle/acp-app/cordis.patch.yml b/packages/bundle/acp-app/cordis.patch.yml index a6d25bb569..223e6dcddf 100644 --- a/packages/bundle/acp-app/cordis.patch.yml +++ b/packages/bundle/acp-app/cordis.patch.yml @@ -8,6 +8,9 @@ - id: hmr disabled: true +- id: session-title-llm + disabled: true + - insert: - id: acp-app-startup name: '@deepseek-ai/dsh-acp-app' diff --git a/packages/bundle/acp-app/tests/acp-app.spec.ts b/packages/bundle/acp-app/tests/acp-app.spec.ts index a72626f236..285ac95ee7 100644 --- a/packages/bundle/acp-app/tests/acp-app.spec.ts +++ b/packages/bundle/acp-app/tests/acp-app.spec.ts @@ -25,6 +25,7 @@ describe('dsh-acp-app bundle', () => { insert?: Array<{ config?: { model?: string; provider?: string }; id?: string; inject?: string[]; name?: string }> }> expect(patches.find(patch => patch.id === 'hmr')).toMatchObject({ disabled: true }) + expect(patches.find(patch => patch.id === 'session-title-llm')).toMatchObject({ disabled: true }) const rows = patches.flatMap(patch => patch.insert ?? []) expect(rows.find(row => row.id === 'acp-app-startup')?.name).toBe('@deepseek-ai/dsh-acp-app') expect(rows.find(row => row.id === 'acp')).toMatchObject({ diff --git a/packages/examples/acp-demo/README.i18n.yaml b/packages/examples/acp-demo/README.i18n.yaml deleted file mode 100644 index d96950f388..0000000000 --- a/packages/examples/acp-demo/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# 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 packages/examples/acp-demo/README.md -README.md: dea2831d372e0075cee7e948ee7abd185e27cf6c -README.zh.md: a1493ec1ded67cd8f936e76c4db0bfde9a0a7d42 diff --git a/packages/examples/acp-demo/README.md b/packages/examples/acp-demo/README.md deleted file mode 100644 index dea2831d37..0000000000 --- a/packages/examples/acp-demo/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# @deepseek-ai/dsh-acp-demo - -English | [中文](README.zh.md) - -ACP automation server app: the default agent spine, client-created agents through [`@deepseek-ai/dsh-acp`](../../acp/acp/README.md), JSONL persistence, and semantic checkpointing behind one JSON-RPC stdio bin. Programmatic clients create, list, close, and resume sessions; this package mounts no human UI. - -## Composition - -| Plugin | Role | -|---|---| -| `@deepseek-ai/dsh-agent-spine-demo` | Providerless agent spine with no pre-created agents; `session/new` creates each agent. | -| `@deepseek-ai/dsh-session-persistence-jsonl` | Durable session logs used by checkpointing, observability, and snapshot replay. | -| `@deepseek-ai/dsh-session-checkpoint-policy` | Durability barriers before model calls and top-level tool effects, plus completed-step checkpoints. | -| `@deepseek-ai/dsh-session-query-sqlite` | Derived exact/FTS session-query service, opened before the ACP transport so leaf consumers are ready for the first model request. | -| `@deepseek-ai/dsh-acp` | Automation-only ACP transport over stdin/stdout. | - -The app does not install commands, user interaction, session navigation, configuration pickers, or a stdout logger. It owns these plugins through one ordered effect so the query service is ready before ACP accepts work and ACP sessions quiesce before checkpointing and persistence detach. Leaf configurations supply LLM, executor, sandbox, approval, filesystem, and model-facing tool plugins. - -## Config - -| Key | Default | Routed to | -|---|---|---| -| `provider` | required | Provider route for each ACP-created agent. | -| `model` | required | Model for each ACP-created agent. | -| `maxParallelToolCalls` | agent-loop default | Positive-integer tool-call concurrency cap; `1` is serial. | -| `persona` | — | Deployment persona template for `dsh-system-prompt`. | -| `toolOrder` | lexicographic | Explicit model-facing tool order for `dsh-system-prompt`. | -| `tools` | `{ mode: 'native' }` | Native, Code Mode, or combined model tool transport. | -| `dshHome` | `$DSH_HOME` or `~/.dsh` | Harness home shared by bash and local skill discovery. | -| `sessionTitle` | spine example limits | Durable fallback-title limits; titles remain off the ACP wire. | -| `persistenceRoot` | `./.sessions` | JSONL backend root and parent directory of the derived `session-query.db` index. | -| `packChunks` | `true` | Pack consecutive delta-chunk events in storage. | -| `persistenceCompression` | `zstd` | Checksummed Zstandard frames or raw `none`. | -| `workspaceContext` | required | Workspace-instruction byte budget/config, or `false`. | -| `skills` | owner defaults | Skill registry, local provider, and model-facing skill tool. | -| `toolBash` | owner defaults | Model-facing bash tool config. | -| `jobs` | `{ maxConcurrentJobsPerOwner: 10 }` | Process-local per-owner active-task admission. | -| `toolJobs` | owner defaults | Generic background-job control config, or `false`. | -| `goals` | owner defaults | Persisted same-session goal domain and model tools, or `false`. | - -The shipped [`examples/acp-agent/cordis.yml`](../../../examples/acp-agent/cordis.yml) adds the DeepSeek adapter, sandboxed bash and filesystem providers, one-shot approval policy, compaction, subagents, workflows, hooks, and model-facing tools. The app supplies the derived session-query index, while the model-facing query consumer remains an explicit leaf opt-in. Snapshot overlays replace only nondeterministic providers or policy values. - -## Bin - -`dsh-acp-demo [--config path-to-cordis.yml]` (short form `-c`; default `./cordis.yml`) loads the gitignored `.env`, except in replay mode; `DSH_SNAPSHOT=replay` selects the sibling `cordis.snapshot.yml`; stdin EOF disposes the context and flushes sessions before exit. Loader's installed optional `node-addon-require-builtin` peer resolves bare plugin specifiers for the built bin under plain Node. Diagnostics use stderr because stdout is the ACP wire. - -## Standard automation workflow - -An ACP v1 SDK client initializes the bin, creates a session with an absolute `cwd` and optional standard stdio/HTTP MCP declarations, chooses an advertised `model` or `reasoning_effort`, prompts and observes semantic standard updates, then calls `session/close`. A later process can use `session/list` and `session/resume` against the same `persistenceRoot`; resume reconnects the MCP declarations supplied by that request and does not replay history. - -The complete supported/unsupported method matrix, MCP trust model, update mapping, and stop reasons live in the [`dsh-acp` protocol contract](../../acp/acp/README.md#standard-acp-v1-surface). The demo adds no private method, capability, `_meta`, environment variable, or transport field. The keyless control-surface conformance test drives this exact bin through the public ACP SDK. - -## Model Experience - -Indirectly, through `dsh-agent-spine-demo` and the leaf's model-facing plugins. ACP prompt text becomes the ordinary logged user message; protocol metadata and permission choices do not enter the model request. - -#### KV Cache effect - -Append-only per session; the app adds no request-prefix content itself. - -## Known Limitations and Deferred Work - -- **JSONL persistence is fixed** — a different backend requires another composition. -- **Sibling plugins can corrupt stdout** — the app cannot prevent another entry from writing non-protocol bytes. -- **Automation only** — session controls and semantic execution updates are protocol data, not a human UI; human interaction remains in other entry points. diff --git a/packages/examples/acp-demo/README.zh.md b/packages/examples/acp-demo/README.zh.md deleted file mode 100644 index a1493ec1de..0000000000 --- a/packages/examples/acp-demo/README.zh.md +++ /dev/null @@ -1,65 +0,0 @@ -# @deepseek-ai/dsh-acp-demo - -[English](README.md) | 中文 - -ACP(Agent Client Protocol)自动化服务器应用:默认 agent(智能体)主干、客户端通过 [`@deepseek-ai/dsh-acp`](../../acp/acp/README.zh.md) 创建的 agent、JSONL 持久化,以及语义检查点机制,并通过一个 JSON-RPC stdio bin 对外提供服务。程序化客户端可以创建、列出、关闭和恢复会话;此包不挂载人工交互 UI。 - -## 组合 - -| 插件 | 角色 | -|---|---| -| `@deepseek-ai/dsh-agent-spine-demo` | 不含提供方且不预创建 agent 的 agent 主干;`session/new` 创建每个 agent。 | -| `@deepseek-ai/dsh-session-persistence-jsonl` | 检查点、可观测性和快照回放所使用的持久会话日志。 | -| `@deepseek-ai/dsh-session-checkpoint-policy` | 在模型调用和顶层工具 effect 前建立持久性屏障,并为已完成步骤建立检查点。 | -| `@deepseek-ai/dsh-session-query-sqlite` | 派生的精确/FTS 会话查询服务;先于 ACP 传输打开,使叶节点消费方在首次模型请求前就绪。 | -| `@deepseek-ai/dsh-acp` | 通过 stdin/stdout 提供的纯自动化 ACP 传输。 | - -应用不安装命令、用户交互、会话导航、配置选择器或 stdout logger。它通过一个有序 effect 拥有这些插件,因此查询服务会在 ACP 接受工作前就绪,而 ACP 会话会在检查点与持久化插件卸载前完全停稳。叶节点配置负责提供 LLM(大语言模型)、执行器、沙箱、审批、文件系统和面向模型的工具插件。 - -## 配置 - -| 键 | 默认值 | 路由目标 | -|---|---|---| -| `provider` | 必填 | 每个由 ACP 创建的 agent 所用的提供方路由。 | -| `model` | 必填 | 每个由 ACP 创建的 agent 所用的模型。 | -| `maxParallelToolCalls` | agent loop(智能体循环)默认值 | 正整数工具调用并发上限;`1` 表示串行。 | -| `persona` | 无 | 供 `dsh-system-prompt` 使用的部署 persona 模板。 | -| `toolOrder` | 字典序 | 供 `dsh-system-prompt` 使用的显式面向模型工具顺序。 | -| `tools` | `{ mode: 'native' }` | Native、Code Mode 或组合式模型工具传输。 | -| `dshHome` | `$DSH_HOME` 或 `~/.dsh` | bash 与本地 skill(技能)发现共享的 harness 主目录。 | -| `sessionTitle` | 主干示例限制 | 持久后备标题限制;标题仍不会进入 ACP wire。 | -| `persistenceRoot` | `./.sessions` | JSONL 后端根目录,以及派生 `session-query.db` 索引的父目录。 | -| `packChunks` | `true` | 在存储中打包连续的增量分片事件。 | -| `persistenceCompression` | `zstd` | 带校验和的 Zstandard 帧,或原始 `none`。 | -| `workspaceContext` | 必填 | 工作区指令字节预算/配置,或 `false`。 | -| `skills` | 拥有者默认值 | skill 注册表、本地提供方和面向模型的 skill 工具。 | -| `toolBash` | 拥有者默认值 | 面向模型的 bash 工具配置。 | -| `jobs` | `{ maxConcurrentJobsPerOwner: 10 }` | 进程内按 owner 限制活动任务的准入配置。 | -| `toolJobs` | 拥有者默认值 | 通用后台任务控制配置,或 `false`。 | -| `goals` | 拥有者默认值 | 持久化的同会话目标领域与模型工具,或 `false`。 | - -已交付的 [`examples/acp-agent/cordis.yml`](../../../examples/acp-agent/cordis.yml) 添加 DeepSeek 适配器、沙箱化 bash 与文件系统提供方、一次性审批策略、压缩(compaction)、subagent、工作流、钩子,以及面向模型的工具。应用提供派生会话查询索引,而面向模型的查询消费方仍由叶节点显式选用。快照 overlay 只替换非确定性提供方或策略值。 - -## Bin - -`dsh-acp-demo [--config path-to-cordis.yml]`(短形式 `-c`;默认为 `./cordis.yml`)会加载 gitignore 排除的 `.env`,回放模式除外;`DSH_SNAPSHOT=replay` 选择同级 `cordis.snapshot.yml`;stdin EOF 会在退出前 dispose(资源释放)上下文并刷新会话。Loader 已安装的可选对等依赖(peer dependency)`node-addon-require-builtin` 使纯 Node 下构建后的 bin 可以解析裸插件说明符。诊断使用 stderr,因为 stdout 是 ACP wire。 - -## 标准自动化工作流 - -ACP v1 SDK 客户端先初始化 bin,再使用绝对 `cwd` 和可选标准 stdio/HTTP MCP 声明创建会话,选择已公布的 `model` 或 `reasoning_effort`,发送提示词并观察标准语义更新,最后调用 `session/close`。后续进程可以针对同一个 `persistenceRoot` 使用 `session/list` 和 `session/resume`;恢复会重新连接该请求提供的 MCP 声明,并且不会重放历史。 - -完整的支持/不支持方法矩阵、MCP 信任模型、更新映射和 stop reason 位于 [`dsh-acp` 协议约定](../../acp/acp/README.zh.md#standard-acp-v1-surface)。Demo 不增加私有方法、能力、`_meta`、环境变量或传输字段。Keyless control-surface conformance 测试只通过公开 ACP SDK 驱动这个确切 bin。 - -## 模型体验 - -模型体验由 `dsh-agent-spine-demo` 和叶节点的面向模型插件间接提供。ACP 提示词文本会成为普通的已记录用户消息;协议元数据与权限选择不会进入模型请求。 - -#### KV Cache 影响 - -每个会话仅追加;应用本身不添加请求前缀内容。 - -## 已知限制与暂缓事项 - -- **JSONL 持久化固定不变**:使用其他后端需要另一种组合。 -- **同级插件可能破坏 stdout**:应用无法阻止另一个 Cordis 配置项写入非协议字节。 -- **仅面向自动化**:会话控制和语义执行更新是协议数据,不是人工 UI;人工交互仍属于其他运行入口。 diff --git a/packages/examples/acp-demo/package.json b/packages/examples/acp-demo/package.json deleted file mode 100644 index dc422c008e..0000000000 --- a/packages/examples/acp-demo/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "@deepseek-ai/dsh-acp-demo", - "description": "ACP automation server app: agent spine + JSONL persistence + ACP transport, with a JSON-RPC stdio bin", - "version": "0.1.1-rc.2", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/examples/acp-demo" - }, - "type": "module", - "main": "lib/index.js", - "types": "lib/types/index.d.ts", - "bin": { - "dsh-acp-demo": "lib/bin.js" - }, - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "default": "./lib/index.js" - }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, - "./bin": { - "types": "./lib/types/bin.d.ts", - "default": "./lib/bin.js" - }, - "./src/*": "./src/*", - "./package.json": "./package.json" - }, - "files": [ - "lib/index.js", - "lib/invariant.js", - "lib/bin.js", - "lib/types/**/*.d.ts" - ], - "license": "MIT", - "peerDependencies": { - "@deepseek-ai/cordis-plugin-include": "workspace:^", - "@deepseek-ai/cordis-plugin-loader": "workspace:^", - "@deepseek-ai/dsh-acp": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", - "@deepseek-ai/dsh-app-boot": "workspace:^", - "@deepseek-ai/dsh-invariants": "workspace:^", - "@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^", - "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", - "@deepseek-ai/dsh-session-query": "workspace:^", - "@deepseek-ai/dsh-session-query-sqlite": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-agent-instructions": "workspace:^", - "@deepseek-ai/cordis": "workspace:^", - "@deepseek-ai/schemastery": "workspace:^" - }, - "devDependencies": { - "@agentclientprotocol/sdk": "1.4.0", - "@deepseek-ai/cordis-plugin-include": "workspace:^", - "@deepseek-ai/cordis-plugin-loader": "workspace:^", - "@deepseek-ai/dsh-acp": "workspace:^", - "@deepseek-ai/dsh-acp-snapshot": "workspace:^", - "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", - "@deepseek-ai/dsh-app-boot": "workspace:^", - "@deepseek-ai/dsh-invariants": "workspace:^", - "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^", - "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", - "@deepseek-ai/dsh-session-query": "workspace:^", - "@deepseek-ai/dsh-session-query-sqlite": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-agent-instructions": "workspace:^", - "@deepseek-ai/cordis": "workspace:^", - "@deepseek-ai/schemastery": "workspace:^" - } -} diff --git a/packages/examples/acp-demo/src/bin.ts b/packages/examples/acp-demo/src/bin.ts deleted file mode 100644 index 3f528a5fca..0000000000 --- a/packages/examples/acp-demo/src/bin.ts +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env node -/** - * Boot an ACP stdio server from `cordis.yml`; usage is - * `dsh-acp-demo [--config path]`, defaulting to `./cordis.yml`. Shared env - * loading, Loader guards, snapshot config selection, and settled-tree boot live - * in dsh-app-boot. Replay skips `.env` and selects sibling - * `cordis.snapshot.yml` so a stray key cannot trigger a model call. EOF disposes - * and flushes snapshot runs; the calling automation owns process lifetime. Stdout is - * reserved for JSON-RPC, so diagnostics go only to stderr. - * @module @deepseek-ai/dsh-acp-demo/bin - */ - -import { parseArgs } from 'node:util' -import { boot, installFailLoud, loadEnv, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' - -const NAME = 'dsh-acp-demo' - -/* v8 ignore start -- thin self-executing composition over the unit-tested - dsh-app-boot helpers; exercised end-to-end by the snapshot suite and the - built-bin smoke */ -installFailLoud(NAME) -const snapshotMode = process.env['DSH_SNAPSHOT'] -if (snapshotMode !== 'replay') loadEnv(NAME) -const { values } = parseArgs({ - args: process.argv.slice(2), - options: { config: { type: 'string', short: 'c' } }, - strict: true, -}) -const ctx = await boot(NAME, resolveConfigPath(values.config ?? './cordis.yml', snapshotMode)) -if (snapshotMode !== undefined) { - process.stdin.on('end', () => { - void ctx.fiber.dispose().then(() => { process.exit(0) }) - }) -} -/* v8 ignore stop */ diff --git a/packages/examples/acp-demo/src/index.ts b/packages/examples/acp-demo/src/index.ts deleted file mode 100644 index bd5981ae9e..0000000000 --- a/packages/examples/acp-demo/src/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * The ACP automation server app: the default agent spine - * ({@link @deepseek-ai/dsh-agent-spine-demo}), JSONL session persistence, and - * the {@link @deepseek-ai/dsh-acp} bridge. The app owns those plugins through one - * ordered lifecycle so ACP sessions quiesce before persistence detaches. It - * writes nothing to stdout. - * It pre-creates no agents and leaves adapters, executors, and optional tools to - * the leaf, which must likewise avoid stdout loggers. Named exports are - * required so Loader retains this plugin's `Config` schema (see - * docs/postmortem/0001). - * @module @deepseek-ai/dsh-acp-demo - */ - -import type { Context } from '@deepseek-ai/cordis' -import { join } from 'node:path' -import z from '@deepseek-ai/schemastery' -import * as acp from '@deepseek-ai/dsh-acp' -import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo' -import * as workspaceContext from '@deepseek-ai/dsh-agent-instructions' -import ToolRuntime, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools' -import JsonlSessionPersistence, { - JsonlCompressionSchema, - type JsonlCompression, -} from '@deepseek-ai/dsh-session-persistence-jsonl' -import * as sessionCheckpointPolicy from '@deepseek-ai/dsh-session-checkpoint-policy' -import SqliteSessionQueryEngine from '@deepseek-ai/dsh-session-query-sqlite' - -export const name = 'acp-demo' -const DEFAULT_PERSISTENCE_ROOT = './.sessions' - -/** - * App config: the swappable per-deployment values. `provider` and `model` configure - * each agent the ACP bridge creates at `session/new`; `persona` is the - * deployment persona (forwarded to the system-prompt plugin); `toolOrder` is - * the explicit model-facing tool order (forwarded to the system-prompt plugin); - * `tools` is the tool registry's config (its presentation `mode`, forwarded - * through agent-spine-demo); `persistenceRoot` is the JSONL backend's directory. - */ -export interface Config { - /** Provider route for ACP-created agents. */ - provider: string - /** Model name for ACP-created agents (must have a registered adapter). */ - model: string - /** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */ - maxParallelToolCalls?: number - /** Deployment persona (the system-prompt plugin's `persona` config). */ - persona?: string - /** Explicit model-facing tool order (the system-prompt plugin's `toolOrder` config; see dsh-system-prompt). */ - toolOrder?: string[] - /** Tool-registry config — its presentation `mode` (forwarded through agent-spine-demo; see dsh-tools). */ - tools?: ToolsConfig - /** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */ - dshHome?: string - /** Fallback session-title limits forwarded through agent-spine-demo. */ - sessionTitle?: NonNullable - /** Directory for JSONL sessions and the derived query index. Defaults to `./.sessions`. */ - persistenceRoot?: string - /** Write delta-chunk runs as packed storage rows (the JSONL backend's `packChunks`). Defaults to `true`. */ - packChunks?: boolean - /** JSONL artifact encoding; defaults to checksummed Zstandard frames. */ - persistenceCompression?: JsonlCompression - /** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */ - workspaceContext: agentCore.Config['workspaceContext'] - /** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */ - skills?: agentCore.SkillConfig - /** Model-facing bash tool config forwarded through agent-core. */ - toolBash?: NonNullable - /** Process-local background-job admission config forwarded through agent-core. */ - jobs?: NonNullable - /** Generic background-job controls forwarded through agent-core; set false to omit their tools. */ - toolJobs?: NonNullable - /** Persisted same-session goals; owner defaults enable them, or false disables the stack and tools. */ - goals?: agentCore.GoalConfig | false -} - -// Each entry point owns a complete, directly readable config schema; extracting -// the common fields would make two small app contracts depend on a new facade. -/* jscpd:ignore-start */ -export const Config: z = z.object({ - provider: z.string().required(), - model: z.string().required(), - maxParallelToolCalls: z.number().step(1).min(1), - persona: z.string(), - // The array default is forced to undefined: ABSENT means "lexicographic - // order" (the owning dsh-system-prompt schema does the same), while - // schemastery's native [] default would read as an invalid configured list. - toolOrder: z.array(z.string()).default(undefined as unknown as string[]), - tools: ToolRuntime.Config, - dshHome: z.string(), - sessionTitle: agentCore.SessionTitleConfigSchema, - persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT), - packChunks: z.boolean().default(true), - persistenceCompression: JsonlCompressionSchema, - workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(), - skills: agentCore.SkillConfigSchema, - toolBash: agentCore.ToolBashConfigSchema, - jobs: agentCore.JobsConfigSchema, - toolJobs: z.union([z.const(false), agentCore.ToolJobsConfigSchema]), - goals: z.union([z.const(false), agentCore.GoalConfigSchema]), -}) -/* jscpd:ignore-end */ - -/** - * Compose the spine with the ACP automation transport. The agent-spine-demo bundle pre-creates - * NO agents (its `agents` list defaults to `[]`) and carries the deployment - * `persona`; the JSONL backend and derived query index persist under - * `persistenceRoot`; the ACP bridge owns stdout for JSON-RPC and creates one - * agent per `session/new` from the provider/model pair. The composite effect - * unloads in reverse order, keeping checkpoint and persistence listeners - * attached until ACP agents have flushed their closing events. No logger, no - * `hmr` — stdout stays pure. - */ -export async function apply(ctx: Context, config: Config): Promise { - const goals = config.goals ?? {} - const persistenceRoot = config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT - await ctx.effect(async function* () { - const spine = ctx.plugin(agentCore, { ...agentCore.pickSpineConfig(config), goals }) - await spine - yield spine.dispose - // Same rationale as the Config schema above: each entry point forwards its own - // persistence passthroughs rather than sharing a facade with stdio-demo. - /* jscpd:ignore-start */ - const persistence = ctx.plugin(JsonlSessionPersistence, { - root: persistenceRoot, - ...config.packChunks !== undefined ? { packChunks: config.packChunks } : {}, - ...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }), - }) - await persistence - yield persistence.dispose - /* jscpd:ignore-end */ - const checkpoint = ctx.plugin(sessionCheckpointPolicy) - await checkpoint - yield checkpoint.dispose - const query = ctx.plugin(SqliteSessionQueryEngine, { path: join(persistenceRoot, 'session-query.db') }) - await query - yield query.dispose - const transport = ctx.plugin(acp, { provider: config.provider, model: config.model }) - await transport - yield transport.dispose - }, 'acp-demo.composition') -} diff --git a/packages/examples/acp-demo/src/invariant.ts b/packages/examples/acp-demo/src/invariant.ts deleted file mode 100644 index 106ba974d5..0000000000 --- a/packages/examples/acp-demo/src/invariant.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-acp-demo`. - * @module @deepseek-ai/dsh-acp-demo/invariant - */ - -/* jscpd:ignore-start */ -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-acp-demo' - -/** Cordis companion plugin name. */ -export const name = 'acp-demo-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * No runtime invariant: this composition package owns no independent event stream or mutable data; - * Loader and built-entry tests cover its wiring. - */ -const install: InvariantInstaller = () => {} - -/** - * Register this package's invariant companion. - * @param ctx - Cordis context carrying the invariant service. - * @returns the installed registration's disposer after setup succeeds. - */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) -/* jscpd:ignore-end */ diff --git a/packages/examples/acp-demo/tests/acp-agent.spec.ts b/packages/examples/acp-demo/tests/acp-agent.spec.ts deleted file mode 100644 index 0f49589929..0000000000 --- a/packages/examples/acp-demo/tests/acp-agent.spec.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { randomUUID } from 'node:crypto' -import { mkdtemp } from 'node:fs/promises' -import { join } from 'node:path' -import { tmpdir } from 'node:os' -import { Context } from '@deepseek-ai/cordis' -import Loader from '@deepseek-ai/cordis-plugin-loader' -import { agentEvents } from '@deepseek-ai/dsh-agent' -import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt' -import type { Message } from '@deepseek-ai/dsh-llm' -import { SessionId } from '@deepseek-ai/dsh-session' -import * as acpAgent from '../src/index.ts' - -/** - * In-process unit coverage for the @deepseek-ai/dsh-acp-demo composition: - * mounting it brings up the agent-spine-demo spine + JSONL persistence + the ACP - * bridge in one `ctx.plugin`. It loads no Loader-only plugin (no hmr), so it - * mounts in a plain Context. - * - * The REAL Loader-path guard (export shape via `unwrapExports`, the headline - * ACP operations end-to-end) is the keyless bin smoke in `load-path.e2e.ts`; - * this spec asserts the composition and the persistenceRoot default branch. - */ -async function mount(config: acpAgent.Config, withBash = false): Promise { - const ctx = new Context() - if (withBash) { - ctx.provide('shell', { - sandboxMode: undefined, - resolve() { throw new Error('composition test does not execute bash') }, - run() { throw new Error('composition test does not execute bash') }, - start() { throw new Error('composition test does not execute bash') }, - }) - } - config.persistenceRoot ??= await mkdtemp(join(tmpdir(), 'dsh-acp-demo-persistence-')) - await ctx.plugin(acpAgent, config) - return ctx -} - -async function isolatedSkillsConfig(catalogDescriptionMaxLength?: number): Promise> { - const home = await mkdtemp(join(tmpdir(), 'dsh-acp-demo-skills-')) - return { - filesystem: { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents') }, - ...catalogDescriptionMaxLength !== undefined ? { tool: { catalogDescriptionMaxLength } } : {}, - } -} - -async function composePrefix(ctx: Context): Promise { - const agent = ctx.agentLoop.create(SessionId(`acp-demo-prefix-${randomUUID()}`), {}, { cwd: '/tmp' }) - const signal = new AbortController().signal - const decision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', { messages: [], turn: 1, step: 1, signal }, - () => Promise.resolve({ kind: 'enter', messages: [] }), - ) - if (decision.kind === 'enter') { - for (const message of decision.messages) { - agent.session.append('user/message', message, { surfaceOp: 'append' }) - } - } - return agent.session.deriveMessages() -} - -async function withIsolatedSkillHomes(run: () => Promise): Promise { - const oldDshHome = process.env.DSH_HOME - const oldAgentsHome = process.env.DSH_AGENTS_HOME - const home = await mkdtemp(join(tmpdir(), 'dsh-acp-demo-default-skills-')) - process.env.DSH_HOME = join(home, '.dsh') - process.env.DSH_AGENTS_HOME = join(home, '.agents') - try { - return await run() - } finally { - if (oldDshHome === undefined) { - delete process.env.DSH_HOME - } else { - process.env.DSH_HOME = oldDshHome - } - if (oldAgentsHome === undefined) { - delete process.env.DSH_AGENTS_HOME - } else { - process.env.DSH_AGENTS_HOME = oldAgentsHome - } - } -} - -describe('dsh-acp-demo composition', () => { - it('brings up the spine + persistence + the ACP bridge', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - persona: 'hi', - persistenceRoot: await mkdtemp(join(tmpdir(), 'dsh-acp-demo-test-')), - persistenceCompression: 'none', - skills: await isolatedSkillsConfig(), - workspaceContext: false, - }) - expect(ctx.get('agents')).toBeDefined() - expect(ctx.get('sessions')).toBeDefined() - expect(ctx.get('sessionPersistence')).toBeDefined() - expect(ctx.get('sessionQuery')).toBeDefined() - expect(ctx.get('sessionReferenceResolver')).toBeUndefined() - expect((ctx.get('sessionPersistence') as unknown as { config: { compression?: string } }).config.compression).toBe('none') - expect(ctx.get('agentLoop')).toBeDefined() - expect(ctx.get('userQuestions')).toBeUndefined() - expect(ctx.get('commands')).toBeUndefined() - expect(ctx.get('tools')?.get('ask_user_question')).toBeUndefined() - expect(ctx.get('goals')).toBeDefined() - expect(ctx.get('tools')?.get('get_goal')).toBeDefined() - // No pre-created agents — ACP session/new creates them on demand. - expect(ctx.get('agents')!.list()).toHaveLength(0) - await ctx.fiber.dispose() - }) - - it('can explicitly omit the persisted-goal stack', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - goals: false, - workspaceContext: false, - }) - expect(ctx.get('goals')).toBeUndefined() - expect(ctx.get('tools')?.get('get_goal')).toBeUndefined() - await ctx.fiber.dispose() - }) - - it('defaults the persistence root when omitted', async () => { - // Exercises the `DEFAULT_PERSISTENCE_ROOT` fallback for a direct-apply caller that - // bypasses the schema's `.default(...)`: call `apply` directly (not via - // `ctx.plugin`, which validates+defaults the config first) with no - // persistenceRoot, so the runtime fallback is the one that fires. - const ctx = new Context() - // No persona: covers the omitted-persona forwarding branch too. - await acpAgent.apply(ctx, { - provider: 'mock', - model: 'mock', - skills: await isolatedSkillsConfig(), - workspaceContext: false, - }) - expect(ctx.get('sessionPersistence')).toBeDefined() - await ctx.fiber.dispose() - }) - - it('forwards explicit project-instruction controls to the bundled spine', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - persona: 'hi', - persistenceRoot: await mkdtemp(join(tmpdir(), 'dsh-acp-demo-workspace-context-')), - workspaceContext: false, - }) - expect(ctx.get('agents')).toBeDefined() - expect(ctx.get('agentLoop')).toBeDefined() - await ctx.fiber.dispose() - }) - - it('uses default skill config when apply is called directly without skills', async () => { - await withIsolatedSkillHomes(async () => { - const ctx = new Context() - await acpAgent.apply(ctx, { provider: 'mock', model: 'mock', workspaceContext: false }) - expect(ctx.skills).toBeDefined() - expect(await ctx.skills.list()).toEqual([]) - await ctx.fiber.dispose() - }) - }) - - it('forwards skill config and dshHome into agent-spine-demo', async () => { - const skills = await isolatedSkillsConfig(6) - const ctx = await mount({ provider: 'mock', model: 'mock', persona: 'hi', dshHome: skills.filesystem!.dshHome!, skills, workspaceContext: false }) - ctx.skills.register({ name: 'acp-skill', description: 'ACP skill', source: 'runtime', content: 'body' }) - expect(JSON.stringify(await composePrefix(ctx))).toContain('- `acp-skill`: ACP...') - await ctx.fiber.dispose() - }) - - it('forwards maxParallelToolCalls to the bundled agent loop', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - maxParallelToolCalls: 3, - persistenceRoot: await mkdtemp(join(tmpdir(), 'dsh-acp-demo-test-parallel-')), - skills: await isolatedSkillsConfig(), - workspaceContext: false, - }) - expect(ctx.get('agentLoop')?.config.maxParallelToolCalls).toBe(3) - await ctx.fiber.dispose() - }) - - it('forwards task admission config to the bundled task provider', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - jobs: { maxConcurrentJobsPerOwner: 1 }, - skills: await isolatedSkillsConfig(), - workspaceContext: false, - }) - let settle!: (outcome: { status: 'killed' }) => void - ctx.jobs.start({ - kind: 'bash', - label: 'hold configured slot', - run: () => ({ - cancel: () => { settle({ status: 'killed' }) }, - done: new Promise((resolve) => { settle = resolve }), - }), - }) - expect(() => ctx.jobs.start({ - kind: 'bash', - label: 'blocked configured task', - run: () => ({ cancel: () => {}, done: Promise.resolve({ status: 'completed' }) }), - })).toThrow('(limit: 1)') - await ctx.fiber.dispose() - }) - - it('forwards bundled tool config into agent-core', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - workspaceContext: false, - toolBash: { enableRunInBackground: false }, - toolJobs: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 }, - skills: await isolatedSkillsConfig(), - }, true) - const bash = ctx.tools.schemas().find(tool => tool.name === 'bash') - expect(Object.keys((bash!.parameters as { properties: Record }).properties)) - .not.toContain('run_in_background') - await ctx.fiber.dispose() - }) - - it('exposes its plugin shape', () => { - expect(acpAgent.name).toBe('acp-demo') - expect(acpAgent.Config).toBeDefined() - }) - - it('forwards toolOrder through agent-spine-demo to the system-prompt assembly', async () => { - const ctx = await mount({ - provider: 'mock', - model: 'mock', - toolOrder: ['zulu', TOOL_ORDER_REST], - persistenceRoot: await mkdtemp(join(tmpdir(), 'dsh-acp-demo-test-tool-order-')), - workspaceContext: false, - }) - // The bundle's own bash tools pend on the absent `ctx.shell` executor in - // this providerless mount, so register two plain tools to order. - for (const name of ['alpha', 'zulu']) { - ctx.get('tools')!.register({ - name, - description: name, - parameters: {}, - output: { schema: { type: 'null' }, render: () => [] }, - execute: async () => null, - }) - } - const assembly = await ctx.get('systemPrompt')!.assemble() - expect(assembly.tools.map(tool => tool.name)).toEqual([ - 'zulu', - 'alpha', - 'create_goal', - 'get_goal', - 'job_kill', - 'job_list', - 'job_output', - 'skill', - 'update_goal', - ]) - await ctx.fiber.dispose() - }) - - it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/Config/apply', () => { - // A default export would make `unwrapExports` collapse this inject-less namespace and silently - // drop `name`/`Config` while the app still boots. Guard the postmortem-0001 shape directly. - expect('default' in acpAgent).toBe(false) - expect(typeof acpAgent.apply).toBe('function') - - const loader = Object.create(Loader.prototype) as Loader - const unwrapped = loader.unwrapExports(acpAgent) as Record - expect(unwrapped).toBe(acpAgent) - expect(unwrapped.name).toBe('acp-demo') - expect(unwrapped.Config).toBeDefined() - expect(typeof unwrapped.apply).toBe('function') - }) -}) diff --git a/packages/examples/acp-demo/tests/built-bin.e2e.ts b/packages/examples/acp-demo/tests/built-bin.e2e.ts deleted file mode 100644 index 215cdb7caa..0000000000 --- a/packages/examples/acp-demo/tests/built-bin.e2e.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { spawn } from 'node:child_process' -import { mkdtemp, mkdir, readdir, rm, symlink, writeFile, readFile } from 'node:fs/promises' -import { existsSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { dirname, join } from 'node:path' -import { fileURLToPath, pathToFileURL } from 'node:url' -import { - client as createAcpClientApp, - methods, - ndJsonStream, - PROTOCOL_VERSION, - type SessionNotification, -} from '@agentclientprotocol/sdk' -import { Readable, Writable } from 'node:stream' -import { promisify } from 'node:util' -import { zstdDecompress } from 'node:zlib' -import { execa } from 'execa' -import { afterEach, describe, expect, it } from 'vitest' - -/** - * Published-entry smoke: run `lib/bin.js` under plain Node in a symlinked external consumer and - * complete a mock-backed turn. This catches built-only settle races, stdout protocol leaks, and - * published persistence behavior that the tsx source-path smoke cannot. It skips before build. - */ - -const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url)) -const acpBin = join(repoRoot, 'packages/examples/acp-demo/lib/bin.js') -const decompress = promisify(zstdDecompress) - -const dshPackages = [ - 'examples/agent-spine-demo', 'core/agent', 'core/scope', 'core/session', 'core/system-prompt', - 'core/tools', 'core/agent-loop', 'llm/llm', 'shell/shell', - 'shell/bash-local', 'shell/tool-bash', 'subprocess/subprocess', 'subprocess/subprocess-local', 'context/agent-instructions', 'runtime-diagnostics/invariants', 'boot/app-boot', - 'session/session-persistence', - 'session/session-checkpoint-policy', 'session/session-persistence-jsonl', - 'acp/acp', 'mcp/mcp-client', 'examples/acp-demo', 'util/home-paths', 'util/timeout', -] -const vendorPackages = [ - 'cordis', 'loader', 'include', 'timer', 'hmr', 'logger-console', - 'schemastery', 'cosmokit', -] -// Resolve ACP's declared third-party dependencies from that package, not this test: pnpm's strict -// layout need not hoist them. Symlink those exact paths into the plain-Node consumer. -const npmDeps = ['@agentclientprotocol/sdk'] -const acpPkgDir = join(repoRoot, 'packages/acp/acp') - -async function pkgName(absDir: string): Promise { - const json = JSON.parse(await readFile(join(absDir, 'package.json'), 'utf8')) as { name: string } - return json.name -} - -async function link(target: string, name: string, nm: string): Promise { - const dest = join(nm, name) - await mkdir(dirname(dest), { recursive: true }) - await symlink(target, dest) -} - -/** Build a temp consumer dir + a minimal acp `cordis.yml`. Returns the dir. */ -async function makeConsumer(): Promise { - const dir = await mkdtemp(join(tmpdir(), 'acp-built-bin-')) - const nm = join(dir, 'node_modules') - for (const rel of dshPackages) { - const abs = join(repoRoot, 'packages', rel) - await link(abs, await pkgName(abs), nm) - } - for (const v of vendorPackages) { - const abs = join(repoRoot, 'vendor', v) - await link(abs, await pkgName(abs), nm) - } - for (const dep of npmDeps) { - // Resolve from ACP's package.json URL (the package that declares the - // dep), not this test file's location — `acp-agent` does not depend on these. - // ACP SDK 1.4 intentionally does not export package.json; its stable entry - // is `/dist/acp.js`, so the package root is two directories up. - const fromAcp = pathToFileURL(join(acpPkgDir, 'package.json')).href - const resolved = fileURLToPath(import.meta.resolve(dep, fromAcp)) - await link(dirname(dirname(resolved)), dep, nm) - } - await writeFile(join(dir, 'mock-llm.mjs'), [ - "import { LlmAdapter } from '@deepseek-ai/dsh-llm'", - 'class Mock extends LlmAdapter {', - ' async * stream() {', - " yield { type: 'block-start', index: 0, blockType: 'text' }", - " yield { type: 'text-delta', index: 0, text: 'ACP BUILT OK' }", - " yield { type: 'block-end', index: 0, block: { type: 'text', text: 'ACP BUILT OK' } }", - " yield { type: 'finish', reason: { kind: 'stop' } }", - ' }', - '}', - "export const name = 'built-acp-mock'", - "export const inject = ['llm']", - "export function apply(ctx) { ctx.llm.registerAdapter(['built-acp-mock'], new Mock()) }", - '', - ].join('\n')) - await writeFile(join(dir, 'cordis.yml'), [ - '- id: mock-llm', - ' name: \'./mock-llm.mjs\'', - '- id: subprocess', - ' name: \'@deepseek-ai/dsh-subprocess-local\'', - '- id: bash', - ' name: \'@deepseek-ai/dsh-bash-local\'', - '- id: acp-agent', - ' name: \'@deepseek-ai/dsh-acp-demo\'', - ' config:', - ' provider: built-acp-mock', - ' model: built-acp-mock', - ' persona: \'test agent\'', - ' workspaceContext: false', - '', - ].join('\n')) - return dir -} - -let consumer: string | undefined -let child: ReturnType | undefined - -afterEach(async () => { - if (child !== undefined) { - const proc = child - child = undefined - // Windows retains the child's cwd and session-log handles until process - // teardown completes, so await exit before removing the temp directory. - if (proc.exitCode === null && proc.signalCode === null) { - const exited = new Promise((resolve) => { proc.once('exit', () => { resolve() }) }) - proc.kill('SIGKILL') - await exited - } - } - // Windows can briefly retain released handles after exit; retry removal. - if (consumer !== undefined) await rm(consumer, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }) - consumer = undefined -}) - -describe.skipIf(!existsSync(acpBin))('dsh-acp-demo BUILT bin (node lib/bin.js, no tsx)', () => { - it('boots the published bin, completes a turn, and writes default Zstandard persistence', async () => { - consumer = await makeConsumer() - child = spawn(process.execPath, [acpBin, '--config', './cordis.yml'], { - cwd: consumer, - env: { - ...process.env, - DSH_HOME: join(consumer, '.dsh'), - DSH_AGENTS_HOME: join(consumer, '.agents'), - }, - stdio: ['pipe', 'pipe', 'pipe'], - }) - const stderr: string[] = [] - child.stderr!.setEncoding('utf8') - child.stderr!.on('data', (c: string) => stderr.push(c)) - // Tee raw stdout for a protocol-purity check, and feed it to the SDK client. - const rawOut: string[] = [] - const passthrough = new Readable({ read() {} }) - child.stdout!.on('data', (buf: Buffer) => { rawOut.push(buf.toString('utf8')); passthrough.push(buf) }) - child.stdout!.on('end', () => passthrough.push(null)) - const stream = ndJsonStream( - Writable.toWeb(child.stdin!) as WritableStream, - Readable.toWeb(passthrough) as ReadableStream, - ) - const updates: SessionNotification['update'][] = [] - const clientApp = createAcpClientApp({ name: 'dsh-acp-built-smoke' }) - .onNotification(methods.client.session.update, ({ params }) => { - updates.push(params.update) - return Promise.resolve() - }) - .onRequest(methods.client.session.requestPermission, () => { - return Promise.resolve({ outcome: { outcome: 'cancelled' } }) - }) - const client = clientApp.connect(stream).agent - - const init = await client.request(methods.agent.initialize, { - protocolVersion: PROTOCOL_VERSION, - clientCapabilities: {}, - }) - .catch((error: unknown): never => { - throw new Error(`built ACP initialize failed\n${stderr.join('')}`, { cause: error }) - }) - expect(init.agentCapabilities).toEqual({ - mcpCapabilities: { http: true }, - promptCapabilities: { image: false, audio: false, embeddedContext: false }, - sessionCapabilities: { close: {}, list: {}, resume: {} }, - }) - const sessionCwd = consumer - const { sessionId } = await client.request(methods.agent.session.new, { cwd: sessionCwd, mcpServers: [] }) - const result = await client.request(methods.agent.session.prompt, { - sessionId, - prompt: [{ type: 'text', text: 'reply' }], - }) - expect(result.stopReason).toBe('end_turn') - await expect.poll(() => updates).toHaveLength(1) - expect(updates[0]).toMatchObject({ - sessionUpdate: 'agent_message_chunk', - content: { type: 'text', text: 'ACP BUILT OK' }, - }) - expect(updates[0] !== undefined && 'messageId' in updates[0] && typeof updates[0].messageId === 'string').toBe(true) - const sessionsRoot = join(sessionCwd, '.sessions') - let log: string | undefined - await expect.poll(async () => { - log = (await readdir(sessionsRoot, { recursive: true })).find(file => file.endsWith('.jsonl.zstd')) - return log - }).toBeTypeOf('string') - const compressed = await readFile(join(sessionsRoot, log!)) - expect(compressed.subarray(0, 4).toString('hex')).toBe('28b52ffd') - expect(JSON.parse((await decompress(compressed)).toString())).toMatchObject({ type: 'session', id: sessionId }) - expect(stderr.join('')).not.toContain('without inject') - // stdout purity: every emitted line is a JSON-RPC frame, no logger leak. - for (const line of rawOut.join('').split('\n').filter(l => l.trim().length > 0)) { - expect(() => JSON.parse(line) as unknown).not.toThrow() - } - }, 30_000) - - it('fails LOUD (non-zero exit + stderr) on a config whose directory does not exist', async () => { - // boot() pre-resolves the bootstrap include to an absolute URL, so a nonexistent config - // directory cannot break its import; the include plugin's own read must fail loud instead. - const { code, stderr } = await runBinExpectingExit('/nonexistent/dir/cordis.yml') - expect(code).not.toBe(0) - expect(stderr).toContain('config file not found') - }, 30_000) - - it('fails LOUD (non-zero exit + stderr) on a missing config file in a real directory', async () => { - // Existing directory plus missing config exercises the include plugin's fail-loud path. - consumer = await makeConsumer() - const { code, stderr } = await runBinExpectingExit('./does-not-exist.yml', consumer) - expect(code).not.toBe(0) - expect(stderr).toContain('config file not found') - }, 30_000) - -}) - -/** Spawn the built acp bin against `configArg` (stdin closed at EOF) and resolve with its exit code + stderr. */ -async function runBinExpectingExit(configArg: string, cwd: string = tmpdir()): Promise<{ code: number; stderr: string }> { - const result = await execa(process.execPath, [acpBin, '--config', configArg], { - cwd, - env: { - DSH_HOME: join(cwd, '.dsh'), - DSH_AGENTS_HOME: join(cwd, '.agents'), - }, - input: '', - timeout: 25_000, - killSignal: 'SIGKILL', - reject: false, - }) - if (result.timedOut) throw new Error(`bin did not exit within 25s. stderr:\n${result.stderr}`) - return { code: result.exitCode ?? -1, stderr: result.stderr } -} diff --git a/packages/examples/acp-demo/tests/load-path.e2e.ts b/packages/examples/acp-demo/tests/load-path.e2e.ts deleted file mode 100644 index 865ba24c28..0000000000 --- a/packages/examples/acp-demo/tests/load-path.e2e.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process' -import { Readable, Writable } from 'node:stream' -import { mkdtemp, rm, writeFile } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { fileURLToPath } from 'node:url' -import { afterEach, describe, expect, it } from 'vitest' -import { - client as createAcpClientApp, - methods, - ndJsonStream, - PROTOCOL_VERSION, - type ClientContext, -} from '@agentclientprotocol/sdk' - -/** - * Source-path Loader smoke through the package's own bin, covering the - * automation server's initialize and fresh-session path across the - * `unwrapExports` shape implicated by postmortem 0001. Session creation reaches - * the factory but not the model, so a dummy key is sufficient. - */ - -const binScript = fileURLToPath(new URL('../src/bin.ts', import.meta.url)) -const tsxLoader = fileURLToPath(import.meta.resolve('tsx')) -// Repo root is four levels up from packages/examples/acp-demo/tests. -const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) - -// A minimal opt-in leaf that loads this app + the two backends and the optional -// session-query consumer/policies, inlined so the package test owns its fixture. -const CORDIS_YML = ` -- id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' -- id: bash - name: '@deepseek-ai/dsh-bash-local' -- id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek-official - model: deepseek-v4-flash - persona: 'You are a test agent.' - workspaceContext: false -- id: tool-session-query - name: '@deepseek-ai/dsh-tool-session-query' -- id: timeout-policy - name: '@deepseek-ai/dsh-tool-call-timeout-policy' -- id: spill-local - name: '@deepseek-ai/dsh-spill-local' -- id: spill-policy - name: '@deepseek-ai/dsh-spill-policy' - config: - maxInlineBytes: 50000 -` - -interface Spawned { - child: ChildProcessWithoutNullStreams - client: ClientContext - stderr: string[] -} - -let spawned: Spawned | undefined -let workdir: string | undefined - -afterEach(async () => { - if (spawned !== undefined) { - spawned.child.kill('SIGKILL') - spawned = undefined - } - if (workdir !== undefined) await rm(workdir, { recursive: true, force: true }) - workdir = undefined -}) - -async function boot(): Promise { - workdir = await mkdtemp(join(tmpdir(), 'acp-agent-pkg-')) - const cwd = workdir - const configPath = join(cwd, 'cordis.yml') - await writeFile(configPath, CORDIS_YML) - const child = spawn( - process.execPath, - ['--import', tsxLoader, binScript, '--config', configPath], - { - cwd, - env: { - ...process.env, - TSX_TSCONFIG_PATH: repoTsconfig, - // Key-present check only; no prompt is sent, so the model is never called. - DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ?? 'keyless-acp-agent-smoke', - DSH_HOME: join(cwd, '.dsh'), - DSH_AGENTS_HOME: join(cwd, '.agents'), - }, - stdio: ['pipe', 'pipe', 'pipe'], - }, - ) - const stderr: string[] = [] - child.stderr.setEncoding('utf8') - child.stderr.on('data', (chunk: string) => stderr.push(chunk)) - const stream = ndJsonStream( - Writable.toWeb(child.stdin) as WritableStream, - Readable.toWeb(child.stdout) as ReadableStream, - ) - const client = createAcpClientApp({ name: 'dsh-acp-load-smoke' }) - .onNotification(methods.client.session.update, () => Promise.resolve()) - .onRequest(methods.client.session.requestPermission, () => ( - Promise.resolve({ outcome: { outcome: 'cancelled' } }) - )) - .connect(stream).agent - spawned = { child, client, stderr } - return { ...spawned, cwd } -} - -describe('dsh-acp-demo real-load-path smoke (bin + Loader, keyless)', () => { - it('boots via its bin and exposes the standard automation controls', async () => { - const { client, cwd, stderr } = await boot() - // initialize: a broken export shape (collapsed bridge plugin, dropped inject) - // crashes the tree on the first service read here — see postmortem 0001. - const init = await client.request(methods.agent.initialize, { - protocolVersion: PROTOCOL_VERSION, - clientCapabilities: {}, - }) - expect(init.agentCapabilities).toEqual({ - mcpCapabilities: { http: true }, - promptCapabilities: { image: false, audio: false, embeddedContext: false }, - sessionCapabilities: { close: {}, list: {}, resume: {} }, - }) - - // session/new reaches the agent FACTORY (create) without the model. - const { sessionId } = await client.request(methods.agent.session.new, { cwd, mcpServers: [] }) - expect(sessionId).toBeTruthy() - - expect(stderr.join('')).not.toContain('without inject') - }, 30_000) -}) diff --git a/packages/examples/acp-demo/tsconfig.json b/packages/examples/acp-demo/tsconfig.json deleted file mode 100644 index 8511f21347..0000000000 --- a/packages/examples/acp-demo/tsconfig.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "../../../vendor/cordis" - }, - { - "path": "../../../vendor/schemastery" - }, - { - "path": "../../../vendor/loader" - }, - { - "path": "../../boot/app-boot" - }, - { - "path": "../../acp/acp" - }, - { - "path": "../../core/agent" - }, - { - "path": "../../session-query/session-query" - }, - { - "path": "../../session-query/session-query-sqlite" - }, - { - "path": "../agent-spine-demo" - }, - { - "path": "../../context/agent-instructions" - }, - { - "path": "../../session/session-checkpoint-policy" - }, - { - "path": "../../session/session-persistence-jsonl" - }, - { - "path": "../../runtime-diagnostics/invariants" - } - ] -} diff --git a/packages/examples/acp-demo/tsdown.config.ts b/packages/examples/acp-demo/tsdown.config.ts deleted file mode 100644 index 2fa93780be..0000000000 --- a/packages/examples/acp-demo/tsdown.config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineConfig } from 'tsdown' - -/** - * acp-agent ships TWO entries: the plugin (`index`) and the CLI `bin` (`bin`), - * the latter referenced by package.json `bin`/`exports["./bin"]`. The root - * tsdown builds only `lib/types/index.js`, so this override adds - * `lib/types/bin.js`. Declarations come from `tsc -b` (dts: false), - * matching every package. - */ -export default defineConfig({ - entry: ['lib/types/index.js', 'lib/types/invariant.js', 'lib/types/bin.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, -}) diff --git a/packages/subagent/subagent-acp/README.i18n.yaml b/packages/subagent/subagent-acp/README.i18n.yaml index 4aaf1b7552..fa56c715a5 100644 --- a/packages/subagent/subagent-acp/README.i18n.yaml +++ b/packages/subagent/subagent-acp/README.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 packages/subagent/subagent-acp/README.md -README.md: 3bccddbca021bed1f8bf5766b9575f3bd7441669 -README.zh.md: 7ae89ece0ce4282ad5b9a20142a2ba9b111f6d88 +README.md: 0201f9bacca5c59031d2fc2eec7fe7210489dd09 +README.zh.md: c987e6251e035f0cf94ac41b570f3be10f0118f5 diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 3bccddbca0..0201f9bacc 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -33,15 +33,18 @@ ACP advertises no start-time capabilities because this process cannot enforce th | `disposeEofGraceMs` | `6000` | Positive grace after stdin EOF before platform termination; it cannot exceed [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md). | | `disposeGraceMs` | `3000` | Positive POSIX grace after SIGTERM before SIGKILL (Windows force-terminates directly); it cannot exceed [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md). | +A DeepSeek Harness child uses the product launcher and an explicit absolute `DSH_HOME`. The isolated home prevents a nested runtime from discovering the launching person's profiles or credentials; the generic ACP provider does not impose this requirement on non-DSH agents. + ```yaml - id: subagent-acp name: '@deepseek-ai/dsh-subagent-acp' config: providerName: acp - command: node - args: ['--import', 'tsx', './packages/examples/acp-demo/src/bin.ts', '--config', './examples/acp-agent/cordis.yml'] + command: dsh + args: ['--profile', 'acp', '--patch', '/absolute/path/to/acp.patch.yml'] permission: reject env: + DSH_HOME: /absolute/path/to/isolated-child-home DEEPSEEK_API_KEY: !!js process.env.DEEPSEEK_API_KEY ``` diff --git a/packages/subagent/subagent-acp/README.zh.md b/packages/subagent/subagent-acp/README.zh.md index 7ae89ece0c..c987e6251e 100644 --- a/packages/subagent/subagent-acp/README.zh.md +++ b/packages/subagent/subagent-acp/README.zh.md @@ -33,15 +33,18 @@ ACP 不声明任何启动时能力,因为当前进程无法强制执行远程 | `disposeEofGraceMs` | `6000` | stdin EOF 之后、平台终止之前的宽限时间须为正值,且不得大于 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.zh.md)。 | | `disposeGraceMs` | `3000` | POSIX 在 SIGTERM 后、SIGKILL 前的宽限时间(Windows 直接强制终止),须为正值且不得大于 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.zh.md)。 | +DeepSeek Harness 子进程使用产品启动器和一个显式的绝对路径 `DSH_HOME`。隔离 home 可防止嵌套 runtime 发现启动者个人的 profile 或凭据;通用 ACP provider 不会把这一要求强加给非 DSH agent。 + ```yaml - id: subagent-acp name: '@deepseek-ai/dsh-subagent-acp' config: providerName: acp - command: node - args: ['--import', 'tsx', './packages/examples/acp-demo/src/bin.ts', '--config', './examples/acp-agent/cordis.yml'] + command: dsh + args: ['--profile', 'acp', '--patch', '/absolute/path/to/acp.patch.yml'] permission: reject env: + DSH_HOME: /absolute/path/to/isolated-child-home DEEPSEEK_API_KEY: !!js process.env.DEEPSEEK_API_KEY ``` diff --git a/packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts b/packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts index 9654df5feb..e54b50d647 100644 --- a/packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts +++ b/packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts @@ -11,29 +11,33 @@ import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke' import * as acp from '../src/index.ts' /** - * With-key cross-process boundary proof: the backend spawns the real acp-agent example, speaks ACP over + * With-key cross-process boundary proof: the backend spawns the real dsh ACP profile, speaks ACP over * stdio, and returns its real model answer. This is the out-of-process counterpart to in-process * spawn coverage and self-skips without `DEEPSEEK_API_KEY`. */ -// The real acp-agent example: its bin + cordis.yml (the live DeepSeek config). -const binScript = fileURLToPath(new URL('../../../examples/acp-demo/src/bin.ts', import.meta.url)) +// The real ACP profile: dsh plus the example's live DeepSeek patch. +const binScript = fileURLToPath(new URL('../../../../apps/cli/src/bin.ts', import.meta.url)) const exampleConfig = fileURLToPath(new URL('../../../../examples/acp-agent/cordis.yml', import.meta.url)) const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) -// How to launch the child acp-agent (src via tsx / lib via plain node, per DSH_EXAMPLE_MODE). +// How to launch the child ACP profile (src via tsx / lib via plain node, per DSH_EXAMPLE_MODE). // The subprocess seam scrubs ambient creds while spec.env merges after it, so the model key is // forwarded explicitly; TSX_TSCONFIG_PATH is added by the resolver in src mode only. -const childLaunch = resolveExampleLaunch({ - srcBin: binScript, - configArgs: ['--config', exampleConfig], - tsconfigPath: repoTsconfig, - env: { - ...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {}, - ...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {}, - DSH_PERMISSION_MODE: 'danger-full-access', - }, -}) +function resolveChildLaunch(dshHome: string) { + return resolveExampleLaunch({ + srcBin: binScript, + sourceImport: 'tsx/esm', + configArgs: ['--profile', 'acp', '--patch', exampleConfig], + tsconfigPath: repoTsconfig, + env: { + ...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {}, + ...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {}, + DSH_HOME: dshHome, + DSH_PERMISSION_MODE: 'danger-full-access', + }, + }) +} /** The ACP backend ignores the parent, but the seam requires one. */ const fakeParent = { id: 'parent', session: { header: {} } } as unknown as Agent @@ -51,6 +55,7 @@ afterEach(async () => { describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive our own acp-agent)', () => { it('drives the real acp-agent example process to answer a prompt', async () => { workdir = await mkdtemp(join(tmpdir(), 'dsh-subagent-acp-e2e-')) + const childLaunch = resolveChildLaunch(join(workdir, '.dsh-child')) ctx = new Context() await ctx.plugin(SubagentRuntime) await ctx.plugin(LocalSubprocessRuntime) @@ -81,6 +86,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive it('drives the child to do real file work via its own bash tool', async () => { workdir = await mkdtemp(join(tmpdir(), 'dsh-subagent-acp-e2e-')) + const childLaunch = resolveChildLaunch(join(workdir, '.dsh-child')) ctx = new Context() await ctx.plugin(SubagentRuntime) await ctx.plugin(LocalSubprocessRuntime) diff --git a/packages/test-support/acp-snapshot/README.i18n.yaml b/packages/test-support/acp-snapshot/README.i18n.yaml index d4ac4a54b4..921dfcc00c 100644 --- a/packages/test-support/acp-snapshot/README.i18n.yaml +++ b/packages/test-support/acp-snapshot/README.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 packages/test-support/acp-snapshot/README.md -README.md: 3e7f907aed899ddf4c3fd7979ea08d00b985e9e3 -README.zh.md: 749da06af7233ddacb87ee11bf88ce80e296ea9b +README.md: 2d753cb0d05c35f78cd4effe3f410b021c16462f +README.zh.md: 9a5413297d0a4af1a506d846fa26128e84201e45 diff --git a/packages/test-support/acp-snapshot/README.md b/packages/test-support/acp-snapshot/README.md index 3e7f907aed..2d753cb0d0 100644 --- a/packages/test-support/acp-snapshot/README.md +++ b/packages/test-support/acp-snapshot/README.md @@ -6,7 +6,7 @@ The ACP snapshot suite kit: the shared machinery behind the keyless snapshot tie Four layers, importable separately: -- **`launchAcpTestAgent` (launcher)** — boots a source agent under tsx or a built `lib` agent under plain Node from a supplied cwd, connects the SDK client over a raw-byte stdout tee, collects session updates and stderr, surfaces asynchronous spawn failures through startup, fails closed on unhandled permission requests, and owns graceful or signalled shutdown. Shutdown waits for process exit, inherited stdio closure, and ACP parser exhaustion before resolving or propagating a child error, so captures are complete and callers can remove owned paths after either outcome. When Windows accepts forced termination but publishes its exit marker asynchronously, shutdown gives that marker a bounded grace before treating fallback refusal as a second failure. Snapshot and ordinary e2e suites share this process boundary; a test supplies only agent paths, cwd, environment overrides, and any permission policy. +- **`launchAcpTestAgent` (launcher)** — boots a source entry under tsx or a built `lib` entry under plain Node from a supplied cwd, connects the SDK client over a raw-byte stdout tee, collects session updates and stderr, surfaces asynchronous spawn failures through startup, fails closed on unhandled permission requests, and owns graceful or signalled shutdown. Product suites name a `dsh` profile: the launcher passes the base and selected scenario patches through `--patch`, selects the scenario's sibling `*cordis.snapshot.yml` in replay, and materializes temporary copies whose relative plugin modules become absolute file URLs. Test-only fake bins may omit the profile and retain their own config grammar. Shutdown waits for process exit, inherited stdio closure, and ACP parser exhaustion before resolving or propagating a child error, so captures are complete and callers can remove owned paths after either outcome. - **`runScenario` (harness)** — drives ACP JSON-RPC stdio from a deterministic `input.json` script through the launcher, tees raw stdout for the expected-output and purity checks, and harvests every persisted raw JSONL session log (parent and subagent children, primary-first) after graceful stdin EOF. `AgentUnderTest` supplies absolute `binScript`, optional `libBinScript`, `configPath`, and `tsconfigPath` paths because the subprocess cwd is outside the repo; `workspaceParent` may move the generated child cwd from the platform temp directory when that grant is itself under test. Startup failures preserve captured agent stderr in the rejected diagnostic. - **Normalizers** — pure functions turning captured surfaces into stable text or portable fixtures: `normalizeStdout` (JSON-RPC ids → first-seen sequence; UUIDs and every native/JavaScript filesystem spelling of the generated cwd → tokens, longest-first; cwd-rooted separators selected as canonical `/` or host-native; doubles as the stdout-purity check), `normalizeSessionLog` (sequence envelopes retained, times zeroed, the same cwd-path policy), `normalizeSessionSnapshot` (session-log normalization followed by request-header scrubbing and body-envelope projection), `tokenizeSessionFixtureCwd` (the generated workspace and its filesystem aliases → one canonical `{{cwd}}`, including an already-tokenized macOS `/private` alias; authored temp paths unchanged), `scrubSystemPrompts` (prompt text → `{{system}}`), `scrubToolSchemas` (schema bulk → `{{tools}}`), `scrubRequestHeaders` (all header bulk → `{{system}}`/`{{tools}}`/`{{messagePrefix}}` outside each pin, structure kept — [pinned-header Agent Note](../../../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)), and `stabilizeFixtureMessageIds` (committed UUIDs carried into unchanged, mutually unique messages by structurally rewriting only complete surface and durable-inbox message ID fields across any recorder's fixture-ready parent/child logs). - **`defineAcpSnapshotSuite` (factory)** — registers the whole describe/it tree for a scenario table: per-scenario expected-output and re-persisted-log comparisons, record/refresh fixture write-back, rejection of structured `UNKNOWN_TOOL` results, a tokenized pin per header class composed with independently shared `system-prompt.expected.md` and `tool-schemas.expected.json` sidecars, and a live uniformity guard. Its fixture guards reject orphan scenario dirs, missing files, multiple pins for one class, duplicate sidecar content, noncanonical macOS-prefixed cwd tokens, unscrubbed JSONL headers, and malformed pinning headers. Before record or refresh writes fixtures, an unchanged complete message retains its committed UUID only when both its ID and identity-free fingerprint are unique across the scenario's fixture-ready parent/child logs; the session package's authoritative surface-type predicate selects surface carriers, correlated `agent/inbox/spliced` copies join the same mapping, and only validated `id` fields in those carriers are rewritten. New, changed, malformed, and graph-ambiguous messages keep fresh UUIDs. Refresh evaluates fresh leaves with the harvested run's ids, cwd, and every cwd alias, then reuses normalized-equivalent leaves only when the complete logical-record layout aligns and volatile string replacements form a bijection; complete message IDs in surface or inbox carriers are excluded because the later structural pass owns them, ambiguous logs keep fresh strings, and fresh semantic values remain authoritative. It also expands packed timing envelopes before aligning event times, so switching between packed and unpacked layouts cannot shift later records. A newly inserted `session/title` receives its preceding event's time so feature-driven insertions do not churn the remainder of a fixture. Each scenario directory's `session.jsonl` plus contiguous `session..jsonl` siblings are the ordered primary/child inventory; the scenario table does not duplicate their count. Must be called at vitest collection time. @@ -41,8 +41,9 @@ const SCENARIOS: Scenario[] = [ defineAcpSnapshotSuite({ agent: { // absolute paths, resolved from the suite's own location - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), }, snapshotsDir: join(dirname(fileURLToPath(import.meta.url)), 'snapshots'), @@ -51,7 +52,7 @@ defineAcpSnapshotSuite({ }) ``` -A scenario booting a differently-composed tree sets its own `configPath` (an overlay whose basename still ends in `cordis.yml`, so the bin's replay swap finds the sibling `*cordis.snapshot.yml`) and, when that composition changes the request header, its own `headerClass` with its own pinning scenario — the acp-agent example's Code Mode and filesystem scenarios are templates. Default generated workspaces are stored in session fixtures as `{{cwd}}` so platform temp roots and random basenames do not affect recordings; `workspaceParent` moves the generated cwd outside the platform temp area when temporary-directory grants are themselves under test, keeps that explicit path in the fixture, and remains parent-owned while the harness removes only the generated child. A scenario's committed `workspace/` is copied into that child first, then `prepareWorkspace` runs against the generated cwd before the agent starts. Reserve this hook for fixtures Git cannot represent portably, keep ordinary seeds in `workspace/`, and pair it with `posixOnly` when the generated paths are invalid on Windows. +A scenario booting a differently composed profile sets its own `configPath` patch (its basename still ends in `cordis.yml`, so the launcher finds the sibling `*cordis.snapshot.yml`) and, when that composition changes the request header, its own `headerClass` with its own pinning scenario — the acp-agent example's Code Mode and filesystem scenarios are templates. Default generated workspaces are stored in session fixtures as `{{cwd}}` so platform temp roots and random basenames do not affect recordings; `workspaceParent` moves the generated cwd outside the platform temp area when temporary-directory grants are themselves under test, keeps that explicit path in the fixture, and remains parent-owned while the harness removes only the generated child. A scenario's committed `workspace/` is copied into that child first, then `prepareWorkspace` runs against the generated cwd before the agent starts. Reserve this hook for fixtures Git cannot represent portably, keep ordinary seeds in `workspace/`, and pair it with `posixOnly` when the generated paths are invalid on Windows. A pin owns its generated `system-prompt.expected.md` or `tool-schemas.expected.json` by default; `systemPromptSource` and `toolSchemasSource` name another pin when the complete corresponding sequence is identical, so each distinct version is committed once. The pin's `session.jsonl` stores `"system":"{{system}}","tools":"{{tools}}"` while retaining config, reason, and any model-visible prefix. A pin with legitimate mid-run header changes declares `expectedHeaderChanges`; a shared source must declare the same count, and record/refresh rejects claimants that generate different bytes. @@ -59,7 +60,7 @@ A child session whose own scope composes a different request declares it per fix Every scenario compares `stdout.expected.jsonl` with cwd-rooted separators canonicalized to `/`. On Windows, `pinsNativeWindowsStdout` additionally compares the complete `stdout.expected.windows.jsonl` after the shared expected output and requires that sidecar exactly when enabled. A scenario requiring a non-Windows host declares `posixOnly`, which skips its run test on Windows while the fixture guards keep covering its committed files everywhere; examples include POSIX process semantics (e.g. cancelling a live bash call kills a detached process group) and generated paths Windows cannot represent. A scenario whose composition needs a usable `pwsh` declares `pwshOnly`; the caller-supplied `hasPwsh` probe (the shipped acp-agent suite follows the executor's own resolution, so Program Files installs count) skips the run test when no usable `pwsh` resolves while the fixture guards keep covering its committed files everywhere. -The example also ships a `cordis.snapshot.yml` replay overlay next to its `cordis.yml` (the bin swaps them under `DSH_SNAPSHOT=replay` — [single-source replay config Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md)); replay fixtures are served by [`dsh-llm-replay`](../llm-replay/README.md), which this package points at via the `DSH_SNAPSHOT_*` env vars it sets on the child. `pnpm run test:snapshot:record` calls the live LLM and rewrites the recorded scenarios' model fixtures; `pnpm run test:snapshot:refresh` stays keyless, runs the replay overlay, and rewrites stdout, comparable session-log expected outputs, and owned prompt and tool-schema sidecars from the committed model scripts. Fixture roles, record/replay/refresh semantics, and scenario-table fields are documented on `Scenario` and in the [snapshot Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md). +The example also ships a `cordis.snapshot.yml` replay patch next to its live patch. The launcher applies the live base patch and the selected replay sibling under `DSH_SNAPSHOT=replay` ([single-source replay config Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md)); [`dsh-llm-replay`](../llm-replay/README.md) serves fixtures named by the `DSH_SNAPSHOT_*` environment values. `pnpm run test:snapshot:record` calls the live LLM and rewrites the recorded scenarios' model fixtures; `pnpm run test:snapshot:refresh` stays keyless, runs the replay patch, and rewrites stdout, comparable session-log expected outputs, and owned prompt and tool-schema sidecars from the committed model scripts. Fixture roles, record/replay/refresh semantics, and scenario-table fields are documented on `Scenario` and in the [snapshot Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md). Constraints: `suite.ts` and `harness.ts` import vitest (the harness polls its durable-boundary waits through `vi.waitFor`), so the package entry is importable only inside a vitest run (the launcher and normalizers have no such dependency but ship from the same entry). The launcher and suite factory are ACP-specific by design — the launcher speaks the SDK's `ClientSideConnection` — while the normalizers are transport-neutral session-log/text helpers also consumed by the JSON-RPC and Web snapshot recorders. Input scripts cover initialization, fresh-session creation, shorthand text prompts, exact structured ACP prompt blocks, cancellation, expected RPC failures, and durable turn-boundary waits. Permission round-trips are a FIFO queue of option-kind selections (`allow_once`, `reject_once`, …) mapped to the agent-issued `optionId`; an absent or exhausted queue answers `cancelled`, and an unoffered kind rejects the run. diff --git a/packages/test-support/acp-snapshot/README.zh.md b/packages/test-support/acp-snapshot/README.zh.md index 749da06af7..9a5413297d 100644 --- a/packages/test-support/acp-snapshot/README.zh.md +++ b/packages/test-support/acp-snapshot/README.zh.md @@ -6,7 +6,7 @@ ACP(Agent Client Protocol)快照套件工具包:无密钥快照层(`pnpm 四层可单独导入: -- **`launchAcpTestAgent`(启动器)**:从指定 cwd 在 tsx 下启动源 agent(智能体),或在普通 Node 下启动已构建 `lib` agent;通过原始字节 stdout tee 连接 SDK 客户端,收集会话更新和 stderr,在启动阶段报告异步 spawn 失败,默认拒绝未处理的权限请求,并负责优雅或带信号关闭。关闭会等待进程退出、继承 stdio 关闭和 ACP parser 耗尽,然后才完成关闭或传播子级错误,使捕获内容完整,且调用方可在任一结果后移除自有路径。当 Windows 接受强制终止但异步发布退出标记时,关闭会给该标记有界宽限,然后才将回退拒绝视为第二次失败。快照和普通 e2e 套件共享该进程边界;测试只需提供 agent 路径、cwd、环境覆盖和任何权限策略。 +- **`launchAcpTestAgent`(启动器)**:从指定 cwd 在 tsx 下启动源码入口,或在普通 Node 下启动已构建 `lib` 入口;通过原始字节 stdout tee 连接 SDK 客户端,收集会话更新和 stderr,在启动阶段报告异步 spawn 失败,默认拒绝未处理的权限请求,并负责优雅或带信号关闭。产品套件指定一个 `dsh` profile:启动器通过 `--patch` 传入基础 patch 与所选场景 patch,在 replay 时选择场景同级的 `*cordis.snapshot.yml`,并把相对插件模块改写成绝对 file URL 后物化为临时副本。测试专用 fake bin 可以省略 profile 并保留自己的配置语法。关闭会等待进程退出、继承 stdio 关闭和 ACP parser 耗尽,然后才完成关闭或传播子级错误,使捕获内容完整,且调用方可在任一结果后移除自有路径。 - **`runScenario`(harness)**:通过启动器从确定性 `input.json` 脚本驱动 ACP JSON-RPC stdio,将原始 stdout tee 给预期输出和纯度检查,并在优雅 stdin EOF 后收集每个持久化原始 JSONL 会话日志(父会话和 subagent 子会话,主会话优先)。`AgentUnderTest` 提供绝对 `binScript`、可选 `libBinScript`、`configPath` 和 `tsconfigPath` 路径,因为子进程 cwd 位于仓库外。当生成子级 cwd 的授权本身是测试对象时,`workspaceParent` 可以将它从平台临时目录移出。启动失败会在拒绝诊断中保留已捕获 agent stderr。 - **规范化器**:将已捕获内容转换为稳定文本或可移植 fixture 的纯函数:`normalizeStdout`(JSON-RPC id → 首次出现序列;UUID 以及生成 cwd 的每种原生/JavaScript 文件系统写法 → token,按最长优先;根据 cwd 的分隔符选择规范 `/` 或宿主原生形式;同时作为 stdout 纯度检查)、`normalizeSessionLog`(保留序号 envelope、将时间归零,并使用同一 cwd 路径策略)、`normalizeSessionSnapshot`(依次执行会话日志规范化、request header 清理和正文 envelope 投影)、`tokenizeSessionFixtureCwd`(生成的 workspace 及其文件系统别名,包括已进行 token 化的 macOS `/private` 别名 → 单一规范 `{{cwd}}`;手工编写的临时路径保持不变)、`scrubSystemPrompts`(提示词文本 → `{{system}}`)、`scrubToolSchemas`(schema bulk → `{{tools}}`)、`scrubRequestHeaders`(每个 pin 之外的所有 header bulk → `{{system}}`/`{{tools}}`/`{{messagePrefix}}`,保留结构;见[header 固定 Agent Note](../../../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))和 `stabilizeFixtureMessageIds`(针对任意录制器已准备写入 fixture 的父级/子级日志,通过结构化方式仅改写 surface 和持久 inbox 中完整消息的 ID 字段,将已提交 UUID 带入未变化且双向唯一匹配的消息)。 - **`defineAcpSnapshotSuite`(工厂)**:为场景表注册完整 describe/it 树:每场景预期输出与重新持久化日志比较、录制/刷新 fixture 回写、拒绝结构化 `UNKNOWN_TOOL` 结果、每个 header 类别一个 token 化 pin(由可独立共享的 `system-prompt.expected.md` 和 `tool-schemas.expected.json` 伴随文件组合而成),以及实时一致性保护。其 fixture 保护会拒绝遗留场景目录、缺失文件、一个类别包含多个 pin、重复的伴随文件内容、带非规范 macOS 前缀的 cwd token、未擦除的 JSONL header,以及格式错误的 pin header。在录制或刷新模式写入 fixture 前,仅当一条未变化完整消息的 ID 及其去除身份后的指纹在场景可写入 fixture 的父级/子级日志中均唯一时,该消息才会保留已提交的 UUID;会话包的权威 surface 类型谓词负责选择 surface 载体,与其关联的 `agent/inbox/spliced` 副本也纳入同一映射,且仅改写这些载体中通过验证的 `id` 字段。新增、发生变化、格式错误以及图关系存在歧义的消息保留本次生成的 UUID。刷新会使用收集所得本次运行的 id、cwd 及全部 cwd 别名评估本次生成的叶值;只有完整逻辑记录布局对齐且易变字符串替换形成双射时,才会复用归一化后等价的叶值;surface 或 inbox 载体中的完整消息 ID 不参与此路径,因为后续结构化处理负责这些 ID;有歧义的日志保留本次生成的字符串,而本次生成的语义值仍为权威数据。它还会在对齐事件时间前展开打包时序 envelope,因此切换打包/非打包布局无法移动后续记录。新插入的 `session/title` 使用前一个事件的时间,因此功能驱动的插入不会扰动 fixture 余下部分。每个场景目录的 `session.jsonl` 和连续 `session..jsonl` 同级文件构成有序的主会话/子会话清单;场景表不重复其数量。必须在 vitest 收集时调用。 @@ -41,8 +41,9 @@ const SCENARIOS: Scenario[] = [ defineAcpSnapshotSuite({ agent: { // absolute paths, resolved from the suite's own location - binScript: fileURLToPath(new URL('../../../packages/examples/acp-demo/src/bin.ts', import.meta.url)), + binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)), configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)), + profile: 'acp', tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)), }, snapshotsDir: join(dirname(fileURLToPath(import.meta.url)), 'snapshots'), @@ -51,7 +52,7 @@ defineAcpSnapshotSuite({ }) ``` -启动不同组合树的场景会设置自己的 `configPath`(一个 basename 仍以 `cordis.yml` 结尾的 overlay,使 bin 的回放交换可找到同级 `*cordis.snapshot.yml`);当该组合改变请求 header 时,还会设置自己的 `headerClass` 和 pin 场景,acp-agent 示例的 Code Mode 与文件系统场景是模板。默认生成的 workspace 在会话 fixture 中存储为 `{{cwd}}`,使平台临时根目录和随机 basename 不影响录制结果;当临时目录授权自身待测时,`workspaceParent` 将生成 cwd 移出平台临时区域,在 fixture 中保留该显式路径,并仍归父级所有,而 harness 只移除生成的子级。场景签入的 `workspace/` 会先复制到该子级,随后 `prepareWorkspace` 在 agent 启动前针对生成 cwd 运行。此 hook 仅用于 Git 无法跨平台表示的 fixture;普通种子应留在 `workspace/` 中,而生成路径在 Windows 上无效时还必须搭配 `posixOnly`。 +启动不同 profile 组合的场景会设置自己的 `configPath` patch(其 basename 仍以 `cordis.yml` 结尾,使启动器可找到同级 `*cordis.snapshot.yml`);当该组合改变请求 header 时,还会设置自己的 `headerClass` 和 pin 场景,acp-agent 示例的 Code Mode 与文件系统场景是模板。默认生成的 workspace 在会话 fixture 中存储为 `{{cwd}}`,使平台临时根目录和随机 basename 不影响录制结果;当临时目录授权自身待测时,`workspaceParent` 将生成 cwd 移出平台临时区域,在 fixture 中保留该显式路径,并仍归父级所有,而 harness 只移除生成的子级。场景签入的 `workspace/` 会先复制到该子级,随后 `prepareWorkspace` 在 agent 启动前针对生成 cwd 运行。此 hook 仅用于 Git 无法跨平台表示的 fixture;普通种子应留在 `workspace/` 中,而生成路径在 Windows 上无效时还必须搭配 `posixOnly`。 每个 pin 默认拥有其生成的 `system-prompt.expected.md` 或 `tool-schemas.expected.json`;当完整的对应序列相同时,`systemPromptSource` 和 `toolSchemasSource` 指定另一个 pin 作为来源,因此每个不同版本只提交一次。该 pin 的 `session.jsonl` 存储 `"system":"{{system}}","tools":"{{tools}}"`,同时保留配置、原因和任何模型可见前缀。具有合法运行中 header 变更的 pin 声明 `expectedHeaderChanges`;共享来源必须声明相同的 header 变更数量,录制/刷新会拒绝生成不同字节的共享引用方。 @@ -59,7 +60,7 @@ defineAcpSnapshotSuite({ 每个场景都比较 `stdout.expected.jsonl`,其中以 cwd 为根的分隔符规范化为 `/`。在 Windows 上,`pinsNativeWindowsStdout` 还会在共享预期输出之后比较完整 `stdout.expected.windows.jsonl`,并且仅在启用时要求存在该伴随文件。需要非 Windows 主机的场景声明 `posixOnly`,在 Windows 上跳过运行测试,但 fixture 保护仍在所有平台覆盖其已提交文件;示例包括 POSIX 进程语义(例如取消正在运行的 bash 调用会终止一个已脱离的进程组)和 Windows 无法表示的生成路径。组合需要可用 `pwsh` 的场景声明 `pwshOnly`;调用方提供的 `hasPwsh` 探测(随附的 acp-agent 套件遵循执行器自身的解析,因此 Program Files 安装也计入)在解析不到可用 `pwsh` 时跳过运行测试,而 fixture 保护仍处处覆盖其已提交文件。 -示例还发布 `cordis.snapshot.yml` 回放 overlay,位于 `cordis.yml` 旁边(bin 在 `DSH_SNAPSHOT=replay` 下交换它们,见[单源回放配置 Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md));回放 fixture 由 [`dsh-llm-replay`](../llm-replay/README.zh.md) 提供,本包通过为子进程设置的 `DSH_SNAPSHOT_*` env var 指向它。`pnpm run test:snapshot:record` 调用在线 LLM(大语言模型),并重写已记录场景的模型 fixture;`pnpm run test:snapshot:refresh` 保持无密钥,运行回放 overlay,并从已提交模型脚本重写 stdout、可比较会话日志预期输出,以及各 pin 自有的提示词与工具 schema 伴随文件。Fixture 角色、录制/回放/刷新语义和场景表字段记录在 `Scenario` 以及[快照 Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md) 中。 +示例还在 live patch 旁提供 `cordis.snapshot.yml` replay patch。`DSH_SNAPSHOT=replay` 下,启动器应用 live 基础 patch 和所选场景的 replay 同级文件(见[单源回放配置 Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md));[`dsh-llm-replay`](../llm-replay/README.zh.md) 提供由 `DSH_SNAPSHOT_*` 环境值指向的 fixture。`pnpm run test:snapshot:record` 调用在线 LLM(大语言模型),并重写已记录场景的模型 fixture;`pnpm run test:snapshot:refresh` 保持无密钥,运行回放 overlay,并从已提交模型脚本重写 stdout、可比较会话日志预期输出,以及各 pin 自有的提示词与工具 schema 伴随文件。Fixture 角色、录制/回放/刷新语义和场景表字段记录在 `Scenario` 以及[快照 Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md) 中。 约束:`suite.ts` 与 `harness.ts` 导入 vitest(harness 通过 `vi.waitFor` 轮询其持久边界等待),因此包入口只能在 vitest 运行中导入(启动器和规范化器没有此依赖,但从同一入口发布)。启动器和套件工厂按设计专用于 ACP,启动器使用 SDK 的 `ClientSideConnection`;规范化器是与传输无关的会话日志/文本辅助工具,还由 JSON-RPC 和 Web 快照录制器消费。输入脚本覆盖初始化、新建会话、文本提示简写、精确结构化 ACP 提示词块、取消、预期 RPC 失败和持久轮次边界等待。权限往返是选项类别选择(`allow_once`、`reject_once` 等)的 FIFO 队列,映射到 agent 发出的 `optionId`;缺少或耗尽的队列回答 `cancelled`,未提供类别会拒绝运行。 diff --git a/packages/test-support/acp-snapshot/package.json b/packages/test-support/acp-snapshot/package.json index cffac8f1d3..3c6b58df14 100644 --- a/packages/test-support/acp-snapshot/package.json +++ b/packages/test-support/acp-snapshot/package.json @@ -33,7 +33,9 @@ "license": "MIT", "dependencies": { "@agentclientprotocol/sdk": "1.4.0", + "@deepseek-ai/cordis-plugin-include": "workspace:*", "@deepseek-ai/dsh-loader-smoke": "workspace:*", + "js-yaml": "^4.2.0", "vitest": "^4.1.8" }, "peerDependencies": { @@ -44,6 +46,7 @@ "devDependencies": { "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" + "@deepseek-ai/cordis": "workspace:^", + "@types/js-yaml": "^4.0.9" } } diff --git a/packages/test-support/acp-snapshot/src/harness.ts b/packages/test-support/acp-snapshot/src/harness.ts index 9541c8db78..f98ff9892d 100644 --- a/packages/test-support/acp-snapshot/src/harness.ts +++ b/packages/test-support/acp-snapshot/src/harness.ts @@ -192,12 +192,9 @@ export interface RunOptions { */ workspaceParent?: string /** - * Alternate LIVE config path for the boot (absolute), overriding - * {@link AgentUnderTest.configPath} for this run. A scenario needing a - * differently-composed tree (the Code Mode scenarios) ships an overlay - * whose basename still ends in `cordis.yml`, so the bin's replay swap - * resolves the sibling `*cordis.snapshot.yml` the same way it does for - * the default. + * Alternate live profile patch (absolute), overriding + * {@link AgentUnderTest.configPath} for this run. Its basename still ends + * in `cordis.yml` so the launcher can select the replay sibling. */ configPath?: string } diff --git a/packages/test-support/acp-snapshot/src/launcher.ts b/packages/test-support/acp-snapshot/src/launcher.ts index 865835cbfd..5b146f8ebe 100644 --- a/packages/test-support/acp-snapshot/src/launcher.ts +++ b/packages/test-support/acp-snapshot/src/launcher.ts @@ -8,8 +8,12 @@ */ import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process' -import { join } from 'node:path' +import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, symlinkSync, writeFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { basename, dirname, join, resolve } from 'node:path' import { Readable, Writable } from 'node:stream' +import { pathToFileURL } from 'node:url' +import * as yaml from 'js-yaml' import { client as createAcpClientApp, methods, @@ -33,18 +37,28 @@ import { type SetSessionConfigOptionResponse, type SessionNotification, } from '@agentclientprotocol/sdk' +import { entryListSchema, type PatchOptions } from '@deepseek-ai/cordis-plugin-include' import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke' const EXIT_MARKER_GRACE_MS = 250 -/** The source/built agent entry, leaf config, and workspace tsconfig an ACP test boots. */ +/** Loader fields needed while rebasing authored relative module names. */ +interface ProfilePatchEntry { + name: string + group?: boolean | null + config?: unknown +} + +/** The source/built entry, profile patch, and workspace tsconfig an ACP test boots. */ export interface AgentUnderTest { - /** The agent source bin entry (for example `packages/examples/acp-demo/src/bin.ts`). */ + /** The source bin entry; product suites use `apps/cli/src/bin.ts`. */ binScript: string /** Explicit built-mode entry for fixtures whose source path is not under `src/`. */ libBinScript?: string | undefined - /** The leaf `cordis.yml` loaded by the bin. */ + /** Base config or profile patch loaded by the bin. */ configPath: string + /** Named dsh profile; omitted only for test-only fake bins with their own config grammar. */ + profile?: string /** The repo tsconfig whose paths resolve unbuilt workspace imports. */ tsconfigPath: string } @@ -104,11 +118,15 @@ export interface LaunchedAcpTestAgent { */ export function launchAcpTestAgent(options: AcpTestLaunchOptions): LaunchedAcpTestAgent { const { agent, cwd } = options + const selectedConfig = options.configPath ?? agent.configPath const launch = resolveExampleLaunch({ srcBin: agent.binScript, libBin: agent.libBinScript, - configArgs: ['--config', options.configPath ?? agent.configPath], + configArgs: agent.profile === undefined + ? ['--config', selectedConfig] + : profileArgs(agent.profile, agent.configPath, selectedConfig, options.env?.DSH_SNAPSHOT, cwd), tsconfigPath: agent.tsconfigPath, + ...agent.profile === undefined ? {} : { sourceImport: 'tsx/esm' }, env: { ...options.env, DSH_HOME: join(cwd, '.dsh'), @@ -322,6 +340,99 @@ export function launchAcpTestAgent(options: AcpTestLaunchOptions): LaunchedAcpTe } } +/** Build one dsh profile invocation from the base and optional scenario patches. */ +function profileArgs( + profile: string, + basePatch: string, + selectedPatch: string, + snapshotMode: string | undefined, + cwd: string, +): string[] { + const base = resolve(cwd, basePatch) + const selected = resolve(cwd, selectedPatch) + // Replay siblings already contain the selected scenario's complete delta + // over the base patch; live scenario patches are not also applied. + const patches = snapshotMode === 'replay' + ? [base, replayPatchPath(selected)] + : [...new Set([base, selected])] + const materializedRoot = join(cwd, '.dsh-profile-patches') + mkdirSync(materializedRoot, { recursive: true }) + const materializedDir = mkdtempSync(join(materializedRoot, 'launch-')) + const materialized = patches.map((file, index) => materializeProfilePatch(file, cwd, materializedDir, index)) + return ['--profile', profile, ...materialized.flatMap(file => ['--patch', file])] +} + +/** Derive the replay-only sibling patch selected by the former app-bin swap. */ +function replayPatchPath(source: string): string { + const replayName = basename(source).replace(/cordis\.ya?ml$/, 'cordis.snapshot.yml') + return resolve(dirname(source), replayName) +} + +/** Parse a bare package or subpath specifier into its package name. */ +function barePackageName(specifier: string): string | undefined { + if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.includes(':')) return undefined + const [first = '', second = ''] = specifier.split('/') + return first.startsWith('@') ? `${first}/${second}` : first +} + +/** Find a bare package's directory from the authored patch's module-resolution anchor. */ +function packageDirFromPatch(source: string, packageName: string): string | undefined { + for (const searchPath of createRequire(pathToFileURL(source)).resolve.paths(packageName) ?? []) { + const candidate = join(searchPath, packageName) + if (existsSync(join(candidate, 'package.json'))) return realpathSync(candidate) + } + return undefined +} + +/** + * Install an authored patch's resolvable bare package into the temporary + * profile fallback. This mirrors `dsh plugin` while retaining the bare entry + * name and package provenance used by request metadata. + */ +function linkProfilePackage(source: string, cwd: string, packageName: string): void { + const packageDir = packageDirFromPatch(source, packageName) + // The package may instead belong to the dsh installation; profile boot heals those links. + if (packageDir === undefined) return + const link = join(cwd, '.dsh', 'profiles', 'node_modules', packageName) + mkdirSync(dirname(link), { recursive: true }) + if (existsSync(link)) { + if (realpathSync(link) !== packageDir) { + throw new Error(`ACP profile package ${packageName} resolves to two directories`) + } + return + } + /* v8 ignore next -- Windows uses directory junctions; the platform lane owns that branch. */ + symlinkSync(packageDir, link, process.platform === 'win32' ? 'junction' : 'dir') +} + +/** Copy one authored patch into the launch cwd with relative plugin names made absolute. */ +function materializeProfilePatch(source: string, cwd: string, targetDir: string, index: number): string { + const parsed = yaml.load(readFileSync(source, 'utf8'), { schema: entryListSchema }) + if (!Array.isArray(parsed)) throw new Error(`ACP profile patch must be a top-level array: ${source}`) + const patches = parsed as PatchOptions[] + const baseDir = dirname(source) + const resolveName = (value: string): string => { + const packageName = barePackageName(value) + if (packageName !== undefined) linkProfilePackage(source, cwd, packageName) + return value.startsWith('./') || value.startsWith('../') + ? pathToFileURL(resolve(baseDir, value)).href + : value + } + const visitEntry = (entry: ProfilePatchEntry): void => { + entry.name = resolveName(entry.name) + if (entry.group === true && Array.isArray(entry.config)) { + for (const child of entry.config as ProfilePatchEntry[]) visitEntry(child) + } + } + for (const patch of patches) { + if (typeof patch.name === 'string') patch.name = resolveName(patch.name) + for (const entry of patch.insert ?? []) visitEntry(entry) + } + const target = join(targetDir, `${String(index)}-${basename(source)}`) + writeFileSync(target, yaml.dump(patches, { schema: entryListSchema, lineWidth: -1, noRefs: true })) + return target +} + /** Resolve once a running child exits. */ function waitForExit(child: ChildProcessWithoutNullStreams): Promise { return new Promise(resolve => child.once('exit', () => { resolve() })) diff --git a/packages/test-support/acp-snapshot/src/suite.ts b/packages/test-support/acp-snapshot/src/suite.ts index 46327cfdae..3312089d92 100644 --- a/packages/test-support/acp-snapshot/src/suite.ts +++ b/packages/test-support/acp-snapshot/src/suite.ts @@ -145,11 +145,10 @@ export interface Scenario { */ headerClass?: string /** - * Alternate LIVE config path (absolute) this scenario boots instead of - * {@link AgentUnderTest.configPath} — an overlay composing a different - * tree (its basename must still end in `cordis.yml` so the bin's replay - * swap finds the sibling `*cordis.snapshot.yml`). A scenario whose - * overlay changes the composed header also needs its own + * Alternate live profile patch (absolute) this scenario boots instead of + * {@link AgentUnderTest.configPath}. Its basename must still end in + * `cordis.yml` so the launcher finds the replay sibling. A scenario whose + * patch changes the composed header also needs its own * {@link headerClass}. */ configPath?: string @@ -1194,8 +1193,8 @@ export function defineAcpSnapshotSuite(options: SnapshotSuiteOptions): void { ...existsSync(workspaceDir) ? { workspaceDir } : {}, ...scenario.prepareWorkspace !== undefined ? { prepareWorkspace: scenario.prepareWorkspace } : {}, ...scenario.workspaceParent !== undefined ? { workspaceParent: scenario.workspaceParent } : {}, - // A scenario booting an overlay tree passes its own live config; the - // bin's replay swap derives the sibling `*cordis.snapshot.yml` from it. + // A scenario passes its live profile patch; the launcher derives + // the sibling `*cordis.snapshot.yml` for replay. ...scenario.configPath !== undefined ? { configPath: scenario.configPath } : {}, }) diff --git a/packages/test-support/acp-snapshot/tests/harness.spec.ts b/packages/test-support/acp-snapshot/tests/harness.spec.ts index 005456e7c0..9a2d70855a 100644 --- a/packages/test-support/acp-snapshot/tests/harness.spec.ts +++ b/packages/test-support/acp-snapshot/tests/harness.spec.ts @@ -1,8 +1,8 @@ -import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, readFile, readdir, realpath, rm, symlink, writeFile } from 'node:fs/promises' import { once } from 'node:events' import { tmpdir } from 'node:os' import { delimiter, join, relative, sep } from 'node:path' -import { fileURLToPath } from 'node:url' +import { fileURLToPath, pathToFileURL } from 'node:url' import { afterAll, describe, expect, it, vi } from 'vitest' import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk' import { runScenario, snapshotSpillRoot, type AgentUnderTest, type InputStep } from '../src/harness.ts' @@ -44,6 +44,13 @@ const AGENT: AgentUnderTest = { tsconfigPath: fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)), } +async function materializedPatch(root: string, filename: string): Promise { + const entries = await readdir(root, { recursive: true }) + const matches = entries.filter(entry => entry.endsWith(filename)) + expect(matches).toHaveLength(1) + return join(root, matches[0] as string) +} + /** Temp scenario dirs to drop after the suite. */ const tempDirs: string[] = [] afterAll(async () => { @@ -142,6 +149,110 @@ describe('runScenario', () => { expect(exited).toBe(true) }) + it('builds dsh profile argv and rebases relative modules in live and replay patches', async () => { + const { dir, fixtureFile } = await scenario({}) + const patchDir = join(dir, 'patches') + const basePatch = join(patchDir, 'base.cordis.yml') + const selectedPatch = join(patchDir, 'selected.cordis.yml') + const replayPatch = join(patchDir, 'selected.cordis.snapshot.yml') + const packageDir = join(dir, 'node_modules', 'example-package') + const scopedPackageDir = join(dir, 'node_modules', '@fixture', 'example-package') + await Promise.all([ + mkdir(patchDir, { recursive: true }), + mkdir(packageDir, { recursive: true }), + mkdir(scopedPackageDir, { recursive: true }), + ]) + await Promise.all([ + writeFile(basePatch, [ + '- id: asserted', + ' name: ./plugin.mjs', + ' disabled: true', + '- insert:', + ' - id: relative', + ' name: ./plugin.mjs', + ' - id: package', + ' name: example-package', + ' - id: scoped-package', + " name: '@fixture/example-package/subpath'", + ' - id: builtin', + ' name: fs', + ' - id: group', + ' name: cordis:group', + ' group: true', + ' config:', + ' - id: nested', + ' name: ../nested.mjs', + '', + ].join('\n')), + writeFile(selectedPatch, '[]\n'), + writeFile(replayPatch, '[]\n'), + writeFile(join(patchDir, 'plugin.mjs'), 'export function apply() {}\n'), + writeFile(join(packageDir, 'package.json'), '{"name":"example-package","version":"1.0.0"}\n'), + writeFile(join(scopedPackageDir, 'package.json'), '{"name":"@fixture/example-package","version":"1.0.0"}\n'), + ]) + const profileAgent: AgentUnderTest = { ...AGENT, configPath: basePatch, profile: 'acp' } + + const live = launchAcpTestAgent({ + agent: profileAgent, + cwd: dir, + configPath: selectedPatch, + env: { DSH_SNAPSHOT: 'record', DSH_SNAPSHOT_FILE: fixtureFile }, + }) + await live.spawned + await live.close() + const materializedRoot = join(dir, '.dsh-profile-patches') + const materialized = await readFile(await materializedPatch(materializedRoot, '0-base.cordis.yml'), 'utf8') + expect(materialized).toContain(pathToFileURL(join(patchDir, 'plugin.mjs')).href) + expect(materialized).toContain(pathToFileURL(join(dir, 'nested.mjs')).href) + expect(materialized).toContain('example-package') + expect(await realpath(join(dir, '.dsh', 'profiles', 'node_modules', 'example-package'))) + .toBe(await realpath(packageDir)) + expect(await realpath(join(dir, '.dsh', 'profiles', 'node_modules', '@fixture', 'example-package'))) + .toBe(await realpath(scopedPackageDir)) + expect(await readFile(await materializedPatch(materializedRoot, '1-selected.cordis.yml'), 'utf8')).toContain('[]') + + const replay = launchAcpTestAgent({ + agent: profileAgent, + cwd: dir, + configPath: selectedPatch, + env: { DSH_SNAPSHOT: 'replay', DSH_SNAPSHOT_FILE: fixtureFile }, + }) + await replay.spawned + await replay.close() + expect(await readFile(await materializedPatch(materializedRoot, '1-selected.cordis.snapshot.yml'), 'utf8')) + .toContain('[]') + expect((await readdir(materializedRoot, { withFileTypes: true })).filter(entry => entry.isDirectory())) + .toHaveLength(2) + + const conflictPatch = join(dir, 'conflict.cordis.yml') + const conflictPackage = join(dir, 'node_modules', 'conflict-package') + const otherPackage = join(dir, 'other-conflict-package') + const conflictLink = join(dir, '.dsh', 'profiles', 'node_modules', 'conflict-package') + await Promise.all([ + mkdir(conflictPackage, { recursive: true }), + mkdir(otherPackage, { recursive: true }), + writeFile(conflictPatch, '- id: conflict\n name: conflict-package\n'), + ]) + await Promise.all([ + writeFile(join(conflictPackage, 'package.json'), '{"name":"conflict-package","version":"1.0.0"}\n'), + writeFile(join(otherPackage, 'package.json'), '{"name":"conflict-package","version":"2.0.0"}\n'), + ]) + await symlink(otherPackage, conflictLink, process.platform === 'win32' ? 'junction' : 'dir') + expect(() => launchAcpTestAgent({ + agent: { ...profileAgent, configPath: conflictPatch }, + cwd: dir, + env: { DSH_SNAPSHOT: 'record', DSH_SNAPSHOT_FILE: fixtureFile }, + })).toThrow('ACP profile package conflict-package resolves to two directories') + + const invalidPatch = join(dir, 'invalid.cordis.yml') + await writeFile(invalidPatch, 'not: a-list\n') + expect(() => launchAcpTestAgent({ + agent: { ...profileAgent, configPath: invalidPatch }, + cwd: dir, + env: { DSH_SNAPSHOT: 'record', DSH_SNAPSHOT_FILE: fixtureFile }, + })).toThrow(`ACP profile patch must be a top-level array: ${invalidPatch}`) + }) + it('waits for inherited stdio and buffered ACP parsing after the parent exits', { timeout: 20_000 }, async () => { const { dir, fixtureFile } = await scenario({ lateInheritedOutput: true }) const launched = launchAcpTestAgent({ diff --git a/packages/test-support/acp-snapshot/tsconfig.json b/packages/test-support/acp-snapshot/tsconfig.json index 5c4fa7d80a..b258ab7851 100644 --- a/packages/test-support/acp-snapshot/tsconfig.json +++ b/packages/test-support/acp-snapshot/tsconfig.json @@ -8,6 +8,9 @@ "src" ], "references": [ + { + "path": "../../../vendor/include" + }, { "path": "../loader-smoke" }, diff --git a/packages/test-support/loader-smoke/src/index.ts b/packages/test-support/loader-smoke/src/index.ts index 33ec56ec0e..7f8b262ead 100644 --- a/packages/test-support/loader-smoke/src/index.ts +++ b/packages/test-support/loader-smoke/src/index.ts @@ -65,6 +65,8 @@ export interface ExampleLaunchOptions { readonly mode?: ExampleMode /** Absolute repo tsconfig whose `paths` map resolves unbuilt workspace imports. Required in `src` mode, ignored in `lib`. */ readonly tsconfigPath?: string + /** Select the ESM-only tsx hook instead of the generic loader. */ + readonly sourceImport?: 'tsx/esm' /** Extra environment entries the mode-specific ones layer over; the caller then merges the result over `process.env`. */ readonly env?: NodeJS.ProcessEnv } @@ -113,7 +115,9 @@ export function resolveExampleLaunch(options: ExampleLaunchOptions): ExampleLaun if (options.tsconfigPath === undefined) { throw new Error("resolveExampleLaunch: 'src' mode needs tsconfigPath for the workspace paths map.") } - const tsxLoader = import.meta.resolve('tsx') + const tsxLoader = options.sourceImport === 'tsx/esm' + ? import.meta.resolve('tsx/esm') + : import.meta.resolve('tsx') env.TSX_TSCONFIG_PATH = options.tsconfigPath return { command: process.execPath, args: ['--import', tsxLoader, options.srcBin, ...configArgs], env } } diff --git a/packages/test-support/loader-smoke/tests/example-launch.spec.ts b/packages/test-support/loader-smoke/tests/example-launch.spec.ts index 48bb58b09d..03b5e39ee4 100644 --- a/packages/test-support/loader-smoke/tests/example-launch.spec.ts +++ b/packages/test-support/loader-smoke/tests/example-launch.spec.ts @@ -5,7 +5,7 @@ import { resolveExampleMode, } from '@deepseek-ai/dsh-loader-smoke' -const SRC_BIN = '/repo/packages/examples/acp-demo/src/bin.ts' +const SRC_BIN = '/repo/apps/cli/src/bin.ts' const TSCONFIG = '/repo/tsconfig.json' const originalMode = process.env[EXAMPLE_MODE_ENV] @@ -57,6 +57,17 @@ describe('resolveExampleLaunch', () => { expect(() => resolveExampleLaunch({ srcBin: SRC_BIN, mode: 'src' })).toThrow(/needs tsconfigPath/) }) + it('src mode: resolves an app-specific tsx import hook', () => { + const { args } = resolveExampleLaunch({ + srcBin: SRC_BIN, + mode: 'src', + sourceImport: 'tsx/esm', + tsconfigPath: TSCONFIG, + }) + expect(args[0]).toBe('--import') + expect(args[1]).toContain('/tsx/dist/esm/index.mjs') + }) + it('lib mode: plain node on the derived lib bin, no tsx and no paths env', () => { const { args, env } = resolveExampleLaunch({ srcBin: SRC_BIN, @@ -65,7 +76,7 @@ describe('resolveExampleLaunch', () => { env: { DSH_HOME: '/tmp/home' }, }) expect(args).not.toContain('--import') - expect(args).toContain('/repo/packages/examples/acp-demo/lib/bin.js') + expect(args).toContain('/repo/apps/cli/lib/bin.js') expect(args.slice(-2)).toEqual(['--config', './cordis.yml']) expect(env.TSX_TSCONFIG_PATH).toBeUndefined() expect(env.DSH_HOME).toBe('/tmp/home') @@ -79,18 +90,18 @@ describe('resolveExampleLaunch', () => { it('lib mode: rewrites only the last /src/ segment', () => { const { args } = resolveExampleLaunch({ - srcBin: '/repo/src/packages/examples/acp-demo/src/bin.ts', + srcBin: '/repo/src/apps/cli/src/bin.ts', mode: 'lib', }) - expect(args).toContain('/repo/src/packages/examples/acp-demo/lib/bin.js') + expect(args).toContain('/repo/src/apps/cli/lib/bin.js') }) it('lib mode: derives the built bin from a Windows source path', () => { const { args } = resolveExampleLaunch({ - srcBin: String.raw`D:\repo\src\packages\examples\acp-demo\src\bin.ts`, + srcBin: String.raw`D:\repo\src\apps\cli\src\bin.ts`, mode: 'lib', }) - expect(args).toContain(String.raw`D:\repo\src\packages\examples\acp-demo\lib\bin.js`) + expect(args).toContain(String.raw`D:\repo\src\apps\cli\lib\bin.js`) }) it('lib mode: throws when the bin has no /src/ segment', () => { @@ -100,6 +111,6 @@ describe('resolveExampleLaunch', () => { it('defaults the mode from the environment', () => { process.env[EXAMPLE_MODE_ENV] = 'lib' const { args } = resolveExampleLaunch({ srcBin: SRC_BIN }) - expect(args).toContain('/repo/packages/examples/acp-demo/lib/bin.js') + expect(args).toContain('/repo/apps/cli/lib/bin.js') }) }) diff --git a/scripts/demo-code-mode.mjs b/scripts/demo-code-mode.mjs index 7ddbbae2cc..a395ee9951 100644 --- a/scripts/demo-code-mode.mjs +++ b/scripts/demo-code-mode.mjs @@ -8,9 +8,13 @@ if (process.argv.length > 2) { const child = spawn(process.execPath, [ '--import', - 'tsx', - 'packages/examples/acp-demo/src/bin.ts', - '--config', + 'tsx/esm', + 'apps/cli/src/bin.ts', + '--profile', + 'acp', + '--patch', + 'examples/acp-agent/cordis.yml', + '--patch', 'examples/acp-agent/code-mode.cordis.yml', ], { stdio: 'inherit' }) child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) }) diff --git a/scripts/demo-cordis.mjs b/scripts/demo-cordis.mjs index 43a23ab250..a2564c5fb0 100644 --- a/scripts/demo-cordis.mjs +++ b/scripts/demo-cordis.mjs @@ -6,8 +6,12 @@ import { spawn } from 'node:child_process' const SURFACES = new Map([ // The browser surface with the cordis toolset layered on: `dsh web --config` // applies this overlay over the shipped web composition; it owns port 3081. - ['web', ['--import', 'tsx', 'apps/cli/src/bin.ts', 'web', '--patch', 'examples/web-cordis/cordis.yml']], - ['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/cordis-tools.cordis.yml']], + ['web', ['--import', 'tsx/esm', 'apps/cli/src/bin.ts', 'web', '--patch', 'examples/web-cordis/cordis.yml']], + ['acp', [ + '--import', 'tsx/esm', 'apps/cli/src/bin.ts', '--profile', 'acp', + '--patch', 'examples/acp-agent/cordis.yml', + '--patch', 'examples/acp-agent/cordis-tools.cordis.yml', + ]], ]) const surface = process.argv[2] ?? 'web'