Merge remote-tracking branch 'origin/master' into feat/web-session-log-export
This commit is contained in:
@@ -298,12 +298,18 @@
|
|||||||
"process/2026-07-23-browser-demo-gif-recording.i18n.yaml": "sha256:808ccdda39e540645b440e40a2124baed737b98636265d8f6d8cf036a70f0d50",
|
"process/2026-07-23-browser-demo-gif-recording.i18n.yaml": "sha256:808ccdda39e540645b440e40a2124baed737b98636265d8f6d8cf036a70f0d50",
|
||||||
"process/2026-07-23-browser-demo-gif-recording.md": "sha256:4d3a3dc829c75b66f4f57a6a763b4b9562ce10efb90a19308142f598c5ea8524",
|
"process/2026-07-23-browser-demo-gif-recording.md": "sha256:4d3a3dc829c75b66f4f57a6a763b4b9562ce10efb90a19308142f598c5ea8524",
|
||||||
"process/2026-07-23-browser-demo-gif-recording.zh.md": "sha256:409e5d31ea87f35c5227fcd1bd105167f580a8a24d29a223ce390b29eb31639a",
|
"process/2026-07-23-browser-demo-gif-recording.zh.md": "sha256:409e5d31ea87f35c5227fcd1bd105167f580a8a24d29a223ce390b29eb31639a",
|
||||||
|
"process/2026-07-23-personal-staging-maintenance-skills.i18n.yaml": "sha256:cbd5c32f2997713339699f4c12876b1950895fcb21594927c7509e3cb4c5b8d5",
|
||||||
|
"process/2026-07-23-personal-staging-maintenance-skills.md": "sha256:a1ef960ddc47c8bc14578d8432ed0ac4272e31178937780989e4e3b3e3f4d49f",
|
||||||
|
"process/2026-07-23-personal-staging-maintenance-skills.zh.md": "sha256:c0f3fcf0914af88cafcc7e094a2131bd1f764003defc6eaa446b743319e73ec1",
|
||||||
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml": "sha256:89b6dc255cb0dd9d97ae6f34f37d185412d4260d7bf2f87d6b25e340f01dc26e",
|
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml": "sha256:89b6dc255cb0dd9d97ae6f34f37d185412d4260d7bf2f87d6b25e340f01dc26e",
|
||||||
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md": "sha256:1742e09435ade4a09349c8843eb381e870a2be2d51b74449f08260422c8096b3",
|
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md": "sha256:1742e09435ade4a09349c8843eb381e870a2be2d51b74449f08260422c8096b3",
|
||||||
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md": "sha256:220bd53a88617b09ee8970627c46540fa8951724b102acfe296a7ed7d0f7b5fe",
|
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md": "sha256:220bd53a88617b09ee8970627c46540fa8951724b102acfe296a7ed7d0f7b5fe",
|
||||||
"process/2026-07-27-wine-windows-gates-experiment.i18n.yaml": "sha256:6f4cbc12ee9cddbb297bf7e138ccabcd204f66898a0f7411b1633f03d5a9eab5",
|
"process/2026-07-27-wine-windows-gates-experiment.i18n.yaml": "sha256:6f4cbc12ee9cddbb297bf7e138ccabcd204f66898a0f7411b1633f03d5a9eab5",
|
||||||
"process/2026-07-27-wine-windows-gates-experiment.md": "sha256:8d37dcdab058098c7de3da1de00ce61bef92bbc8d6ee71add959474c6fb3e936",
|
"process/2026-07-27-wine-windows-gates-experiment.md": "sha256:8d37dcdab058098c7de3da1de00ce61bef92bbc8d6ee71add959474c6fb3e936",
|
||||||
"process/2026-07-27-wine-windows-gates-experiment.zh.md": "sha256:77fbf04df36af09e55007a93bd6b22d08ff99869efe8de3e97dac5b4701e0a9e",
|
"process/2026-07-27-wine-windows-gates-experiment.zh.md": "sha256:77fbf04df36af09e55007a93bd6b22d08ff99869efe8de3e97dac5b4701e0a9e",
|
||||||
|
"process/2026-07-31-installer-adopts-existing-checkout.i18n.yaml": "sha256:e7d072856cd3df3b717d94647be4ea05db087b114df4b11d68a171677b94c970",
|
||||||
|
"process/2026-07-31-installer-adopts-existing-checkout.md": "sha256:d97e39d07bde30d534417a406fc6c56ee6765dbc7b671888d17ef2cac9a7c6dc",
|
||||||
|
"process/2026-07-31-installer-adopts-existing-checkout.zh.md": "sha256:ab445493c1aacf3f8dc091bdbd765ad56c551ae613e0d8156610f0c4614a7431",
|
||||||
"process/2026-08-04-forward-only-pr-issue-status.i18n.yaml": "sha256:af23e203a66a95674154899410e2f420d1d0685dbf856c24cfccdaa547a17925",
|
"process/2026-08-04-forward-only-pr-issue-status.i18n.yaml": "sha256:af23e203a66a95674154899410e2f420d1d0685dbf856c24cfccdaa547a17925",
|
||||||
"process/2026-08-04-forward-only-pr-issue-status.md": "sha256:2d31077da47d95ab3ddf64d5efc6b1b8fb7c7709d39aca4a825ef9e9d382d501",
|
"process/2026-08-04-forward-only-pr-issue-status.md": "sha256:2d31077da47d95ab3ddf64d5efc6b1b8fb7c7709d39aca4a825ef9e9d382d501",
|
||||||
"process/2026-08-04-forward-only-pr-issue-status.zh.md": "sha256:b61f865b7a8a0ac901250a3edbb92ea73177067c4c25448c7088925c2caeccd7",
|
"process/2026-08-04-forward-only-pr-issue-status.zh.md": "sha256:b61f865b7a8a0ac901250a3edbb92ea73177067c4c25448c7088925c2caeccd7",
|
||||||
|
|||||||
+2
-2
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-23-personal-staging-maintenance-skills.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-23-personal-staging-maintenance-skills.md
|
||||||
2026-07-23-personal-staging-maintenance-skills.md: a7ccc5b1e0f13e880c58a93d2e4c2cd4f06e2a93
|
2026-07-23-personal-staging-maintenance-skills.md: 86654be1afe986107e7dae4440b497a266636012
|
||||||
2026-07-23-personal-staging-maintenance-skills.zh.md: 8593291f510352429cbb29679e7d033589312e41
|
2026-07-23-personal-staging-maintenance-skills.zh.md: 7593a2e8cafc93df02c9418c94f84bbd3f154a0c
|
||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
# Agent Note: Personal staging maintenance skills
|
# Agent Note: Personal staging maintenance skills
|
||||||
|
|
||||||
Status: implemented
|
Status: implemented
|
||||||
|
Archived: 2026-08-10
|
||||||
|
|
||||||
English | [中文](2026-07-23-personal-staging-maintenance-skills.zh.md)
|
English | [中文](2026-07-23-personal-staging-maintenance-skills.zh.md)
|
||||||
|
|
||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
# Agent Note: 个人集成分支维护 skill
|
# Agent Note: 个人集成分支维护 skill
|
||||||
|
|
||||||
Status: implemented
|
Status: implemented
|
||||||
|
Archived: 2026-08-10
|
||||||
|
|
||||||
[English](2026-07-23-personal-staging-maintenance-skills.md) | 中文
|
[English](2026-07-23-personal-staging-maintenance-skills.md) | 中文
|
||||||
|
|
||||||
+2
-2
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md
|
||||||
2026-07-31-installer-adopts-existing-checkout.md: 1d45a14273095610e3ed8047da2ce9f4ca95cbb6
|
2026-07-31-installer-adopts-existing-checkout.md: 08eec47a1cf1254409df93ab83ddf503b4d7d171
|
||||||
2026-07-31-installer-adopts-existing-checkout.zh.md: 971bc3b389c341b314872b8e45ab20ebd2ed5b2c
|
2026-07-31-installer-adopts-existing-checkout.zh.md: dc3cc5ee8dbbe287860ad02f4bdba66afb4fbeaf
|
||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
# Agent Note: the installer adopts an existing checkout into the managed layout
|
# Agent Note: the installer adopts an existing checkout into the managed layout
|
||||||
|
|
||||||
Status: implemented
|
Status: implemented
|
||||||
|
Archived: 2026-08-10
|
||||||
|
|
||||||
English | [中文](2026-07-31-installer-adopts-existing-checkout.zh.md)
|
English | [中文](2026-07-31-installer-adopts-existing-checkout.zh.md)
|
||||||
|
|
||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
# Agent Note: 安装器把已有检出接管进受管布局
|
# Agent Note: 安装器把已有检出接管进受管布局
|
||||||
|
|
||||||
Status: implemented
|
Status: implemented
|
||||||
|
Archived: 2026-08-10
|
||||||
|
|
||||||
[English](2026-07-31-installer-adopts-existing-checkout.md) | 中文
|
[English](2026-07-31-installer-adopts-existing-checkout.md) | 中文
|
||||||
|
|
||||||
+2
-2
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md
|
||||||
2026-07-29-dsh-source-launch-tsx-esm.md: 5cf4a227f388a1ac8315594af4e0256864ef17f5
|
2026-07-29-dsh-source-launch-tsx-esm.md: b2428602a780f2880f0f803ba59b16a76b39790e
|
||||||
2026-07-29-dsh-source-launch-tsx-esm.zh.md: b5a52b3d01840337c0091310e50d8fac34245519
|
2026-07-29-dsh-source-launch-tsx-esm.zh.md: 866bc8886725788e6e619f9507f5c8ccab63f82d
|
||||||
@@ -14,11 +14,11 @@ Startup latency also mattered: the off-thread `module.register()` hooks worker s
|
|||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
The `dsh` TUI, Web, and headless source launches run `node --import tsx/esm`: tsx's ESM-only hook owns both TypeScript transformation and tsconfig `paths` projection. `bin/dsh`, the root `dsh`/`demo:tui`/`demo:web` scripts, and the Code Mode TUI overlay use the same vector; `bin/dsh` references the hook and tsconfig by absolute checkout paths (bare `tsx/esm` does not resolve from an arbitrary cwd) and pins `TSX_TSCONFIG_PATH` to the root tsconfig. The CJS hook stays off because the CLI source graph is ESM-only; measured TUI time-to-banner is ~0.7s versus ~1.1s under the full tsx default and ~0.75s under the removed native chain.
|
The `dsh` TUI, Web, and headless source launches run `node --import tsx/esm`: tsx's ESM-only hook owns both TypeScript transformation and tsconfig `paths` projection. The root `dsh` script completes the repository build, then uses that vector from the repository root. The CJS hook stays off because the CLI source graph is ESM-only; measured runtime launch to the TUI banner is ~0.7s versus ~1.1s under the full tsx default and ~0.75s under the removed native chain.
|
||||||
|
|
||||||
`scripts/tspath-loader.ts` and `apps/cli/src/tsconfig-paths-loader.ts` are deleted. With them went the loader's runtime rule of mapping a workspace import only for declared runtime dependencies — tsx applies the `paths` map unconditionally. Declaration completeness now rests on the static gates alone: `verify-cordis-config` for configured bare plugins, and workspace constraints for manifests. (That runtime rule found real bugs: `dsh-plan-mode` and `dsh-tool-tasks` imported `@deepseek-ai/dsh-llm` while declaring it only in devDependencies; since fixed.)
|
`scripts/tspath-loader.ts` and `apps/cli/src/tsconfig-paths-loader.ts` are deleted. With them went the loader's runtime rule of mapping a workspace import only for declared runtime dependencies — tsx applies the `paths` map unconditionally. Declaration completeness now rests on the static gates alone: `verify-cordis-config` for configured bare plugins, and workspace constraints for manifests. (That runtime rule found real bugs: `dsh-plan-mode` and `dsh-tool-tasks` imported `@deepseek-ai/dsh-llm` while declaring it only in devDependencies; since fixed.)
|
||||||
|
|
||||||
The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (`apps/cli/tests/source-launch.compat.spec.ts`): a keyless piped-stdio launch of the exact production vector asserting the non-zero-exit TTY refusal. Any future Node change to module hooks or TypeScript handling turns this gate red instead of breaking developers' `pnpm dsh`.
|
The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (`apps/cli/tests/source-launch.compat.spec.ts`): a keyless piped-stdio launch of the exact production runtime vector asserting the non-zero-exit TTY refusal. Any future Node change to module hooks or TypeScript handling turns this gate red instead of breaking developers' `pnpm dsh`.
|
||||||
|
|
||||||
## Alternatives considered
|
## Alternatives considered
|
||||||
|
|
||||||
@@ -35,4 +35,4 @@ The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (`
|
|||||||
- One launch vector across the whole engines range, including future Node lines that change native TypeScript support; the smoke gate enforces it per matrix line.
|
- One launch vector across the whole engines range, including future Node lines that change native TypeScript support; the smoke gate enforces it per matrix line.
|
||||||
- TypeScript transformation is delegated to tsx/esbuild again, reversing the prior note's goal of proving Node-native transformation; that goal is unreachable while vendored sources use non-erasable syntax and Node ships no transform mode.
|
- TypeScript transformation is delegated to tsx/esbuild again, reversing the prior note's goal of proving Node-native transformation; that goal is unreachable while vendored sources use non-erasable syntax and Node ships no transform mode.
|
||||||
- The runtime declared-dependency enforcement in source launches is gone; undeclared workspace imports now surface only through static gates or built-mode resolution failures.
|
- The runtime declared-dependency enforcement in source launches is gone; undeclared workspace imports now surface only through static gates or built-mode resolution failures.
|
||||||
- Startup improves ~0.4s over the full tsx default (`demo:headless` now aliases the same `dsh --profile headless` source launch; ACP keeps `--import tsx` because its graph was not audited for CJS-hook dependence and its launch latency is not on the interactive path).
|
- Runtime launch improves ~0.4s over the full tsx default; ACP keeps `--import tsx` because its graph was not audited for CJS-hook dependence and its launch latency is not on the interactive path.
|
||||||
@@ -14,11 +14,11 @@ Status: implemented
|
|||||||
|
|
||||||
## 决策
|
## 决策
|
||||||
|
|
||||||
`dsh` 的 TUI、Web 与无头源码启动运行 `node --import tsx/esm`:由 tsx 的 ESM-only 钩子同时负责 TypeScript 转换与 tsconfig `paths` 投影。`bin/dsh`、根目录的 `dsh`/`demo:tui`/`demo:web` 脚本以及 Code Mode TUI overlay 使用同一向量;`bin/dsh` 以 checkout 的绝对路径引用钩子与 tsconfig(裸的 `tsx/esm` 无法从任意 cwd 解析),并将 `TSX_TSCONFIG_PATH` 固定到根 tsconfig。CJS 钩子保持关闭,因为 CLI 源码图是纯 ESM;实测 TUI 到 banner 约 0.7s,对比完整 tsx 默认形态约 1.1s、已移除的原生链约 0.75s。
|
`dsh` 的 TUI、Web 与无头源码启动运行 `node --import tsx/esm`:由 tsx 的 ESM-only 钩子同时负责 TypeScript 转换与 tsconfig `paths` 投影。根目录的 `dsh` 脚本先完成仓库构建,然后从仓库根目录使用同一启动方式。CJS 钩子保持关闭,因为 CLI(命令行界面)源码图是纯 ESM;实测运行时启动至 TUI banner 耗时约 0.7s,对比完整 tsx 默认形态约 1.1s、已移除的原生链约 0.75s。
|
||||||
|
|
||||||
`scripts/tspath-loader.ts` 与 `apps/cli/src/tsconfig-paths-loader.ts` 已删除。随之消失的还有该 loader「仅为已声明运行时依赖映射 workspace import」的运行时规则——tsx 无条件应用 `paths` 映射。声明完整性现在仅由静态门禁保障:配置的裸插件走 `verify-cordis-config`,manifest(元数据清单)走 workspace constraints。(该运行时规则确实发现过真实缺陷:`dsh-plan-mode` 与 `dsh-tool-tasks` 导入 `@deepseek-ai/dsh-llm` 却只声明在 devDependencies;后已修复。)
|
`scripts/tspath-loader.ts` 与 `apps/cli/src/tsconfig-paths-loader.ts` 已删除。随之消失的还有该 loader「仅为已声明运行时依赖映射 workspace import」的运行时规则——tsx 无条件应用 `paths` 映射。声明完整性现在仅由静态门禁保障:配置的裸插件走 `verify-cordis-config`,manifest(元数据清单)走 workspace constraints。(该运行时规则确实发现过真实缺陷:`dsh-plan-mode` 与 `dsh-tool-tasks` 导入 `@deepseek-ai/dsh-llm` 却只声明在 devDependencies;后已修复。)
|
||||||
|
|
||||||
node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(`apps/cli/tests/source-launch.compat.spec.ts`):以精确的生产启动向量做 keyless 管道 stdio 启动,断言非零退出的 TTY 拒绝。未来 Node 对模块钩子或 TypeScript 处理的任何改动都会让该门禁变红,而不是破坏开发者的 `pnpm dsh`。
|
node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(`apps/cli/tests/source-launch.compat.spec.ts`):以精确的生产运行时启动向量做 keyless 管道 stdio 启动,断言非零退出的 TTY 拒绝。未来 Node 对模块钩子或 TypeScript 处理的任何改动都会让该门禁变红,而不是破坏开发者的 `pnpm dsh`。
|
||||||
|
|
||||||
## 备选方案
|
## 备选方案
|
||||||
|
|
||||||
@@ -35,4 +35,4 @@ node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(`
|
|||||||
- 整个 engines 范围(包括未来改变原生 TypeScript 支持的 Node 版本线)只有一个启动向量;冒烟门禁按矩阵行强制执行。
|
- 整个 engines 范围(包括未来改变原生 TypeScript 支持的 Node 版本线)只有一个启动向量;冒烟门禁按矩阵行强制执行。
|
||||||
- TypeScript 转换重新委托给 tsx/esbuild,逆转了前一篇 Agent Note「证明 Node 原生转换可用」的目标;在 vendor 源码使用不可擦除语法且 Node 不再提供 transform 模式的情况下,该目标不可达。
|
- TypeScript 转换重新委托给 tsx/esbuild,逆转了前一篇 Agent Note「证明 Node 原生转换可用」的目标;在 vendor 源码使用不可擦除语法且 Node 不再提供 transform 模式的情况下,该目标不可达。
|
||||||
- 源码启动中的运行时依赖声明强制不复存在;未声明的 workspace import 现在只能通过静态门禁或构建模式的解析失败暴露。
|
- 源码启动中的运行时依赖声明强制不复存在;未声明的 workspace import 现在只能通过静态门禁或构建模式的解析失败暴露。
|
||||||
- 启动相比完整 tsx 默认形态快约 0.4s(`demo:headless` 现为同一条 `dsh --profile headless` 源码启动命令的别名;ACP 保留 `--import tsx`,因为它的依赖图尚未就 CJS 钩子依赖性做审计,且其启动延迟不在交互路径上)。
|
- 运行时启动相比完整 tsx 默认形态快约 0.4s;ACP(Agent Client Protocol)保留 `--import tsx`,因为它的依赖图尚未就 CJS 钩子依赖性做审计,且其启动延迟不在交互路径上。
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md
|
||||||
2026-07-20-dsh-cli-personal-config.md: 02883c89f27e51d6091d4d65167ebdd6a96f6f51
|
2026-07-20-dsh-cli-personal-config.md: ed04725e92848bbab550a27ef2f4c021536f765e
|
||||||
2026-07-20-dsh-cli-personal-config.zh.md: 6e56e892cf682ea514b036750e44dbb06e944a80
|
2026-07-20-dsh-cli-personal-config.zh.md: cc97987f803f7fb513e94ce0ce079558f5e3dc75
|
||||||
@@ -14,7 +14,7 @@ The entry modes and the personal file's name and location below are superseded b
|
|||||||
|
|
||||||
Two coupled pieces, aligned with the `apps/` assembly tier proposed by the `dsh web` PR (#443):
|
Two coupled pieces, aligned with the `apps/` assembly tier proposed by the `dsh web` PR (#443):
|
||||||
|
|
||||||
**The `dsh` CLI (`apps/cli`, npm name `@deepseek-ai/dsh`).** `apps/*` is the product-assembly tier over `packages/*` libraries. One bin dispatches the default interactive TUI, `-p`/`--prompt` headless turns, and the `web` surface. The TUI boots `examples/tui-agent/cordis.yml` (or `--config`) with the invoking directory as the workspace. The committed `bin/dsh` launcher resolves the checkout through its own real path and runs the app with tsx's ESM hook; the [source-launch decision](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md) owns that contract. `pnpm run demo:tui` runs the same entry.
|
**The `dsh` CLI (`apps/cli`, npm name `@deepseek-ai/dsh`).** `apps/*` is the product-assembly tier over `packages/*` libraries. One bin dispatches the default interactive TUI, `-p`/`--prompt` headless turns, and the `web` surface. The TUI boots `examples/tui-agent/cordis.yml` (or `--config`) with the invoking directory as the workspace. From a source checkout, the root `pnpm dsh` script builds the repository and runs the same entry with tsx's ESM hook; the [source-launch decision](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md) owns that contract.
|
||||||
|
|
||||||
**Personal config (`dsh-app-boot`).** The personal overlay lives in the Harness home — `$DSH_HOME`, else `~/.dsh` — resolved by the shared [`resolveDshHome`](../architecture/2026-07-24-single-harness-home-resolver.md) (`@deepseek-ai/dsh-paths`), the same single root skills and AGENTS.md resolve against. The dsh TUI, Web, and headless surfaces consume its two optional files; the demo bins boot their committed trees verbatim:
|
**Personal config (`dsh-app-boot`).** The personal overlay lives in the Harness home — `$DSH_HOME`, else `~/.dsh` — resolved by the shared [`resolveDshHome`](../architecture/2026-07-24-single-harness-home-resolver.md) (`@deepseek-ai/dsh-paths`), the same single root skills and AGENTS.md resolve against. The dsh TUI, Web, and headless surfaces consume its two optional files; the demo bins boot their committed trees verbatim:
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ The TUI and Web register the exact personal path through Cordis HMR after boot.
|
|||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- `dsh` from any directory (and `pnpm run demo:tui`) can apply personal providers, models, installed bundle entries, and other Loader entries with no checkout edit; verified end-to-end against a personal Anthropic proxy with Opus 4.8, including a bash tool round trip.
|
- An installed `dsh` command can run from any directory, while source users invoke `pnpm dsh` from the checkout; both can apply personal providers, models, installed bundle entries, and other Loader entries with no checkout edit. The behavior was verified end to end against a personal Anthropic proxy with Opus 4.8, including a bash tool round trip.
|
||||||
- Because an id-targeted patch replaces the whole `config`, a personal override restates the base fields it keeps and can drift when the base entry changes shape; the loader's entry-not-found/name-mismatch warnings and [`dsh --dump-config`](../../../../apps/cli/README.md#profiles) (which prints the composed tree those patches produce) are the diagnostics.
|
- Because an id-targeted patch replaces the whole `config`, a personal override restates the base fields it keeps and can drift when the base entry changes shape; the loader's entry-not-found/name-mismatch warnings and [`dsh --dump-config`](../../../../apps/cli/README.md#profiles) (which prints the composed tree those patches produce) are the diagnostics.
|
||||||
- Personal patches resolve ids against the booted file's own tree, so nested-include overlays (Code Mode) are not personalized; live-run parity for those leaves is deferred.
|
- Personal patches resolve ids against the booted file's own tree, so nested-include overlays (Code Mode) are not personalized; live-run parity for those leaves is deferred.
|
||||||
- `dsh-app-boot` depends on `js-yaml` and imports the include's `!!js` YAML dialect (`entryListSchema`) directly, and, like `apps/cli`, depends on `@deepseek-ai/dsh-paths` for `resolveDshHome`.
|
- `dsh-app-boot` depends on `js-yaml` and imports the include's `!!js` YAML dialect (`entryListSchema`) directly, and, like `apps/cli`, depends on `@deepseek-ai/dsh-paths` for `resolveDshHome`.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Status: implemented
|
|||||||
|
|
||||||
两个耦合的部分,与 `dsh web` PR(#443)提出的 `apps/` 装配层对齐:
|
两个耦合的部分,与 `dsh web` PR(#443)提出的 `apps/` 装配层对齐:
|
||||||
|
|
||||||
**`dsh` CLI(`apps/cli`,npm 名 `@deepseek-ai/dsh`)。** `apps/*` 是位于 `packages/*` 库之上的产品组装层。一个 bin 负责分发默认交互式 TUI、`-p`/`--prompt` 无头轮次和 `web` 界面。TUI 以调用目录为 workspace,启动 `examples/tui-agent/cordis.yml`(或 `--config` 指定的配置)。已提交的 `bin/dsh` 启动器通过自身真实路径解析 checkout,并使用 tsx 的 ESM hook 运行应用;该约定由[源码启动决策](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md)维护。`pnpm run demo:tui` 运行同一入口。
|
**`dsh` CLI(命令行界面;`apps/cli`,npm 名 `@deepseek-ai/dsh`)。** `apps/*` 是位于 `packages/*` 库之上的产品组装层。一个 bin 负责分发默认交互式 TUI、`-p`/`--prompt` 无头轮次和 `web` 界面。TUI 以调用目录为 workspace,启动 `examples/tui-agent/cordis.yml`(或 `--config` 指定的配置)。在源码检出中,根目录的 `pnpm dsh` 脚本先构建仓库,再使用 tsx 的 ESM hook 运行同一入口;该约定由[源码启动决策](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md)维护。
|
||||||
|
|
||||||
**个人配置(`dsh-app-boot`)。** 个人 overlay 存放在 Harness home——`$DSH_HOME`,否则 `~/.dsh`——由共享的 [`resolveDshHome`](../architecture/2026-07-24-single-harness-home-resolver.md)(`@deepseek-ai/dsh-paths`)解析,与 skills、AGENTS.md 解析所依据的单一根目录相同。dsh 的 TUI、Web 和无头界面使用其中两个可选文件;各示例 bin 仍然逐字节按已提交的配置树启动:
|
**个人配置(`dsh-app-boot`)。** 个人 overlay 存放在 Harness home——`$DSH_HOME`,否则 `~/.dsh`——由共享的 [`resolveDshHome`](../architecture/2026-07-24-single-harness-home-resolver.md)(`@deepseek-ai/dsh-paths`)解析,与 skills、AGENTS.md 解析所依据的单一根目录相同。dsh 的 TUI、Web 和无头界面使用其中两个可选文件;各示例 bin 仍然逐字节按已提交的配置树启动:
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ TUI 和 Web 启动后通过 Cordis HMR(热模块替换)注册确切的个人
|
|||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- 在任意目录运行 `dsh`(以及 `pnpm run demo:tui`),无需修改 checkout,即可应用个人提供方、模型、已安装组合包的配置项和其他 Loader 配置项;已针对个人 Anthropic 代理与 Opus 4.8 端到端验证,包括一次 bash 工具往返。
|
- 已安装的 `dsh` 命令可从任意目录运行,源码用户则从 checkout 调用 `pnpm dsh`;两者都无需修改 checkout 即可应用个人提供方、模型、已安装组合包的配置项和其他 Loader 配置项。该行为已针对个人 Anthropic 代理与 Opus 4.8 端到端验证,包括一次 bash 工具往返。
|
||||||
- 由于按 id 定位的补丁替换整个 `config`,个人覆盖必须复述它保留的基础字段,并可能随基础配置项形态变化而漂移;诊断手段是 loader 的「配置项未找到/名称不匹配」警告和 [`dsh --dump-config`](../../../../apps/cli/README.md#profiles)(打印这些补丁合成出的配置树)。
|
- 由于按 id 定位的补丁替换整个 `config`,个人覆盖必须复述它保留的基础字段,并可能随基础配置项形态变化而漂移;诊断手段是 loader 的「配置项未找到/名称不匹配」警告和 [`dsh --dump-config`](../../../../apps/cli/README.md#profiles)(打印这些补丁合成出的配置树)。
|
||||||
- 个人补丁只在被启动文件自身的树里解析 id,因此嵌套 include 的 overlay(Code Mode)不会被个性化;这些叶子的实际运行等价性暂缓。
|
- 个人补丁只在被启动文件自身的树里解析 id,因此嵌套 include 的 overlay(Code Mode)不会被个性化;这些叶子的实际运行等价性暂缓。
|
||||||
- `dsh-app-boot` 依赖 `js-yaml`,并直接导入 include 的 `!!js` YAML 方言(`entryListSchema`);与 `apps/cli` 一样依赖 `@deepseek-ai/dsh-paths` 以获取 `resolveDshHome`。
|
- `dsh-app-boot` 依赖 `js-yaml`,并直接导入 include 的 `!!js` YAML 方言(`entryListSchema`);与 `apps/cli` 一样依赖 `@deepseek-ai/dsh-paths` 以获取 `resolveDshHome`。
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# 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 .agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md
|
||||||
|
2026-08-10-minimal-read-image-tool.md: a43e53d70e98bac7a50aa6bbabbb1e177237df01
|
||||||
|
2026-08-10-minimal-read-image-tool.zh.md: a94e4b296425ad50876b0b45a689442c896a85a1
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Agent Note: A minimal read_image tool over existing seams
|
||||||
|
|
||||||
|
Status: implemented
|
||||||
|
|
||||||
|
English | [中文](2026-08-10-minimal-read-image-tool.zh.md)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The multimodal attachment work gave user uploads a complete durable path — bytes committed to the content-addressed attachment store before the owning `user/message`, an `ImageBlock` carrying only the `sha256:` reference, and the pi-ai route re-reading verified bytes per request — but the model itself had no way to look at an image on disk. `read` rejects binary content by contract, so an agent asked about a screenshot or a rendered chart either failed or shelled out to lossy workarounds. A first standalone attempt (PR #598) solved this together with loop-level route scoping: an `agent/request-ready` extension point publishing exact-model modalities before assembly, per-route schema/guidance visibility, and a reversible `image-placeholder-v1` history projection so text routes could continue over placeholder text. That design worked but coupled a tool to new agent-loop machinery, three new session-log concepts, and per-step registration churn — far more surface than the capability needs.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Ship the smallest tool that loads an image into the next request's context, entirely over existing seams; the withdrawn PR #598 design is the explicit counter-example this note records.
|
||||||
|
|
||||||
|
- **`read_image` lives in `dsh-tool-fs`** beside `read`/`write`/`edit`. Extension selects the declared PNG/JPEG/WebP/GIF media type; the attachment store's magic-byte and pixel validation stays authoritative. Bytes travel `ctx.fs.stat` → bounded `ctx.fs.readBytes` → `ctx.attachments.saveImage` → `fs/observed`, and the tool result is the metadata envelope plus a real `ImageBlock` — `ToolResultBlock.content` already admits image blocks, the pi-ai adapter already renders them, and the Web host's model-switch guard already scans tool results, so nothing downstream changes.
|
||||||
|
- **`FileSystem.readBytes(target, signal, maxBytes)`** is a new required provider primitive: the byte bound lives at the seam so no backend can buffer an unbounded file, with the stat-size short-circuit and a one-byte-past-cap stream guard against post-stat growth (`FS_TOO_LARGE`).
|
||||||
|
- **Registration is composition-conditional, execution is route-gated.** The tool registers only under `ctx.inject(['attachments'], …)` — no store, no tool. At execution, before any I/O, the strict gate resolves the calling route (latest `request/header` config, falling back to agent options) through `ctx.llm.resolveModelInfo` and requires `image` in `inputModalities`; unknown capability refuses. A refusal is a plain `isError` result, so a text route's durable history never acquires an image block and the session cannot brick its own route.
|
||||||
|
- **Code Mode forwards the image out-of-band**: a nested dispatch returns the canonical value (execution-local, no image block) and defers a `user`-role context message carrying the envelope and image, so the picture still reaches the next request.
|
||||||
|
- **llm-replay models may declare `inputModalities`**, which is what lets the two keyless ACP snapshots pin both sides of the gate — the sha256-referenced success on an image-capable replay route and the verbatim refusal on a text-only one.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **PR #598's route-scoped design** (request-ready seam, per-route schema/guidance visibility, reversible history projection) — withdrawn in favor of this note's shape. What it bought: text routes could keep running after images entered history, and the tool disappeared from prompts where it cannot succeed. What it cost: agent-loop changes, three new durable concepts (`agent/request-ready`, `messageProjection`, availability notices), and registration that churned per step. The capability itself — see an image on the next request — never needed any of it. If per-route projection becomes a real requirement, that PR's history is the reference implementation.
|
||||||
|
- **`agent.inject()` instead of the image-bearing tool result** — routes the image around the tool result as a separate injected user message. Rejected: the image *is* the tool's result; splitting them adds a second logged message with no gain, and the tool-result path already works end to end.
|
||||||
|
- **Magic-byte sniffing instead of extension declaration** — sniffing duplicates detection the attachment store already owns (sharp-backed, authoritative). The extension is only a *declaration*; a mismatch fails closed with a rename remedy rather than being silently accepted, which also keeps the model's mental map (file name ↔ content) honest.
|
||||||
|
- **Registering unconditionally and failing on a missing store** — rejected; a deployment without an attachment store cannot ever satisfy the tool, so its schema would be a standing lie. The route gate, by contrast, is per-call state and correctly lives at the execution boundary.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- A text-only route refuses instead of degrading: no placeholder projection means no delegated-viewing story here — that is deliberately the next PR (subagent image readback rebuilt on the current subagent seams).
|
||||||
|
- The route gate races a concurrent model switch; the Web host's image-aware switch guard covers its surface, and other front doors own their equivalent. Recorded as a tool-fs Known Limitation.
|
||||||
|
- Repeated image results accumulate request-token cost until compaction; content addressing deduplicates bytes only.
|
||||||
|
- The tool-result card renders the durable reference, not pixels; inline preview is deferred to the UI packages.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Agent Note: 基于既有 seam 的最小 read_image 工具
|
||||||
|
|
||||||
|
Status: implemented
|
||||||
|
|
||||||
|
[English](2026-08-10-minimal-read-image-tool.md) | 中文
|
||||||
|
|
||||||
|
## 问题
|
||||||
|
|
||||||
|
多模态附件工作为用户上传建立了完整的持久路径:字节在所属 `user/message` 之前提交到内容寻址的附件存储,`ImageBlock` 只携带 `sha256:` 引用,pi-ai 路由在每次请求时重新读取并校验字节。但模型自己没有查看磁盘图像的手段。`read` 按约定拒绝二进制内容,因此被问到截图或渲染图表的 agent 要么失败,要么退到有损的变通做法。第一次独立尝试(PR #598)把这个问题与循环级路由作用域一起解决:新增在组装前发布确切模型模态的 `agent/request-ready` 扩展点、按路由控制 schema/指导可见性,以及可逆的 `image-placeholder-v1` 历史投影让文本路由能在占位符上继续。该设计可行,但让一个工具耦合了新的 agent-loop 机制、三个新的会话日志概念和每步的注册变动,远超这项能力本身的需要。
|
||||||
|
|
||||||
|
## 决定
|
||||||
|
|
||||||
|
只交付能把图像载入下一次请求上下文的最小工具,完全建立在既有 seam 之上;撤回的 PR #598 设计是本记录明确保留的反例。
|
||||||
|
|
||||||
|
- **`read_image` 放在 `dsh-tool-fs`**,与 `read`/`write`/`edit` 并列。扩展名选择声明的 PNG/JPEG/WebP/GIF 媒体类型;附件存储的魔数与像素校验保持权威。字节沿 `ctx.fs.stat` → 有界 `ctx.fs.readBytes` → `ctx.attachments.saveImage` → `fs/observed` 流动,工具结果是元数据信封加真正的 `ImageBlock`——`ToolResultBlock.content` 本就允许图像块,pi-ai 适配器本就会渲染它们,Web 宿主的模型切换防护本就会扫描工具结果,下游无需任何改动。
|
||||||
|
- **`FileSystem.readBytes(target, signal, maxBytes)`** 是新的必备提供方原语:字节上限放在 seam 上,任何后端都无法无界缓冲文件;stat 大小先短路,随后的流最多多读一个字节以防 stat 之后的增长(`FS_TOO_LARGE`)。
|
||||||
|
- **注册随组合条件挂载,执行按路由门禁。** 工具只在 `ctx.inject(['attachments'], …)` 作用域内注册——没有存储就没有工具。执行时在任何 I/O 之前,严格门禁通过 `ctx.llm.resolveModelInfo` 解析调用路由(最新 `request/header` 配置,缺失时回退到 agent 选项),要求 `inputModalities` 包含 `image`;能力未知即拒绝。拒绝是普通的 `isError` 结果,因此文本路由的持久历史绝不会出现图像块,会话不会毁掉自己的路由。
|
||||||
|
- **Code Mode 以带外方式转发图像**:嵌套分派返回规范值(仅限本次执行,不含图像块),并延迟提交一条携带信封和图像的 `user` 角色上下文消息,图片仍会到达下一次请求。
|
||||||
|
- **llm-replay 模型可以声明 `inputModalities`**,这正是两个 keyless ACP 快照能钉住门禁两侧的原因:图像路由上以 sha256 引用的成功结果,和纯文本路由上逐字的拒绝。
|
||||||
|
|
||||||
|
## 考虑过的替代方案
|
||||||
|
|
||||||
|
- **PR #598 的路由作用域设计**(request-ready 扩展点、按路由的 schema/指导可见性、可逆历史投影)——被本记录的形态取代后撤回。它换来的是:图像进入历史后文本路由仍能运行,工具在注定失败的提示词里消失。它付出的是:改动 agent-loop、三个新的持久概念(`agent/request-ready`、`messageProjection`、可用性通知)和每步变动的注册。而这项能力本身——下一次请求看到图像——从不需要这些。如果按路由投影将来成为真实需求,该 PR 的历史就是参考实现。
|
||||||
|
- **用 `agent.inject()` 代替带图像的工具结果**——把图像绕过工具结果,作为单独注入的用户消息。拒绝:图像就是工具的结果;拆开只会多一条无收益的日志消息,而工具结果路径本就端到端可用。
|
||||||
|
- **用魔数嗅探代替扩展名声明**——嗅探重复了附件存储已拥有的检测(基于 sharp,权威)。扩展名只是声明;不匹配时按改名修复提示失败关闭,而不是被静默接受,这也让模型对文件名与内容的对应保持诚实。
|
||||||
|
- **无条件注册、缺存储时执行报错**——拒绝;没有附件存储的部署永远无法满足该工具,其 schema 会是常态谎言。相反,路由门禁是逐调用状态,正确的位置就是执行边界。
|
||||||
|
|
||||||
|
## 后果
|
||||||
|
|
||||||
|
- 纯文本路由得到拒绝而不是降级:没有占位符投影意味着这里没有委托查看的方案——那有意留给下一个 PR(基于当前 subagent seam 重建的 subagent image readback)。
|
||||||
|
- 路由门禁与并发模型切换存在竞态;Web 宿主的图像感知切换防护覆盖其表面,其他前端拥有各自的等价防护。已记入 tool-fs 的已知限制。
|
||||||
|
- 重复的图像结果在压缩之前持续累积请求 token 成本;内容寻址只去重字节。
|
||||||
|
- 工具结果卡片渲染持久引用而非像素;内嵌预览延后到 UI 包处理。
|
||||||
+2
-2
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md
|
||||||
2026-07-30-generated-third-party-notices.md: 58e3fd5007a16d6391956f423d1ec91e851df295
|
2026-07-30-generated-third-party-notices.md: d13eef8412ecd5c8387a7239b812c835671b6bce
|
||||||
2026-07-30-generated-third-party-notices.zh.md: 834ec4ebc349bdee37b0873109de566223bb7d48
|
2026-07-30-generated-third-party-notices.zh.md: 0af6a6bcd665197acd6a0b5e9e6f51940a0267e7
|
||||||
@@ -20,9 +20,9 @@ One trigger gap is accepted rather than worked around: lefthook inspects only fi
|
|||||||
|
|
||||||
The file discloses **direct** dependencies by default. The complete npm closure with pinned versions already lives in `pnpm-lock.yaml` (`pnpm licenses list` renders it) and the Python closure in `python/sdk/uv.lock`; re-materializing either as prose would be a second, worse copy. The one explicit transitive disclosure is the official Claude platform payload set declared by `@anthropic-ai/claude-agent-sdk` through `optionalDependencies`, because those packages carry the distributed Claude Code executable rather than ordinary library implementation detail.
|
The file discloses **direct** dependencies by default. The complete npm closure with pinned versions already lives in `pnpm-lock.yaml` (`pnpm licenses list` renders it) and the Python closure in `python/sdk/uv.lock`; re-materializing either as prose would be a second, worse copy. The one explicit transitive disclosure is the official Claude platform payload set declared by `@anthropic-ai/claude-agent-sdk` through `optionalDependencies`, because those packages carry the distributed Claude Code executable rather than ordinary library implementation detail.
|
||||||
|
|
||||||
**Tiering is by declaring area, not by manifest section.** A package is a runtime dependency when any manifest outside `DEV_ONLY_AREAS` — the root manifest, `packages/support/`, `packages/client/test-runtime/`, `website/`, `examples/`, `native/` — names it under `dependencies` or `optionalDependencies`. Section names alone are wrong in both directions: a test-support package declares `vitest` under `dependencies` without shipping it, and the `bin/dsh` launcher execs through `tsx`, which no manifest declares as a runtime dependency at all (the generator marks it runtime explicitly).
|
**Tiering is by declaring area, not by manifest section.** A package is a runtime dependency when any manifest outside `DEV_ONLY_AREAS` — the root manifest, `packages/support/`, `packages/client/test-runtime/`, `website/`, `examples/`, `native/` — names it under `dependencies` or `optionalDependencies`. Section names alone are wrong in both directions: a test-support package declares `vitest` under `dependencies` without shipping it, and the root source-run scripts execute through `tsx`, which no manifest declares as a runtime dependency at all (the generator marks it runtime explicitly).
|
||||||
|
|
||||||
The runtime tier deliberately covers **every mountable plugin**, not just what the CLI, Web UI, and Python runtime load by default. `scripts/install.sh` installs the repository itself, so a user's `cordis.yml` can mount any plugin package; `@modelcontextprotocol/sdk` and the OpenTelemetry packages reach real users even though no default assembly imports them. Under-disclosure is the costly direction for a legal notice.
|
The runtime tier deliberately covers **every mountable plugin**, not just what the CLI, Web UI, and Python runtime load by default. Source execution can mount any plugin package from a user's `cordis.yml`; `@modelcontextprotocol/sdk` and the OpenTelemetry packages therefore reach real users even though no default assembly imports them. Under-disclosure is the costly direction for a legal notice.
|
||||||
|
|
||||||
The manifest set is derived from the `packages:` members the root `pnpm-workspace.yaml` declares, including the Landlock workspace and its public packages, so a new member area is read the day it is declared rather than the day someone remembers to extend a list. License and repository metadata come from the root workspace's installed pnpm store and package-local link farms, so the generator requires an installed tree and fails loud when a package resolves to neither, rather than emitting an empty cell. `OVERRIDES` carries the packages whose published manifest cannot answer — Rust-built npm bins that omit `license`, and the `modelcontextprotocol/servers` packages whose repository is mid MIT→Apache-2.0 relicensing, so their effective terms are per-contribution. A runtime dependency whose license is not on the permissive list is a hard error: shipping copyleft is a distribution decision, not something a regenerated table may absorb silently. Vendored packages are cross-checked against `vendor/README.md` and rejected if any is not MIT, and `pnpm-workspace.yaml`'s `patchedDependencies` are listed under the runtime table because pnpm applies those patches at install time — shipped artifacts carry modified copies of `@earendil-works/pi-tui` and `node-pty`, and the patch files are the record of what changed.
|
The manifest set is derived from the `packages:` members the root `pnpm-workspace.yaml` declares, including the Landlock workspace and its public packages, so a new member area is read the day it is declared rather than the day someone remembers to extend a list. License and repository metadata come from the root workspace's installed pnpm store and package-local link farms, so the generator requires an installed tree and fails loud when a package resolves to neither, rather than emitting an empty cell. `OVERRIDES` carries the packages whose published manifest cannot answer — Rust-built npm bins that omit `license`, and the `modelcontextprotocol/servers` packages whose repository is mid MIT→Apache-2.0 relicensing, so their effective terms are per-contribution. A runtime dependency whose license is not on the permissive list is a hard error: shipping copyleft is a distribution decision, not something a regenerated table may absorb silently. Vendored packages are cross-checked against `vendor/README.md` and rejected if any is not MIT, and `pnpm-workspace.yaml`'s `patchedDependencies` are listed under the runtime table because pnpm applies those patches at install time — shipped artifacts carry modified copies of `@earendil-works/pi-tui` and `node-pty`, and the patch files are the record of what changed.
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Status: implemented
|
|||||||
|
|
||||||
文件默认只披露**直接**依赖。完整的 npm 闭包连同锁定版本已记录在 `pnpm-lock.yaml`(`pnpm licenses list` 可渲染),Python 闭包记录在 `python/sdk/uv.lock`;再用散文誊一遍只会得到一份更差的副本。唯一明确披露的传递依赖,是 `@anthropic-ai/claude-agent-sdk` 通过 `optionalDependencies` 声明的官方 Claude 平台载荷集合,因为这些包承载随产品分发的 Claude Code 可执行文件,而非普通的库实现细节。
|
文件默认只披露**直接**依赖。完整的 npm 闭包连同锁定版本已记录在 `pnpm-lock.yaml`(`pnpm licenses list` 可渲染),Python 闭包记录在 `python/sdk/uv.lock`;再用散文誊一遍只会得到一份更差的副本。唯一明确披露的传递依赖,是 `@anthropic-ai/claude-agent-sdk` 通过 `optionalDependencies` 声明的官方 Claude 平台载荷集合,因为这些包承载随产品分发的 Claude Code 可执行文件,而非普通的库实现细节。
|
||||||
|
|
||||||
**分层依据是声明方所在区域,而非 manifest 字段名。** 只要 `DEV_ONLY_AREAS` 之外的任一 manifest——即根 manifest、`packages/support/`、`packages/client/test-runtime/`、`website/`、`examples/`、`native/` 之外——在 `dependencies` 或 `optionalDependencies` 里点名某个包,它就是运行时依赖。单看字段名在两个方向上都会出错:测试支撑包把 `vitest` 写在 `dependencies` 里却并不交付它;而 `bin/dsh` 启动器通过 `tsx` 执行,根本没有任何 manifest 把它声明为运行时依赖,只能由生成器显式标记。
|
**分层依据是声明方所在区域,而非 manifest 字段名。** 只要 `DEV_ONLY_AREAS` 之外的任一 manifest——即根 manifest、`packages/support/`、`packages/client/test-runtime/`、`website/`、`examples/`、`native/` 之外——在 `dependencies` 或 `optionalDependencies` 里点名某个包,它就是运行时依赖。单看字段名在两个方向上都会出错:测试支撑包把 `vitest` 写在 `dependencies` 里却并不交付它;而根目录的源码运行脚本通过 `tsx` 执行,根本没有任何 manifest 把它声明为运行时依赖,只能由生成器显式标记。
|
||||||
|
|
||||||
运行时层刻意覆盖**所有可挂载的插件**,而不止 CLI、Web UI 与 Python 运行时默认加载的那些。`scripts/install.sh` 安装的就是仓库本身,用户的 `cordis.yml` 可以挂载任何插件包;`@modelcontextprotocol/sdk` 与 OpenTelemetry 系列即使没有任何默认装配引入,也会触达真实用户。对法务披露而言,披露不足才是代价更高的那个方向。
|
运行时层刻意覆盖**所有可挂载的插件**,而不止 CLI、Web UI 与 Python 运行时默认加载的那些。从源码运行时,用户可以通过 `cordis.yml` 挂载任何插件包;因此,`@modelcontextprotocol/sdk` 与 OpenTelemetry 系列即使没有任何默认装配引入,也会触达真实用户。对法务披露而言,披露不足才是代价更高的那个方向。
|
||||||
|
|
||||||
manifest 集合由根 `pnpm-workspace.yaml` 声明的 `packages:` 成员派生,其中包括 Landlock 工作区及其公开包,因此新增成员区域在声明当天就会被读取,而不必等谁想起来去补一份列表。许可证与仓库地址取自根工作区已安装的 pnpm store 和包本地链接场;某个包两处都解析不到时直接失败,而不是留下空单元格。`OVERRIDES` 收录已发布 manifest 答不上来的包:用 Rust 构建、发布时省略 `license` 字段的 npm 可执行包,以及 `modelcontextprotocol/servers` 系列——该仓库正处在 MIT 向 Apache-2.0 的重新许可过程中,实际条款按贡献逐条而定。运行时依赖的许可证若不在宽松清单内即为硬失败:交付 copyleft 是一项分发决策,不该被一次重新生成悄悄吸收。被源码收编的包会与 `vendor/README.md` 交叉核对,出现非 MIT 即报错;`pnpm-workspace.yaml` 的 `patchedDependencies` 列入运行时表格,因为 pnpm 在安装期就会打上这些补丁——交付产物携带的是改动过的 `@earendil-works/pi-tui` 与 `node-pty`,补丁文件本身就是改动的完整记录。
|
manifest 集合由根 `pnpm-workspace.yaml` 声明的 `packages:` 成员派生,其中包括 Landlock 工作区及其公开包,因此新增成员区域在声明当天就会被读取,而不必等谁想起来去补一份列表。许可证与仓库地址取自根工作区已安装的 pnpm store 和包本地链接场;某个包两处都解析不到时直接失败,而不是留下空单元格。`OVERRIDES` 收录已发布 manifest 答不上来的包:用 Rust 构建、发布时省略 `license` 字段的 npm 可执行包,以及 `modelcontextprotocol/servers` 系列——该仓库正处在 MIT 向 Apache-2.0 的重新许可过程中,实际条款按贡献逐条而定。运行时依赖的许可证若不在宽松清单内即为硬失败:交付 copyleft 是一项分发决策,不该被一次重新生成悄悄吸收。被源码收编的包会与 `vendor/README.md` 交叉核对,出现非 MIT 即报错;`pnpm-workspace.yaml` 的 `patchedDependencies` 列入运行时表格,因为 pnpm 在安装期就会打上这些补丁——交付产物携带的是改动过的 `@earendil-works/pi-tui` 与 `node-pty`,补丁文件本身就是改动的完整记录。
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md
|
||||||
2026-08-08-remove-cli-demo.md: 31879e8284daf5f34af6731b09fce743f9eb5391
|
2026-08-08-remove-cli-demo.md: f8bc09825797f2165c14a75e5841d74b2e1bc1af
|
||||||
2026-08-08-remove-cli-demo.zh.md: d4dfef1ba8c4d27cd667e319519cfa1ace74baef
|
2026-08-08-remove-cli-demo.zh.md: 1044dd136888e33fc9935cf4e24cd3100b41b41f
|
||||||
@@ -12,7 +12,7 @@ The replay suites still need canonical session events to pin assembled backend b
|
|||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
Delete `@deepseek-ai/dsh-cli-demo` completely: its package, bin, parser, app plugin, output formats, tests, workspace references, generated-catalog entries, and active documentation. No alias or compatibility package remains. The root `demo:headless` script is retained only as a direct alias of `dsh --profile headless`; the product command owns final-text stdout, failure diagnostics on stderr, persistence, exit status, and shutdown.
|
Delete `@deepseek-ai/dsh-cli-demo` completely: its package, bin, parser, app plugin, output formats, tests, workspace references, generated-catalog entries, and active documentation. No alias or compatibility package remains. Source users invoke the product command through `pnpm dsh --profile headless`; it owns final-text stdout, failure diagnostics on stderr, persistence, exit status, and shutdown.
|
||||||
|
|
||||||
`examples/headless-agent` becomes an explicit test composition. Its Loader configs mount `@deepseek-ai/dsh-agent-spine-demo`, one root agent, JSONL persistence, and checkpoint policy as separate rows instead of hiding them behind an app bundle. The support-tier `@deepseek-ai/dsh-loader-smoke` package owns the shared direct-agent turn helper; unexported example-local drivers select their Loader configuration and render canonical events as JSONL. They are launched only by tests, have no bin, and do not define a supported product output format.
|
`examples/headless-agent` becomes an explicit test composition. Its Loader configs mount `@deepseek-ai/dsh-agent-spine-demo`, one root agent, JSONL persistence, and checkpoint policy as separate rows instead of hiding them behind an app bundle. The support-tier `@deepseek-ai/dsh-loader-smoke` package owns the shared direct-agent turn helper; unexported example-local drivers select their Loader configuration and render canonical events as JSONL. They are launched only by tests, have no bin, and do not define a supported product output format.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Status: implemented
|
|||||||
|
|
||||||
## 决策
|
## 决策
|
||||||
|
|
||||||
彻底删除 `@deepseek-ai/dsh-cli-demo`:包括它的包、bin、解析器、应用插件、输出格式、测试、workspace 引用、生成目录条目和现行文档。不保留别名或兼容包。根目录的 `demo:headless` 脚本仅作为 `dsh --profile headless` 的直接别名保留;stdout 上的最终文本、stderr 上的失败诊断、持久化、退出状态和关闭行为均由产品命令负责。
|
彻底删除 `@deepseek-ai/dsh-cli-demo`:包括它的包、bin、解析器、应用插件、输出格式、测试、workspace 引用、生成目录条目和现行文档。不保留别名或兼容包。源码用户通过 `pnpm dsh --profile headless` 调用产品命令;stdout 上的最终文本、stderr 上的失败诊断、持久化、退出状态和关闭行为均由该命令负责。
|
||||||
|
|
||||||
`examples/headless-agent` 成为显式测试组装。其 Loader 配置把 `@deepseek-ai/dsh-agent-spine-demo`、一个根 agent(智能体)、JSONL 持久化和检查点策略挂载为独立配置行,不再将其隐藏在应用组合包之后。支持层的 `@deepseek-ai/dsh-loader-smoke` 包负责共享的直接 agent 轮次 helper;未导出的示例本地 driver 选择各自的 Loader 配置,并将规范事件渲染为 JSONL。这些 driver 只由测试启动,不提供 bin,也不定义受支持的产品输出格式。
|
`examples/headless-agent` 成为显式测试组装。其 Loader 配置把 `@deepseek-ai/dsh-agent-spine-demo`、一个根 agent(智能体)、JSONL 持久化和检查点策略挂载为独立配置行,不再将其隐藏在应用组合包之后。支持层的 `@deepseek-ai/dsh-loader-smoke` 包负责共享的直接 agent 轮次 helper;未导出的示例本地 driver 选择各自的 Loader 配置,并将规范事件渲染为 JSONL。这些 driver 只由测试启动,不提供 bin,也不定义受支持的产品输出格式。
|
||||||
|
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
# 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 .agents/notes/implemented/simplification/2026-08-10-source-run-without-managed-installer.md
|
||||||
|
2026-08-10-source-run-without-managed-installer.md: ac506b72acab0dd6c92ce6111487d091b2bf4a73
|
||||||
|
2026-08-10-source-run-without-managed-installer.zh.md: 86e44a90a9e7b34ad37b6a4bfd3ad14de40868f5
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# Agent Note: Source run without a managed installer
|
||||||
|
|
||||||
|
Status: implemented
|
||||||
|
|
||||||
|
English | [中文](2026-08-10-source-run-without-managed-installer.zh.md)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
A repository-owned source installer can provide a stable launcher, isolated staging worktrees, atomic upgrades, rollback storage, and shared maintenance workflows for personal customizations. It also makes the repository responsible for a second lifecycle beside the package manager: host dependency installation, credential prompting, checkout adoption, symlink ownership, staging branch coordination, upgrade recovery, and continued compatibility between the installer and bundled maintenance skills.
|
||||||
|
|
||||||
|
That lifecycle is not required to run or develop DeepSeek Harness from a source checkout. Maintaining it expands the supported filesystem and Git state space without improving the repository-native execution path.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
The repository supports source execution through its root `pnpm` scripts. The `dsh` entry in `package.json` runs `pnpm run build`, then launches `apps/cli/src/bin.ts` through `node --import tsx/esm`; build output remains visible before the CLI output. The package script forwards arguments and inherits the caller's environment, including `NODE_USE_ENV_PROXY=1` when a supporting Node version must honor `HTTP_PROXY` and `HTTPS_PROXY`. Users select Web with `pnpm dsh web` and headless execution with `pnpm dsh --profile headless "task"`. The independent ACP example remains available through `pnpm run demo:acp`.
|
||||||
|
|
||||||
|
The repository does not distribute a source installer, an installer test suite, or skills that assume a managed `current` symlink and timestamped staging worktrees. Users own source checkout placement, Git updates, and any launcher they create outside the repository.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
**Keep the installer but document `pnpm run` as another path.** This retains the managed launcher and rollback capability but keeps both lifecycle contracts active, including the installer tests and staging-aware skills.
|
||||||
|
|
||||||
|
**Keep generic customization and upstream-publication skills.** Their safety rules can apply beyond the staging layout, but the shipped workflows form one coupled maintenance system: customization discovers the installed staging checkout, upgrade performs the cutover, and upstream publication is selected from those personal changes. General Git contribution guidance already belongs to repository instructions and does not require product-bundled skills.
|
||||||
|
|
||||||
|
**Replace the installer with a smaller launcher-link script.** This reduces setup behavior but still makes the repository responsible for host PATH mutation and launcher ownership. Source scripts provide the entry points without that state.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Source users invoke repository scripts rather than an installed `dsh` command. The repository provides no atomic upgrade cutover or preserved staging rollback checkout, and it does not automate the integration or upstream publication of personal source modifications. A future distribution mechanism must justify its ownership of installation and upgrade state, define recovery behavior, and add tests and user documentation without making the source-run path depend on it. Any future publication workflow must isolate one approved feature and obtain explicit approval before its first push and draft PR.
|
||||||
|
|
||||||
|
Verification covers repository-wide references to the removed entry points, documentation links, generated third-party-notice freshness, the build-first `package.json` command, and a source CLI smoke through the exact `node --import tsx/esm` runtime vector.
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# Agent Note: 无需托管安装器的源码运行
|
||||||
|
|
||||||
|
Status: implemented
|
||||||
|
|
||||||
|
[English](2026-08-10-source-run-without-managed-installer.md) | 中文
|
||||||
|
|
||||||
|
## 问题
|
||||||
|
|
||||||
|
仓库自带的源码安装器可以提供稳定的启动器、相互隔离的 staging worktree、原子升级、回滚存储,以及用于个人定制的共享维护工作流。与此同时,仓库还必须在包管理器之外负责第二套生命周期:安装宿主依赖、提示输入凭证、接管检出、管理符号链接归属、协调 staging 分支、处理升级恢复,以及持续保持安装器与随附维护 skill(技能)的兼容性。
|
||||||
|
|
||||||
|
从源码检出运行或开发 DeepSeek Harness 并不需要这套生命周期。维护它会扩大需要支持的文件系统和 Git 状态空间,却无法改进仓库原生的执行路径。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
仓库通过根目录的 `pnpm` 脚本支持从源码运行。`package.json` 中的 `dsh` 项先执行 `pnpm run build`,再通过 `node --import tsx/esm` 启动 `apps/cli/src/bin.ts`;构建输出会显示在 CLI(命令行界面)输出之前。该包脚本会转发参数并继承调用方环境;当支持环境代理的 Node 版本必须遵循 `HTTP_PROXY` 和 `HTTPS_PROXY` 时,调用方可设置 `NODE_USE_ENV_PROXY=1`。用户使用 `pnpm dsh web` 选择 Web,使用 `pnpm dsh --profile headless "task"` 选择无头执行。独立的 ACP(Agent Client Protocol)示例仍可通过 `pnpm run demo:acp` 运行。
|
||||||
|
|
||||||
|
仓库不分发源码安装器、安装器测试套件,也不分发依赖受管理的 `current` 符号链接和带时间戳 staging worktree 的 skill。源码检出的存放位置、Git 更新,以及用户在仓库外创建的任何启动器均由用户负责。
|
||||||
|
|
||||||
|
## 考虑过的备选方案
|
||||||
|
|
||||||
|
**保留安装器,但将 `pnpm run` 记作另一条路径。**这样可以保留受管理的启动器和回滚能力,但两套生命周期约定仍会同时生效,其中包括安装器测试和依赖 staging 布局的 skill。
|
||||||
|
|
||||||
|
**保留通用的定制与上游发布 skill。**其中的安全规则也能用于 staging 布局之外,但现有工作流共同构成了一套耦合的维护系统:定制工作流查找已安装的 staging 检出,升级工作流执行切换,上游发布工作流则从这些个人修改中选择发布内容。通用 Git 贡献指南已经属于仓库指令,无需以产品随附 skill 的形式提供。
|
||||||
|
|
||||||
|
**用更小的启动器链接脚本替换安装器。**这样可以简化设置过程,但仓库仍需负责修改宿主 PATH 和管理启动器归属。源码脚本无需引入这类状态即可提供入口点。
|
||||||
|
|
||||||
|
## 影响
|
||||||
|
|
||||||
|
源码用户通过仓库脚本运行程序,而非使用已安装的 `dsh` 命令。仓库不提供原子升级切换,也不保留 staging 回滚检出;仓库同样不会自动集成个人源码修改或将其发布到上游。未来的分发机制必须说明为何应由其管理安装和升级状态,定义恢复行为,并补充测试与用户文档,同时不得让源码运行路径依赖该机制。未来任何发布工作流都必须隔离出一项获批功能,并在首次推送和创建草稿 PR(Pull Request)前取得明确批准。
|
||||||
|
|
||||||
|
验证范围包括仓库内对已移除入口点的所有引用、文档链接、生成的第三方声明文件的新鲜度、`package.json` 中的先构建后启动命令,以及通过准确的 `node --import tsx/esm` 运行方式对源码 CLI 进行的冒烟测试。
|
||||||
@@ -73,7 +73,7 @@ pnpm run check:windows-wine # ONLY when diagnosing a known Windows failure (nee
|
|||||||
pnpm run hygiene # knip + publint + workspace constraints + NodeNext consumer check
|
pnpm run hygiene # knip + publint + workspace constraints + NodeNext consumer check
|
||||||
pnpm run doc-sync # all documentation gates; leaf list in scripts/run-gates.ts
|
pnpm run doc-sync # all documentation gates; leaf list in scripts/run-gates.ts
|
||||||
pnpm run website:build # VitePress build (doubles as dead-link check)
|
pnpm run website:build # VitePress build (doubles as dead-link check)
|
||||||
pnpm run demo:headless "task" # one-shot agent (needs DEEPSEEK_API_KEY)
|
pnpm dsh --profile headless "task" # build, then run one task (needs DEEPSEEK_API_KEY)
|
||||||
pnpm run demo:cordis # the agent modifies its own runtime (needs key)
|
pnpm run demo:cordis # the agent modifies its own runtime (needs key)
|
||||||
pnpm run demo:acp # ACP automation server (needs DEEPSEEK_API_KEY)
|
pnpm run demo:acp # ACP automation server (needs DEEPSEEK_API_KEY)
|
||||||
```
|
```
|
||||||
|
|||||||
+2
-2
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write README.md
|
# pnpm run verify-translation-pairing --write README.md
|
||||||
README.md: 9b8944027572072e08a39bd9e482996f8128224c
|
README.md: b2d84672275a4ca996b6ea596c104abfaa52432a
|
||||||
README.zh.md: a1b2b6a36c4baac8a49b88a42c9f17c986287ff1
|
README.zh.md: 747a3c88bc129ad5dd24f5fa150a1661c4db0b11
|
||||||
@@ -12,41 +12,34 @@ DeepSeek Harness is under internal testing. Features and interfaces may change.
|
|||||||
|
|
||||||
The internal build uploads all Session Logs by default to help diagnose reported problems. Set `DSH_TELEMETRY_DISABLED=1` to disable telemetry. Send feedback through the internal WeChat group.
|
The internal build uploads all Session Logs by default to help diagnose reported problems. Set `DSH_TELEMETRY_DISABLED=1` to disable telemetry. Send feedback through the internal WeChat group.
|
||||||
|
|
||||||
## Install
|
## Run from source
|
||||||
|
|
||||||
Clone the repository, then run the installer:
|
Clone this repo, complete the [dependency and API-key setup](docs/user/guide/quickstart.md#step-1-install-and-configure-the-api-key), then run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone <repo-url>
|
pnpm dsh web
|
||||||
cd deepseek-harness
|
|
||||||
scripts/install.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer requires `git` and Node `^22.19 || >=24`, offers to install `pnpm` when it is missing, prompts for a DeepSeek API key, builds the required repository artifacts, and launches the Web UI.
|
|
||||||
|
|
||||||
The default active checkout is `~/.dsh/source/current`, and the launcher is linked into `~/.local/bin`. Re-run the installer to update. [`scripts/install.sh`](scripts/install.sh) owns alternate locations, update mechanics, and recovery options.
|
|
||||||
|
|
||||||
## Use DeepSeek Harness
|
## Use DeepSeek Harness
|
||||||
|
|
||||||
### Web UI
|
### Web UI
|
||||||
|
|
||||||
For the recommended local interface, choose Web UI when the installer finishes. To start it later, or after updating the active checkout, build the repository and run:
|
Start the recommended local interface from the repository root:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
(cd ~/.dsh/source/current && pnpm run build)
|
pnpm dsh web
|
||||||
dsh web
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The path above is the installer's default. If you set `DSH_SOURCE` or `DSH_CURRENT`, or reused an existing checkout, replace `~/.dsh/source/current` with that checkout path; see [`scripts/install.sh`](scripts/install.sh) for details. The Web UI is served at `http://127.0.0.1:3080` by default.
|
The command builds the repository before starting the Web UI, which is served at `http://127.0.0.1:3080` by default.
|
||||||
|
|
||||||
### Profiles
|
### Profiles
|
||||||
|
|
||||||
`dsh` boots profiles — ordered stacks of plugin-bundle patch layers under your own overrides in `$DSH_HOME/profiles/<name>`:
|
The source CLI boots profiles — ordered stacks of plugin-bundle patch layers under your own overrides in `$DSH_HOME/profiles/<name>`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsh --profile web # the browser UI (same as: dsh web)
|
pnpm dsh --profile web # the browser UI
|
||||||
dsh plugin --profile tui add <package> # install a plugin into a custom profile
|
pnpm dsh plugin --profile tui add <package> # install a plugin into a custom profile
|
||||||
dsh --profile tui # boot it
|
pnpm dsh --profile tui # boot it
|
||||||
```
|
```
|
||||||
|
|
||||||
The [CLI reference](apps/cli/README.md#profiles) describes profile layout, layer semantics, and config dump commands.
|
The [CLI reference](apps/cli/README.md#profiles) describes profile layout, layer semantics, and config dump commands.
|
||||||
@@ -56,7 +49,7 @@ The [CLI reference](apps/cli/README.md#profiles) describes profile layout, layer
|
|||||||
Run one task, print the final answer, and exit:
|
Run one task, print the final answer, and exit:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsh --profile headless "summarize this workspace"
|
pnpm dsh --profile headless "summarize this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Automation and SDKs
|
### Automation and SDKs
|
||||||
|
|||||||
+11
-18
@@ -12,41 +12,34 @@ DeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化
|
|||||||
|
|
||||||
为帮助诊断上报的问题,内测版本默认上传所有会话日志。设置 `DSH_TELEMETRY_DISABLED=1` 可关闭遥测。请通过内部企业微信群反馈问题和建议。
|
为帮助诊断上报的问题,内测版本默认上传所有会话日志。设置 `DSH_TELEMETRY_DISABLED=1` 可关闭遥测。请通过内部企业微信群反馈问题和建议。
|
||||||
|
|
||||||
## 安装
|
## 从源码运行
|
||||||
|
|
||||||
克隆仓库,然后运行安装器:
|
克隆本仓库,完成[依赖安装和 API 密钥配置](docs/user/guide/quickstart.md#step-1-install-and-configure-the-api-key),然后运行:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone <repo-url>
|
pnpm dsh web
|
||||||
cd deepseek-harness
|
|
||||||
scripts/install.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
安装器要求系统已安装 `git` 和 Node `^22.19 || >=24`,缺少 `pnpm` 时可代为安装,并会提示输入 DeepSeek API 密钥,然后构建所需的仓库产物并启动 Web UI。
|
|
||||||
|
|
||||||
默认生效的检出位于 `~/.dsh/source/current`,启动器链接到 `~/.local/bin`。再次运行安装器即可更新。其他位置、更新机制和恢复选项由 [`scripts/install.sh`](scripts/install.sh) 负责。
|
|
||||||
|
|
||||||
## 使用 DeepSeek Harness
|
## 使用 DeepSeek Harness
|
||||||
|
|
||||||
### Web UI
|
### Web UI
|
||||||
|
|
||||||
推荐在本地使用 Web UI;安装结束时,选择 Web UI 即可。以后需要启动时,或更新当前生效的检出后,请构建仓库并运行:
|
请从仓库根目录启动推荐的本地界面:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
(cd ~/.dsh/source/current && pnpm run build)
|
pnpm dsh web
|
||||||
dsh web
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上述路径是安装器的默认位置。如果你设置过 `DSH_SOURCE` 或 `DSH_CURRENT`,或者复用了已有检出,请把 `~/.dsh/source/current` 换成该检出路径;详情见 [`scripts/install.sh`](scripts/install.sh)。Web UI 默认通过 `http://127.0.0.1:3080` 提供服务。
|
该命令会先构建仓库,再启动 Web UI。Web UI 默认通过 `http://127.0.0.1:3080` 提供服务。
|
||||||
|
|
||||||
### Profile
|
### Profile
|
||||||
|
|
||||||
`dsh` 启动 profile:按序叠放的插件组合包 patch 层,之上再叠加你在 `$DSH_HOME/profiles/<name>` 中的自有覆盖层:
|
源码 CLI(命令行界面)会启动 profile:按序叠放的插件组合包 patch 层,之上再叠加你在 `$DSH_HOME/profiles/<name>` 中的自有覆盖层:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsh --profile web # the browser UI (same as: dsh web)
|
pnpm dsh --profile web # the browser UI
|
||||||
dsh plugin --profile tui add <package> # install a plugin into a custom profile
|
pnpm dsh plugin --profile tui add <package> # install a plugin into a custom profile
|
||||||
dsh --profile tui # boot it
|
pnpm dsh --profile tui # boot it
|
||||||
```
|
```
|
||||||
|
|
||||||
profile 布局、层语义与配置输出命令详见 [CLI(命令行界面)参考](apps/cli/README.md#profiles)。
|
profile 布局、层语义与配置输出命令详见 [CLI(命令行界面)参考](apps/cli/README.md#profiles)。
|
||||||
@@ -56,7 +49,7 @@ profile 布局、层语义与配置输出命令详见 [CLI(命令行界面)
|
|||||||
运行一项任务,打印最终答案后退出:
|
运行一项任务,打印最终答案后退出:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsh --profile headless "summarize this workspace"
|
pnpm dsh --profile headless "summarize this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 自动化与 SDK
|
### 自动化与 SDK
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ The Cordis framework and its foundation libraries are source-vendored into this
|
|||||||
|
|
||||||
## Runtime npm dependencies
|
## Runtime npm dependencies
|
||||||
|
|
||||||
External packages that a workspace package resolves at runtime. `scripts/install.sh` installs this repository itself, so the tier covers every plugin a user can mount from `cordis.yml` — not only what the `dsh` CLI, Web UI, and Python SDK runtime load by default.
|
External packages that a workspace package resolves at runtime. The tier covers every plugin a user can mount from `cordis.yml` — not only what the `dsh` CLI, Web UI, and Python SDK runtime load by default.
|
||||||
|
|
||||||
| Package | License |
|
| Package | License |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write apps/cli/README.md
|
# pnpm run verify-translation-pairing --write apps/cli/README.md
|
||||||
README.md: 4fae5338a89ce12c2620e123530acf883ae9efff
|
README.md: 98a856261bc632c97f350db8fc7bb0b10c22235d
|
||||||
README.zh.md: a2d086b8ff12fb07f2446fc4162de09739bcdeab
|
README.zh.md: 283e54138e24202ed6b88d1d309538c58cd66b3e
|
||||||
+2
-2
@@ -31,8 +31,8 @@ dsh --help # the launcher's own help
|
|||||||
|
|
||||||
A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list) and a `cordis.patch.yml` (the user's own patch layer, hot-reloaded on long-lived surfaces). The tree composes over an empty root: each bundle's patch in `dsh.profile.bundles` order, then the profile's `cordis.patch.yml`, then the home-level `$DSH_HOME/cordis.patch.yml`, then `--patch` overlays. Bundles named in `dsh.profile.bundles` resolve from the dsh installation first (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins. Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it.
|
A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list) and a `cordis.patch.yml` (the user's own patch layer, hot-reloaded on long-lived surfaces). The tree composes over an empty root: each bundle's patch in `dsh.profile.bundles` order, then the profile's `cordis.patch.yml`, then the home-level `$DSH_HOME/cordis.patch.yml`, then `--patch` overlays. Bundles named in `dsh.profile.bundles` resolve from the dsh installation first (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins. Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it.
|
||||||
|
|
||||||
The [CLI behavior reference](reference/README.md) owns exact layer precedence, flags, shutdown behavior, deployment defaults, and the source launcher.
|
The [CLI behavior reference](reference/README.md) owns exact layer precedence, flags, shutdown behavior, deployment defaults, and source execution.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Production runs require built package and frontend artifacts. From a checkout, `pnpm run dsh` runs the TypeScript entry and forwards arguments; the [source-launcher reference](reference/README.md#source-launcher) describes the PATH symlink and module-resolution contract.
|
Production runs require built package and frontend artifacts. From the repository root, `pnpm dsh <args...>` builds those artifacts, runs the TypeScript entry, and forwards every argument; the [source-execution reference](reference/README.md#source-execution) owns the module-resolution contract.
|
||||||
@@ -31,8 +31,8 @@ dsh --help # the launcher's own help
|
|||||||
|
|
||||||
profile 目录包含一个 `package.json`(树外插件依赖,加上 profile manifest(元数据清单)`dsh.profile` 及其有序的 `bundles` 列表)和一个 `cordis.patch.yml`(用户自己的 patch 层,在长期运行的 surface 上热重载)。配置树在空根之上组合:先按 `dsh.profile.bundles` 顺序应用各组合包的 patch,然后是 profile 的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml`,然后是 `--patch` overlay。`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自己的 `node_modules` 解析;pnpm 把树外插件安装在后者。使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。
|
profile 目录包含一个 `package.json`(树外插件依赖,加上 profile manifest(元数据清单)`dsh.profile` 及其有序的 `bundles` 列表)和一个 `cordis.patch.yml`(用户自己的 patch 层,在长期运行的 surface 上热重载)。配置树在空根之上组合:先按 `dsh.profile.bundles` 顺序应用各组合包的 patch,然后是 profile 的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml`,然后是 `--patch` overlay。`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自己的 `node_modules` 解析;pnpm 把树外插件安装在后者。使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。
|
||||||
|
|
||||||
[CLI(命令行界面)行为参考](reference/README.md)负责确切的层优先级、flag、关闭行为、部署默认值和源码启动器。
|
[CLI(命令行界面)行为参考](reference/README.md)负责确切的层优先级、flag、关闭行为、部署默认值和源码执行。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
生产运行需要已构建的包与前端产物。在 checkout 中,`pnpm run dsh` 会运行 TypeScript 入口并转发参数;[源码启动器参考](reference/README.md#source-launcher)说明 PATH 符号链接和模块解析契约。
|
生产运行需要已构建的包与前端产物。从仓库根目录运行 `pnpm dsh <args...>` 会先构建这些产物,再运行 TypeScript 入口并转发所有参数;模块解析约定由[源码执行参考](reference/README.md#source-execution)负责。
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write apps/cli/reference/README.md
|
# pnpm run verify-translation-pairing --write apps/cli/reference/README.md
|
||||||
README.md: fd0647347312051a1814a5e3464b34032ae70dfc
|
README.md: cef687dc392f97886ea18b162e8f668a44ce2284
|
||||||
README.zh.md: afe4b9ba5651e962288ffebbe7c095ad20bcc617
|
README.zh.md: f6721ec256d404e2b602fb2ed921b41c03633a82
|
||||||
@@ -79,12 +79,6 @@ Session events stream as OTLP/HTTP logs by default. `DSH_TELEMETRY_OTLP_URL` sel
|
|||||||
|
|
||||||
Install external plugin bundles through `dsh plugin --profile <name> add <package-or-git-spec>`. The installed package owns its dependencies and contributes its declared `cordis.patch.yml` layer. The CLI also ships `@deepseek-ai/dsh-mcp-client` as a dependency for patch layers, but no MCP server is enabled by default because each server command is trusted executable code outside the agent sandbox.
|
Install external plugin bundles through `dsh plugin --profile <name> add <package-or-git-spec>`. The installed package owns its dependencies and contributes its declared `cordis.patch.yml` layer. The CLI also ships `@deepseek-ai/dsh-mcp-client` as a dependency for patch layers, but no MCP server is enabled by default because each server command is trusted executable code outside the agent sandbox.
|
||||||
|
|
||||||
## Source launcher
|
## Source execution
|
||||||
|
|
||||||
Link the source-running launcher onto PATH:
|
From the repository root, use `pnpm dsh <args...>`. The `package.json` script runs the complete repository build, launches `apps/cli/src/bin.ts` with `node --import tsx/esm`, and forwards every argument. Build output appears before CLI output. The process inherits the launch environment; set `NODE_USE_ENV_PROXY=1` when a supporting Node version must honor `HTTP_PROXY` and `HTTPS_PROXY`. The installed form launches the built `apps/cli/lib/bin.js` without rebuilding the repository.
|
||||||
|
|
||||||
```sh
|
|
||||||
ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh
|
|
||||||
```
|
|
||||||
|
|
||||||
It resolves the checkout through its real path and launches `apps/cli/src/bin.ts` with `node --import tsx/esm`. `TSX_TSCONFIG_PATH` is pinned to the checkout root, so workspace package resolution is independent of the invoking directory. `pnpm run dsh` uses the same entry and forwards arguments. The built form is `apps/cli/lib/bin.js` after `pnpm run build`.
|
|
||||||
@@ -79,12 +79,6 @@ dsh web --help
|
|||||||
|
|
||||||
通过 `dsh plugin --profile <name> add <package-or-git-spec>` 安装外部插件组合包。安装的包拥有其依赖,并贡献其声明的 `cordis.patch.yml` 层。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。
|
通过 `dsh plugin --profile <name> add <package-or-git-spec>` 安装外部插件组合包。安装的包拥有其依赖,并贡献其声明的 `cordis.patch.yml` 层。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。
|
||||||
|
|
||||||
## 源码启动器
|
## 源码执行
|
||||||
|
|
||||||
把源码运行启动器链接到 PATH:
|
请从仓库根目录使用 `pnpm dsh <args...>`。`package.json` 中的脚本会完成整个仓库的构建,通过 `node --import tsx/esm` 启动 `apps/cli/src/bin.ts`,并转发所有参数。构建输出会显示在 CLI 输出之前。该进程会继承启动环境;当支持环境代理的 Node 版本必须遵循 `HTTP_PROXY` 和 `HTTPS_PROXY` 时,请设置 `NODE_USE_ENV_PROXY=1`。安装形式会直接启动构建后的 `apps/cli/lib/bin.js`,不会重新构建仓库。
|
||||||
|
|
||||||
```sh
|
|
||||||
ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh
|
|
||||||
```
|
|
||||||
|
|
||||||
它通过 real path 解析 checkout,并使用 `node --import tsx/esm` 启动 `apps/cli/src/bin.ts`。`TSX_TSCONFIG_PATH` 固定到 checkout 根目录,因此 workspace 包解析不依赖调用目录。`pnpm run dsh` 使用同一入口并转发参数。运行 `pnpm run build` 后,构建形式为 `apps/cli/lib/bin.js`。
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import { chmodSync, copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
||||||
import { mkdtemp, rm } from 'node:fs/promises'
|
|
||||||
import { tmpdir } from 'node:os'
|
|
||||||
import { join } from 'node:path'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
import { execa } from 'execa'
|
|
||||||
import { afterEach, describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
const installer = fileURLToPath(new URL('../../../scripts/install.sh', import.meta.url))
|
|
||||||
const fixtures: string[] = []
|
|
||||||
|
|
||||||
const PTY_DRIVER = String.raw`
|
|
||||||
import errno, json, os, pty, select, signal, sys, time
|
|
||||||
script, cwd, env_json, actions_json = sys.argv[1:]
|
|
||||||
env = os.environ.copy()
|
|
||||||
env.update(json.loads(env_json))
|
|
||||||
actions = json.loads(actions_json)
|
|
||||||
pid, fd = pty.fork()
|
|
||||||
if pid == 0:
|
|
||||||
os.chdir(cwd)
|
|
||||||
os.execvpe("sh", ["sh", script], env)
|
|
||||||
|
|
||||||
output = bytearray()
|
|
||||||
action_index = 0
|
|
||||||
deadline = time.monotonic() + 15
|
|
||||||
status = None
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
ready, _, _ = select.select([fd], [], [], 0.05)
|
|
||||||
if ready:
|
|
||||||
try:
|
|
||||||
chunk = os.read(fd, 65536)
|
|
||||||
except OSError as error:
|
|
||||||
if error.errno != errno.EIO:
|
|
||||||
raise
|
|
||||||
chunk = b""
|
|
||||||
output.extend(chunk)
|
|
||||||
while action_index < len(actions) and actions[action_index]["waitFor"].encode() in output:
|
|
||||||
os.write(fd, actions[action_index]["send"].encode())
|
|
||||||
action_index += 1
|
|
||||||
waited, candidate = os.waitpid(pid, os.WNOHANG)
|
|
||||||
if waited == pid:
|
|
||||||
status = candidate
|
|
||||||
break
|
|
||||||
|
|
||||||
if status is None:
|
|
||||||
os.kill(pid, signal.SIGKILL)
|
|
||||||
_, status = os.waitpid(pid, 0)
|
|
||||||
sys.stdout.buffer.write(output)
|
|
||||||
if action_index != len(actions):
|
|
||||||
sys.stderr.write(f"completed {action_index}/{len(actions)} PTY actions\n")
|
|
||||||
sys.exit(124)
|
|
||||||
sys.exit(os.waitstatus_to_exitcode(status))
|
|
||||||
`
|
|
||||||
|
|
||||||
interface Action {
|
|
||||||
readonly waitFor: string
|
|
||||||
readonly send: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Fixture {
|
|
||||||
readonly binDirectory: string
|
|
||||||
readonly launchLog: string
|
|
||||||
readonly pnpmLog: string
|
|
||||||
readonly root: string
|
|
||||||
readonly script: string
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await Promise.all(fixtures.splice(0).map(async (fixture) => { await rm(fixture, { force: true, recursive: true }) }))
|
|
||||||
})
|
|
||||||
|
|
||||||
function executable(path: string, content: string): void {
|
|
||||||
writeFileSync(path, content)
|
|
||||||
chmodSync(path, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createFixture(): Promise<Fixture> {
|
|
||||||
const root = await mkdtemp(join(tmpdir(), 'dsh-install-'))
|
|
||||||
fixtures.push(root)
|
|
||||||
const checkoutDirectory = join(root, 'checkout')
|
|
||||||
const scriptsDirectory = join(checkoutDirectory, 'scripts')
|
|
||||||
const sourceBinDirectory = join(checkoutDirectory, 'bin')
|
|
||||||
const fakeBinDirectory = join(root, 'fake-bin')
|
|
||||||
const binDirectory = join(root, 'path-bin')
|
|
||||||
for (const directory of [scriptsDirectory, sourceBinDirectory, fakeBinDirectory, binDirectory, join(root, 'home/.dsh')]) {
|
|
||||||
mkdirSync(directory, { recursive: true })
|
|
||||||
}
|
|
||||||
const script = join(scriptsDirectory, 'install.sh')
|
|
||||||
copyFileSync(installer, script)
|
|
||||||
const launchLog = join(root, 'launch.log')
|
|
||||||
const pnpmLog = join(root, 'pnpm.log')
|
|
||||||
executable(join(sourceBinDirectory, 'dsh'), '#!/bin/sh\nprintf \'%s\\n\' "$*" >"$DSH_TEST_LAUNCH_LOG"\n')
|
|
||||||
executable(join(fakeBinDirectory, 'pnpm'), `#!/bin/sh
|
|
||||||
if [ "\${1:-}" = --version ]; then printf '11.7.0\\n'; exit 0; fi
|
|
||||||
printf '%s\\n' "$*" >>"$DSH_TEST_PNPM_LOG"
|
|
||||||
`)
|
|
||||||
await execa('git', ['init', '-q'], { cwd: checkoutDirectory })
|
|
||||||
await execa('git', ['add', 'bin/dsh', 'scripts/install.sh'], { cwd: checkoutDirectory })
|
|
||||||
await execa('git', [
|
|
||||||
'-c', 'user.name=dsh-test',
|
|
||||||
'-c', 'user.email=dsh-test@example.invalid',
|
|
||||||
'commit', '-qm', 'fixture',
|
|
||||||
], { cwd: checkoutDirectory })
|
|
||||||
writeFileSync(join(root, 'home/.dsh/.env'), 'DEEPSEEK_API_KEY=test\n')
|
|
||||||
return { binDirectory, launchLog, pnpmLog, root, script }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runInstaller(fixture: Fixture, actions: readonly Action[]): Promise<string> {
|
|
||||||
const result = await execa('python3', [
|
|
||||||
'-c',
|
|
||||||
PTY_DRIVER,
|
|
||||||
fixture.script,
|
|
||||||
fixture.root,
|
|
||||||
JSON.stringify({
|
|
||||||
DSH_BIN_DIR: fixture.binDirectory,
|
|
||||||
DSH_HOME: join(fixture.root, 'home/.dsh'),
|
|
||||||
DSH_TEST_LAUNCH_LOG: fixture.launchLog,
|
|
||||||
DSH_TEST_PNPM_LOG: fixture.pnpmLog,
|
|
||||||
HOME: join(fixture.root, 'home'),
|
|
||||||
PATH: `${join(fixture.root, 'fake-bin')}:${fixture.binDirectory}:${process.env.PATH ?? ''}`,
|
|
||||||
}),
|
|
||||||
JSON.stringify(actions),
|
|
||||||
], { reject: false, stripFinalNewline: false, timeout: 20_000 })
|
|
||||||
expect(result.exitCode, result.stderr).toBe(0)
|
|
||||||
return result.stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
describe.runIf(process.platform !== 'win32')('one-line installer launch', { timeout: 25_000 }, () => {
|
|
||||||
it('builds and launches the Web UI', async () => {
|
|
||||||
const fixture = await createFixture()
|
|
||||||
|
|
||||||
const output = await runInstaller(fixture, [
|
|
||||||
{ waitFor: 'Replace it?', send: '\n' },
|
|
||||||
])
|
|
||||||
|
|
||||||
expect(output).toContain('launching Web UI')
|
|
||||||
expect(readFileSync(fixture.pnpmLog, 'utf8')).toBe('install\nrun build\n')
|
|
||||||
expect(readFileSync(fixture.launchLog, 'utf8')).toBe('web\n')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { execa } from 'execa'
|
import { execa } from 'execa'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keyless smoke for the SOURCE `dsh` launcher: run `apps/cli/src/bin.ts`
|
* Keyless smoke for SOURCE `dsh` execution: run `apps/cli/src/bin.ts`
|
||||||
* with the exact production launch vector (`node --import tsx/esm`, the same
|
* with the exact production runtime vector (`node --import tsx/esm`, the
|
||||||
* executable and arguments as `bin/dsh` and the root `dsh`/`demo:web` scripts) and assert the
|
* vector the root `dsh` script invokes after building) and assert the
|
||||||
* required-config diagnostic. The Node compatibility matrix runs this
|
* required-config diagnostic. The Node compatibility matrix runs this
|
||||||
* WHOLE file, so a Node release changing module hooks or TypeScript handling
|
* WHOLE file, so a Node release changing module hooks or TypeScript handling
|
||||||
* breaks this gate instead of every developer's `pnpm dsh`; the built-bin
|
* breaks this gate instead of every developer's `pnpm dsh`; the built-bin
|
||||||
@@ -16,6 +17,13 @@ const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
|
|||||||
const dshSourceBin = 'apps/cli/src/bin.ts'
|
const dshSourceBin = 'apps/cli/src/bin.ts'
|
||||||
|
|
||||||
describe('dsh SOURCE launcher (node --import tsx/esm)', () => {
|
describe('dsh SOURCE launcher (node --import tsx/esm)', () => {
|
||||||
|
it('builds before launching the source CLI', async () => {
|
||||||
|
const rootPackage = JSON.parse(await readFile(new URL('../../../package.json', import.meta.url), 'utf8')) as {
|
||||||
|
readonly scripts?: Record<string, string>
|
||||||
|
}
|
||||||
|
expect(rootPackage.scripts?.dsh).toBe('pnpm run build && node --import tsx/esm apps/cli/src/bin.ts')
|
||||||
|
})
|
||||||
|
|
||||||
it('boots the source entry and requires a profile', async () => {
|
it('boots the source entry and requires a profile', async () => {
|
||||||
const result = await execa(process.execPath, ['--import', 'tsx/esm', dshSourceBin], {
|
const result = await execa(process.execPath, ['--import', 'tsx/esm', dshSourceBin], {
|
||||||
cwd: repoRoot,
|
cwd: repoRoot,
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ describe('the shipped Web composition', () => {
|
|||||||
// depend on ripgrep being present on the machine.
|
// depend on ripgrep being present on the machine.
|
||||||
expect(toolNames(ctx, handle.agent).filter(name => name !== 'glob' && name !== 'grep')).toEqual([
|
expect(toolNames(ctx, handle.agent).filter(name => name !== 'glob' && name !== 'grep')).toEqual([
|
||||||
'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
|
'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
|
||||||
'get_goal', 'interrupt_agent', 'list_agents', 'ralph', 'read', 'send_message', 'skill',
|
'get_goal', 'interrupt_agent', 'list_agents', 'ralph', 'read', 'read_image', 'send_message', 'skill',
|
||||||
'subagent', 'subagent_fork', 'task_kill',
|
'subagent', 'subagent_fork', 'task_kill',
|
||||||
'task_list', 'task_output', 'todo_write', 'update_goal', 'web_search',
|
'task_list', 'task_output', 'todo_write', 'update_goal', 'web_search',
|
||||||
'workflow', 'write',
|
'workflow', 'write',
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ describe('web e2e: settled Markdown math rendering', () => {
|
|||||||
await expect.poll(() => page.locator('.katex').count(), { timeout: 10_000 }).toBe(6)
|
await expect.poll(() => page.locator('.katex').count(), { timeout: 10_000 }).toBe(6)
|
||||||
await expect.poll(() => page.locator('.katex-display').count(), { timeout: 10_000 }).toBe(2)
|
await expect.poll(() => page.locator('.katex-display').count(), { timeout: 10_000 }).toBe(2)
|
||||||
expect(await page.locator('.katex-error').count()).toBe(0)
|
expect(await page.locator('.katex-error').count()).toBe(0)
|
||||||
|
await expect.poll(
|
||||||
|
() => page.getByText('Input 0 tok · Output 0 tok', { exact: false }).count(),
|
||||||
|
{ timeout: 10_000 },
|
||||||
|
).toBe(1)
|
||||||
|
|
||||||
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
||||||
.split(SEED_ID).join('{{seededId}}')
|
.split(SEED_ID).join('{{seededId}}')
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const EXPECTED_TOOLS = [
|
|||||||
'list_agents',
|
'list_agents',
|
||||||
'ralph',
|
'ralph',
|
||||||
'read',
|
'read',
|
||||||
|
'read_image',
|
||||||
'send_message',
|
'send_message',
|
||||||
'skill',
|
'skill',
|
||||||
'subagent',
|
'subagent',
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY || notReady.length > 0)('web smoke
|
|||||||
requireDist()
|
requireDist()
|
||||||
sessionsDir = mkdtempSync(join(tmpdir(), 'dsh-web-w5-'))
|
sessionsDir = mkdtempSync(join(tmpdir(), 'dsh-web-w5-'))
|
||||||
const port = await probeFreePort()
|
const port = await probeFreePort()
|
||||||
// tsx boot mirrors demo:web — lib/ may be unbuilt in this worktree. Isolate
|
// tsx boot mirrors the runtime half of the root dsh script. Isolate
|
||||||
// the host-level Harness and shared-agent homes inside the temp world; tsx
|
// the host-level Harness and shared-agent homes inside the temp world; tsx
|
||||||
// also needs the repo's loader and tsconfig paths pointed at explicitly.
|
// also needs the repo's loader and tsconfig paths pointed at explicitly.
|
||||||
const tsxLoader = pathToFileURL(createRequire(join(REPO_ROOT, 'package.json')).resolve('tsx')).href
|
const tsxLoader = pathToFileURL(createRequire(join(REPO_ROOT, 'package.json')).resolve('tsx')).href
|
||||||
|
|||||||
@@ -99,6 +99,19 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal and click a row action, re-hovering if a projection update replaces
|
||||||
|
* the row before its hover-only button becomes visible.
|
||||||
|
*/
|
||||||
|
async function clickHoverAction(row: Locator, name: string): Promise<void> {
|
||||||
|
const button = row.getByRole('button', { name })
|
||||||
|
await expect.poll(async () => {
|
||||||
|
await row.hover()
|
||||||
|
return await button.isVisible()
|
||||||
|
}, { timeout: 10_000 }).toBe(true)
|
||||||
|
await button.click()
|
||||||
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
scaffold = await launchWebScaffold({})
|
scaffold = await launchWebScaffold({})
|
||||||
// Seed one cold session (Ungrouped bucket) for the flat view + hover card.
|
// Seed one cold session (Ungrouped bucket) for the flat view + hover card.
|
||||||
@@ -137,10 +150,8 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
|||||||
|
|
||||||
it('renames a workspace over the wire with a duplicate-name pre-check', async () => {
|
it('renames a workspace over the wire with a duplicate-name pre-check', async () => {
|
||||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-ws-rename'))
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-ws-rename'))
|
||||||
// The actions button is display:none until its row hovers — hover the
|
const alphaRow = page.locator('[role="treeitem"]').filter({ hasText: 'alpha-ws' }).first()
|
||||||
// group row first, then the revealed button becomes actionable.
|
await clickHoverAction(alphaRow, 'Workspace actions for alpha-ws')
|
||||||
await page.locator('[role="treeitem"]').filter({ hasText: 'alpha-ws' }).first().hover()
|
|
||||||
await page.getByRole('button', { name: 'Workspace actions for alpha-ws' }).click()
|
|
||||||
await page.getByRole('menuitem', { name: 'Rename' }).click()
|
await page.getByRole('menuitem', { name: 'Rename' }).click()
|
||||||
const dialog = page.getByRole('dialog', { name: 'Rename workspace' })
|
const dialog = page.getByRole('dialog', { name: 'Rename workspace' })
|
||||||
await dialog.waitFor({ timeout: 10_000 })
|
await dialog.waitFor({ timeout: 10_000 })
|
||||||
@@ -214,17 +225,19 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
|||||||
// The header row is wrapped by its HoverCard anchor span, so the section
|
// The header row is wrapped by its HoverCard anchor span, so the section
|
||||||
// is the nearest groupSection ancestor, not the immediate parent.
|
// is the nearest groupSection ancestor, not the immediate parent.
|
||||||
const groupSection = groupRow.locator('xpath=ancestor::*[contains(@class, "groupSection")][1]')
|
const groupSection = groupRow.locator('xpath=ancestor::*[contains(@class, "groupSection")][1]')
|
||||||
if (await groupSection.locator('[role="treeitem"]').count() < 2) await groupRow.click()
|
await expect.poll(async () => {
|
||||||
await expect.poll(
|
const count = await groupSection.locator('[role="treeitem"]').count()
|
||||||
() => groupSection.locator('[role="treeitem"]').count(),
|
if (count < 2 && await groupRow.getAttribute('aria-expanded') !== 'true') {
|
||||||
{ timeout: 10_000 },
|
await groupRow.click()
|
||||||
).toBeGreaterThanOrEqual(2)
|
await page.waitForTimeout(50)
|
||||||
|
}
|
||||||
|
return await groupSection.locator('[role="treeitem"]').count()
|
||||||
|
}, { timeout: 10_000 }).toBeGreaterThanOrEqual(2)
|
||||||
const seededRow = groupSection.locator('[role="treeitem"]').nth(1)
|
const seededRow = groupSection.locator('[role="treeitem"]').nth(1)
|
||||||
await seededRow.click()
|
await seededRow.click()
|
||||||
await expect.poll(() => seededRow.getAttribute('aria-selected'), { timeout: 10_000 }).toBe('true')
|
await expect.poll(() => seededRow.getAttribute('aria-selected'), { timeout: 10_000 }).toBe('true')
|
||||||
|
|
||||||
await groupRow.hover()
|
await clickHoverAction(groupRow, `Workspace actions for ${workspace.title}`)
|
||||||
await page.getByRole('button', { name: `Workspace actions for ${workspace.title}` }).click()
|
|
||||||
await page.getByRole('menuitem', { name: 'Delete workspace' }).click()
|
await page.getByRole('menuitem', { name: 'Delete workspace' }).click()
|
||||||
const dialog = page.getByRole('dialog', { name: 'Delete workspace' })
|
const dialog = page.getByRole('dialog', { name: 'Delete workspace' })
|
||||||
await dialog.waitFor({ timeout: 10_000 })
|
await dialog.waitFor({ timeout: 10_000 })
|
||||||
@@ -338,8 +351,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
|||||||
if (oldWorkspace === undefined) throw new Error('old same-name Workspace was not registered')
|
if (oldWorkspace === undefined) throw new Error('old same-name Workspace was not registered')
|
||||||
|
|
||||||
const oldRow = page.locator('[role="treeitem"]').filter({ hasText: title }).first()
|
const oldRow = page.locator('[role="treeitem"]').filter({ hasText: title }).first()
|
||||||
await oldRow.hover()
|
await clickHoverAction(oldRow, `Workspace actions for ${title}`)
|
||||||
await page.getByRole('button', { name: `Workspace actions for ${title}` }).click()
|
|
||||||
await page.getByRole('menuitem', { name: 'Delete workspace' }).click()
|
await page.getByRole('menuitem', { name: 'Delete workspace' }).click()
|
||||||
await page.getByRole('dialog', { name: 'Delete workspace' })
|
await page.getByRole('dialog', { name: 'Delete workspace' })
|
||||||
.getByRole('button', { name: 'Delete workspace' }).click()
|
.getByRole('button', { name: 'Delete workspace' }).click()
|
||||||
@@ -509,9 +521,10 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
|||||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-ws-row-menu'))
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-ws-row-menu'))
|
||||||
const sessionRow = await seededSessionRow()
|
const sessionRow = await seededSessionRow()
|
||||||
// The trigger is display:none until its row hovers.
|
// The trigger is display:none until its row hovers.
|
||||||
await sessionRow.hover()
|
|
||||||
const trigger = sessionRow.locator('button[aria-label^="Session actions for "]')
|
const trigger = sessionRow.locator('button[aria-label^="Session actions for "]')
|
||||||
await trigger.click()
|
const triggerName = await trigger.getAttribute('aria-label')
|
||||||
|
if (triggerName === null) throw new Error('seeded Session row has no actions label')
|
||||||
|
await clickHoverAction(sessionRow, triggerName)
|
||||||
const item = page.getByRole('menuitem', { name: 'Rename' })
|
const item = page.getByRole('menuitem', { name: 'Rename' })
|
||||||
await item.waitFor({ timeout: 5_000 })
|
await item.waitFor({ timeout: 5_000 })
|
||||||
// Into the list, then back up to the trigger across the 4px gap below it:
|
// Into the list, then back up to the trigger across the 4px gap below it:
|
||||||
@@ -559,8 +572,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
|||||||
const rowTitle = await sessionRow.locator('[class*="title"]').innerText()
|
const rowTitle = await sessionRow.locator('[class*="title"]').innerText()
|
||||||
// Row menu: hover reveals the actions button; Archive session commits
|
// Row menu: hover reveals the actions button; Archive session commits
|
||||||
// without a confirmation dialog (non-destructive: log + accounting stay).
|
// without a confirmation dialog (non-destructive: log + accounting stay).
|
||||||
await sessionRow.hover()
|
await clickHoverAction(sessionRow, `Session actions for ${rowTitle}`)
|
||||||
await sessionRow.getByRole('button', { name: `Session actions for ${rowTitle}` }).click()
|
|
||||||
await page.getByRole('menuitem', { name: 'Archive session' }).click()
|
await page.getByRole('menuitem', { name: 'Archive session' }).click()
|
||||||
// The row disappears on the archive-set echo; with no other visible
|
// The row disappears on the archive-set echo; with no other visible
|
||||||
// stray, the whole Ungrouped bucket withdraws.
|
// stray, the whole Ungrouped bucket withdraws.
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import react from '@vitejs/plugin-react'
|
|||||||
|
|
||||||
const src = (rel: string): string => fileURLToPath(new URL(rel, import.meta.url))
|
const src = (rel: string): string => fileURLToPath(new URL(rel, import.meta.url))
|
||||||
const STANDALONE_ERROR = 'apps/web is not a standalone application: bare Vite cannot inject window.__DSH_BOOT__. '
|
const STANDALONE_ERROR = 'apps/web is not a standalone application: bare Vite cannot inject window.__DSH_BOOT__. '
|
||||||
+ 'Build with `pnpm run build && pnpm run build:web`, then run `dsh web` (repository checkout: `pnpm run dsh -- web`). '
|
+ 'From a repository checkout, run `pnpm dsh web`; an installed package uses `dsh web`. '
|
||||||
+ 'For client-plugin HMR, run `pnpm run dsh -- web --dev` together with `pnpm run dev:web`.'
|
+ 'For client-plugin HMR, run `pnpm dsh web --dev` together with `pnpm run dev:web`.'
|
||||||
|
|
||||||
/** Fail before a Vite dev or preview server can expose the boot-manifest-free shell. */
|
/** Fail before a Vite dev or preview server can expose the boot-manifest-free shell. */
|
||||||
function rejectStandaloneServe(): Plugin {
|
function rejectStandaloneServe(): Plugin {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# dsh launcher: runs the apps/cli `dsh` bin FROM SOURCE through the tsx ESM
|
|
||||||
# hook, so a symlink from anywhere (e.g. ~/.local/bin/dsh) always executes the
|
|
||||||
# current working tree without a build step.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
# Resolve symlink chains without readlink -f (not on every macOS).
|
|
||||||
script=$0
|
|
||||||
while [ -L "$script" ]; do
|
|
||||||
target=$(readlink "$script")
|
|
||||||
case $target in
|
|
||||||
/*) script=$target ;;
|
|
||||||
*) script=$(dirname "$script")/$target ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
root=$(CDPATH='' cd -- "$(dirname -- "$script")/.." && pwd)
|
|
||||||
|
|
||||||
# The ESM-only tsx hook transforms TypeScript and projects this checkout's
|
|
||||||
# tsconfig paths into Node resolution (the CJS hook stays off: the graph is
|
|
||||||
# ESM-only and the CJS resolver costs ~0.4s of startup). Absolute paths keep
|
|
||||||
# both the hook and the tsconfig anchored to this checkout when the launcher
|
|
||||||
# runs from any cwd, where bare `tsx/esm` would not resolve.
|
|
||||||
NODE_USE_ENV_PROXY=1 \
|
|
||||||
TSX_TSCONFIG_PATH="$root/tsconfig.json" \
|
|
||||||
exec node --import "$root/node_modules/tsx/dist/esm/index.mjs" \
|
|
||||||
"$root/apps/cli/src/bin.ts" "$@"
|
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/api-gateway.md
|
# pnpm run verify-translation-pairing --write docs/api-gateway.md
|
||||||
api-gateway.md: 06f98546c8858af08efcb07c007d8e1b95b90c19
|
api-gateway.md: 3065f3f10861965b327a4412049878dc1ba7faec
|
||||||
api-gateway.zh.md: 82bf8923c45151d0c00e71848be89806427d1ce3
|
api-gateway.zh.md: aa9b726c33fd9f51fc0b2d2ed95c4c9658662796
|
||||||
+2
-8
@@ -138,16 +138,10 @@ SRC solves only dispatch for a Host process running from source. The Client does
|
|||||||
|
|
||||||
## Development mode
|
## Development mode
|
||||||
|
|
||||||
A complete build generates Host contracts before compiling the Host, Client, and Web, so it is the deterministic entry for creating or refreshing all artifacts:
|
The repository `dsh` script completes the Host, Client, and Web build before starting the source Host. Web development runs that command and the Client plugin watcher in separate terminals:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run build
|
pnpm dsh web --dev
|
||||||
```
|
|
||||||
|
|
||||||
Web development normally starts the source Host after one complete build and runs the Client plugin watcher in another terminal:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm run dsh -- web --dev
|
|
||||||
pnpm run dev:web
|
pnpm run dev:web
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -138,16 +138,10 @@ SRC 只解决 Host 源码进程的分发问题。Client 不会从运行中的 Ho
|
|||||||
|
|
||||||
## 开发模式
|
## 开发模式
|
||||||
|
|
||||||
完整构建会先生成 Host 约定,再编译 Host、Client 与 Web,因此是建立或刷新所有产物的确定性入口:
|
仓库的 `dsh` 脚本会先完成 Host、Client 与 Web 构建,再启动源码 Host。Web 开发需要在两个终端中分别运行该命令和 Client plugin watcher:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run build
|
pnpm dsh web --dev
|
||||||
```
|
|
||||||
|
|
||||||
Web 开发通常在完成一次构建后启动源码 Host,并在另一个终端运行 Client plugin watcher:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm run dsh -- web --dev
|
|
||||||
pnpm run dev:web
|
pnpm run dev:web
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||||
config-catalog.md: e2bab765d147f48f0b50a44c4853136954578f3f
|
config-catalog.md: 87665e8454eba5293d334c9595ef79780540df7b
|
||||||
config-catalog.zh.md: 4c788f9b5afdf65798e298c3e7c1d97bc14ffb64
|
config-catalog.zh.md: 3cf2035865f36f18b2395bf8f161157bc5ee9f32
|
||||||
@@ -533,7 +533,7 @@ export interface Config {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Source: [`packages/fs/fs-local/src/index.ts:40`](../packages/fs/fs-local/src/index.ts)
|
Source: [`packages/fs/fs-local/src/index.ts:41`](../packages/fs/fs-local/src/index.ts)
|
||||||
|
|
||||||
## `@deepseek-ai/dsh-fs-sandbox`
|
## `@deepseek-ai/dsh-fs-sandbox`
|
||||||
|
|
||||||
@@ -986,6 +986,8 @@ export interface ReplayModelConfig {
|
|||||||
description?: string
|
description?: string
|
||||||
/** Optional positive integer context capacity published by the replay adapter. */
|
/** Optional positive integer context capacity published by the replay adapter. */
|
||||||
contextWindow?: number
|
contextWindow?: number
|
||||||
|
/** Optional declared input modalities, so a scenario can exercise capability gates (e.g. image-capable `read_image`). */
|
||||||
|
inputModalities?: readonly ModelModality[]
|
||||||
/**
|
/**
|
||||||
* Optional per-request output cap the replay route materializes when callers
|
* Optional per-request output cap the replay route materializes when callers
|
||||||
* omit one, so replay reconstructs the request header a live catalog produced.
|
* omit one, so replay reconstructs the request header a live catalog produced.
|
||||||
@@ -1001,9 +1003,9 @@ export interface ReplayModelConfig {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Depends on: [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
|
Depends on: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
|
||||||
|
|
||||||
Source: [`packages/support/llm-replay/src/index.ts:769`](../packages/support/llm-replay/src/index.ts)
|
Source: [`packages/support/llm-replay/src/index.ts:776`](../packages/support/llm-replay/src/index.ts)
|
||||||
|
|
||||||
## `@deepseek-ai/dsh-llm-retry`
|
## `@deepseek-ai/dsh-llm-retry`
|
||||||
|
|
||||||
@@ -2116,7 +2118,7 @@ export interface Config {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Source: [`packages/fs/tool-fs/src/index.ts:24`](../packages/fs/tool-fs/src/index.ts)
|
Source: [`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
## `@deepseek-ai/dsh-tool-fs-search`
|
## `@deepseek-ai/dsh-tool-fs-search`
|
||||||
|
|
||||||
|
|||||||
@@ -988,6 +988,8 @@ export interface ReplayModelConfig {
|
|||||||
description?: string
|
description?: string
|
||||||
/** Optional positive integer context capacity published by the replay adapter. */
|
/** Optional positive integer context capacity published by the replay adapter. */
|
||||||
contextWindow?: number
|
contextWindow?: number
|
||||||
|
/** Optional declared input modalities, so a scenario can exercise capability gates (e.g. image-capable `read_image`). */
|
||||||
|
inputModalities?: readonly ModelModality[]
|
||||||
/**
|
/**
|
||||||
* Optional per-request output cap the replay route materializes when callers
|
* Optional per-request output cap the replay route materializes when callers
|
||||||
* omit one, so replay reconstructs the request header a live catalog produced.
|
* omit one, so replay reconstructs the request header a live catalog produced.
|
||||||
@@ -1003,9 +1005,9 @@ export interface ReplayModelConfig {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
依赖:[`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
|
依赖:[`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
|
||||||
|
|
||||||
来源:[`packages/support/llm-replay/src/index.ts:769`](../packages/support/llm-replay/src/index.ts)
|
来源:[`packages/support/llm-replay/src/index.ts:776`](../packages/support/llm-replay/src/index.ts)
|
||||||
|
|
||||||
## `@deepseek-ai/dsh-llm-retry`
|
## `@deepseek-ai/dsh-llm-retry`
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/development.md
|
# pnpm run verify-translation-pairing --write docs/development.md
|
||||||
development.md: 8e565f21c6e2ede7dab7dbda3c4b18b77ce0920f
|
development.md: 7d6ca74df1311560b0d61444c92e74927639ca31
|
||||||
development.zh.md: d9c0fbfbb663334b8f7e2ca11d4e8d9a0652c22e
|
development.zh.md: 1461717cf496b1863f2f0c47ad4b42990dc789e9
|
||||||
+1
-1
@@ -129,7 +129,7 @@ The root [contributor instructions](../AGENTS.md#commands) summarize common comm
|
|||||||
The one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
|
The one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run demo:headless "summarize this workspace"
|
pnpm dsh --profile headless "summarize this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
The self-referential cordis demo can inspect and modify its live plugin runtime and needs the same credentials (`web` by default, or `acp`):
|
The self-referential cordis demo can inspect and modify its live plugin runtime and needs the same credentials (`web` by default, or `acp`):
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ keyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若
|
|||||||
单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
|
单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run demo:headless "summarize this workspace"
|
pnpm dsh --profile headless "summarize this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
自指的 cordis 演示可以检查并修改其实时插件运行时,并需要相同的凭证(默认 `web`,也可用 `acp`):
|
自指的 cordis 演示可以检查并修改其实时插件运行时,并需要相同的凭证(默认 `web`,也可用 `acp`):
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/module-graph.md
|
# pnpm run verify-translation-pairing --write docs/module-graph.md
|
||||||
module-graph.md: 65b291dfc049b30ece7a53f23ad32186759d6fcb
|
module-graph.md: d497146da451072770ff3ad5a79050e2d2abbe98
|
||||||
module-graph.zh.md: 8bf1993964931209829733bf0a6a07097c1301b9
|
module-graph.zh.md: 64da3a362bc2769adacad4372c663b0d217e7ef3
|
||||||
@@ -792,6 +792,7 @@ flowchart TD
|
|||||||
pkg_fs_sandbox --> pkg_invariants
|
pkg_fs_sandbox --> pkg_invariants
|
||||||
pkg_fs_sandbox --> pkg_sandbox
|
pkg_fs_sandbox --> pkg_sandbox
|
||||||
pkg_fs_sandbox --> pkg_sandbox_policy
|
pkg_fs_sandbox --> pkg_sandbox_policy
|
||||||
|
pkg_tool_fs --> pkg_attachment
|
||||||
pkg_tool_fs --> pkg_fs
|
pkg_tool_fs --> pkg_fs
|
||||||
pkg_tool_fs --> pkg_invariants
|
pkg_tool_fs --> pkg_invariants
|
||||||
pkg_tool_fs --> pkg_llm
|
pkg_tool_fs --> pkg_llm
|
||||||
@@ -1390,7 +1391,7 @@ flowchart TD
|
|||||||
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||||
| [`pwsh-sandbox`](../packages/bash/pwsh-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`pwsh-local`](../packages/bash/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
| [`pwsh-sandbox`](../packages/bash/pwsh-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`pwsh-local`](../packages/bash/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||||
| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||||
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||||
| [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) |
|
| [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) |
|
||||||
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
||||||
|
|||||||
@@ -794,6 +794,7 @@ flowchart TD
|
|||||||
pkg_fs_sandbox --> pkg_invariants
|
pkg_fs_sandbox --> pkg_invariants
|
||||||
pkg_fs_sandbox --> pkg_sandbox
|
pkg_fs_sandbox --> pkg_sandbox
|
||||||
pkg_fs_sandbox --> pkg_sandbox_policy
|
pkg_fs_sandbox --> pkg_sandbox_policy
|
||||||
|
pkg_tool_fs --> pkg_attachment
|
||||||
pkg_tool_fs --> pkg_fs
|
pkg_tool_fs --> pkg_fs
|
||||||
pkg_tool_fs --> pkg_invariants
|
pkg_tool_fs --> pkg_invariants
|
||||||
pkg_tool_fs --> pkg_llm
|
pkg_tool_fs --> pkg_llm
|
||||||
@@ -894,6 +895,7 @@ flowchart TD
|
|||||||
pkg_llm_replay --> pkg_llm
|
pkg_llm_replay --> pkg_llm
|
||||||
pkg_llm_replay --> pkg_session
|
pkg_llm_replay --> pkg_session
|
||||||
pkg_client_ui_trajectory --> pkg_agent
|
pkg_client_ui_trajectory --> pkg_agent
|
||||||
|
pkg_client_ui_trajectory --> pkg_client_locale
|
||||||
pkg_client_ui_trajectory --> pkg_client_runtime
|
pkg_client_ui_trajectory --> pkg_client_runtime
|
||||||
pkg_client_ui_trajectory --> pkg_client_ui_primitives
|
pkg_client_ui_trajectory --> pkg_client_ui_primitives
|
||||||
pkg_client_ui_trajectory --> pkg_compact
|
pkg_client_ui_trajectory --> pkg_compact
|
||||||
@@ -1391,7 +1393,7 @@ flowchart TD
|
|||||||
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||||
| [`pwsh-sandbox`](../packages/bash/pwsh-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`pwsh-local`](../packages/bash/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
| [`pwsh-sandbox`](../packages/bash/pwsh-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`pwsh-local`](../packages/bash/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||||
| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||||
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||||
| [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) |
|
| [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) |
|
||||||
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
||||||
@@ -1406,7 +1408,7 @@ flowchart TD
|
|||||||
| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||||
| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||||
| [`llm-replay`](../packages/support/llm-replay) | `support` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
| [`llm-replay`](../packages/support/llm-replay) | `support` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||||
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`agent`](../packages/core/agent), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
|
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`agent`](../packages/core/agent), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
|
||||||
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) |
|
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) |
|
||||||
| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||||
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
|
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md
|
||||||
filesystem.md: 00e28130db9f60e6ad5f8c582ca5531482cdeb32
|
filesystem.md: 01fe2d07f5497374019855ca46ced7e173d21445
|
||||||
filesystem.zh.md: 5cf6be12f41b36c8149a941a4d251c4497ed4738
|
filesystem.zh.md: e68246d7a44b8812e132e02979a2c0cda6adb792
|
||||||
@@ -52,7 +52,7 @@ type FsTargetKey = Branded<'FsTargetKey'>
|
|||||||
type FsVersion = Branded<'FsVersion'>
|
type FsVersion = Branded<'FsVersion'>
|
||||||
```
|
```
|
||||||
|
|
||||||
`stat` returns metadata (never content), or `undefined` when the target is absent. `type` lets the tool reject directories/special files before reading, and `size` lets it choose `readText` vs `streamText` without probing by failure. A protocol consumer that needs a byte ceiling applies it while consuming `streamText`, so the filesystem seam needs no consumer-specific bounded-read primitive.
|
`stat` returns metadata (never content), or `undefined` when the target is absent. `type` lets consumers reject directories and special files before reading, and `size` lets text consumers choose `readText` vs `streamText` without probing by failure. A text consumer applies its own retention ceiling while consuming `streamText`. Raw-byte consumers use `readBytes(target, signal, maxBytes)`; its required complete-content cap makes a known or discovered overflow fail with `FS_TOO_LARGE` instead of truncating or buffering without a bound.
|
||||||
|
|
||||||
```ts type-equiv
|
```ts type-equiv
|
||||||
/**
|
/**
|
||||||
@@ -256,6 +256,7 @@ type FsErrorCode =
|
|||||||
| 'FS_NOT_DIRECTORY'
|
| 'FS_NOT_DIRECTORY'
|
||||||
| 'FS_NOT_TEXT'
|
| 'FS_NOT_TEXT'
|
||||||
| 'FS_NOT_REGULAR_FILE'
|
| 'FS_NOT_REGULAR_FILE'
|
||||||
|
| 'FS_TOO_LARGE'
|
||||||
| 'FS_PERMISSION_DENIED'
|
| 'FS_PERMISSION_DENIED'
|
||||||
| 'FS_SANDBOX_DENIED'
|
| 'FS_SANDBOX_DENIED'
|
||||||
| 'FS_IO_ERROR'
|
| 'FS_IO_ERROR'
|
||||||
@@ -274,7 +275,7 @@ type FsErrorCode =
|
|||||||
|
|
||||||
## The service and the plugin
|
## The service and the plugin
|
||||||
|
|
||||||
`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls from unseen/absent/present state and records `FsObservation` values. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures.
|
`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `readBytes`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls from unseen/absent/present state and records `FsObservation` values. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures.
|
||||||
|
|
||||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||||
|
|
||||||
@@ -373,6 +374,18 @@ abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
|
|||||||
*/
|
*/
|
||||||
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the whole regular file as raw bytes with no decoding or binary
|
||||||
|
* rejection. The bound lives at this seam so a backend can never buffer an
|
||||||
|
* unbounded file: a target known or discovered to exceed `maxBytes` fails
|
||||||
|
* with `FS_TOO_LARGE` instead of returning a truncated result.
|
||||||
|
* @param target - the resolved target to read.
|
||||||
|
* @param signal - aborts the read.
|
||||||
|
* @param maxBytes - inclusive byte cap on the complete content.
|
||||||
|
* @returns the full raw content, at most `maxBytes` long.
|
||||||
|
*/
|
||||||
|
abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List direct children of a directory in stable name order. Returns resolved
|
* List direct children of a directory in stable name order. Returns resolved
|
||||||
* child targets plus cheap metadata only; never reads file contents.
|
* child targets plus cheap metadata only; never reads file contents.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ type FsTargetKey = Branded<'FsTargetKey'>
|
|||||||
type FsVersion = Branded<'FsVersion'>
|
type FsVersion = Branded<'FsVersion'>
|
||||||
```
|
```
|
||||||
|
|
||||||
`stat` 返回元数据(从不返回内容),目标不存在时返回 `undefined`。`type` 让工具在读取前拒绝目录或特殊文件;`size` 让工具无需通过失败探测即可选择 `readText` 还是 `streamText`。需要字节上限的协议消费方在消费 `streamText` 时执行该上限,因此文件系统 seam 无需消费方专用的有界读取原语。
|
`stat` 返回元数据(从不返回内容),目标不存在时返回 `undefined`。`type` 让消费方在读取前拒绝目录和特殊文件;`size` 让文本消费方无需通过失败探测即可选择 `readText` 还是 `streamText`。文本消费方在消费 `streamText` 时执行自己的保留量上限。原始字节消费方调用 `readBytes(target, signal, maxBytes)`;其必填的完整内容上限会使已知或读取中发现的超限以 `FS_TOO_LARGE` 失败,不会截断结果或无界缓冲。
|
||||||
|
|
||||||
```ts type-equiv
|
```ts type-equiv
|
||||||
/**
|
/**
|
||||||
@@ -256,6 +256,7 @@ type FsErrorCode =
|
|||||||
| 'FS_NOT_DIRECTORY'
|
| 'FS_NOT_DIRECTORY'
|
||||||
| 'FS_NOT_TEXT'
|
| 'FS_NOT_TEXT'
|
||||||
| 'FS_NOT_REGULAR_FILE'
|
| 'FS_NOT_REGULAR_FILE'
|
||||||
|
| 'FS_TOO_LARGE'
|
||||||
| 'FS_PERMISSION_DENIED'
|
| 'FS_PERMISSION_DENIED'
|
||||||
| 'FS_SANDBOX_DENIED'
|
| 'FS_SANDBOX_DENIED'
|
||||||
| 'FS_IO_ERROR'
|
| 'FS_IO_ERROR'
|
||||||
@@ -274,7 +275,7 @@ type FsErrorCode =
|
|||||||
|
|
||||||
## 服务与插件
|
## 服务与插件
|
||||||
|
|
||||||
`FileSystem`(`ctx.fs`,abstract)拥有提供方原语:`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:根据未见/缺失/存在状态对写入与编辑意图 waterfall 作出决策,并记录 `FsObservation` 值。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall,并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。
|
`FileSystem`(`ctx.fs`,abstract)拥有提供方原语:`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`readBytes`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:根据未见/缺失/存在状态对写入与编辑意图 waterfall 作出决策,并记录 `FsObservation` 值。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall,并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。
|
||||||
|
|
||||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||||
|
|
||||||
@@ -373,6 +374,18 @@ abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
|
|||||||
*/
|
*/
|
||||||
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the whole regular file as raw bytes with no decoding or binary
|
||||||
|
* rejection. The bound lives at this seam so a backend can never buffer an
|
||||||
|
* unbounded file: a target known or discovered to exceed `maxBytes` fails
|
||||||
|
* with `FS_TOO_LARGE` instead of returning a truncated result.
|
||||||
|
* @param target - the resolved target to read.
|
||||||
|
* @param signal - aborts the read.
|
||||||
|
* @param maxBytes - inclusive byte cap on the complete content.
|
||||||
|
* @returns the full raw content, at most `maxBytes` long.
|
||||||
|
*/
|
||||||
|
abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List direct children of a directory in stable name order. Returns resolved
|
* List direct children of a directory in stable name order. Returns resolved
|
||||||
* child targets plus cheap metadata only; never reads file contents.
|
* child targets plus cheap metadata only; never reads file contents.
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/tool-catalog.md
|
# pnpm run verify-translation-pairing --write docs/tool-catalog.md
|
||||||
tool-catalog.md: f61b6daeb7209d0fa81606bda7718fd42c7f22dc
|
tool-catalog.md: 19a4035fdc1e24d439307d11cb585f689b35043e
|
||||||
tool-catalog.zh.md: cbecdba084fa64ec78913706221a1558d206cde8
|
tool-catalog.zh.md: e34fbb85152e012592b4e045e2f505ded6175e94
|
||||||
+23
-2
@@ -23,7 +23,7 @@ This table connects model-visible tool names to the plugin package and service s
|
|||||||
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`, `cordis_mount`, `cordis_unmount` | `ctx.tools` | `tool/call`, `tool/result`, `process-local temporary Plugin lifecycle` | - | Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes. |
|
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`, `cordis_mount`, `cordis_unmount` | `ctx.tools` | `tool/call`, `tool/result`, `process-local temporary Plugin lifecycle` | - | Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes. |
|
||||||
| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`, `ctx.pty`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. |
|
| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`, `ctx.pty`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. |
|
||||||
| `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`, `ctx.fs` | `tool/call`, `fs/observed after view presence/absence, edit absence, or successful mutation`, `tool/result` | - | Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal surface. |
|
| `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`, `ctx.fs` | `tool/call`, `fs/observed after view presence/absence, edit absence, or successful mutation`, `tool/result` | - | Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal surface. |
|
||||||
| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after read presence/absence or successful mutation`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin. |
|
| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `read_image`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt`, `ctx.attachments (read_image registration)`, `ctx.llm + an image-capable route (read_image execution)` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after read presence/absence or successful file operation`, `durable attachment (read_image)`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input. |
|
||||||
| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.subprocess`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background tasks) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. |
|
| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.subprocess`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background tasks) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. |
|
||||||
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.tools`, `ctx.pty`, `ctx.systemPrompt`, `ctx.tasks at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. |
|
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.tools`, `ctx.pty`, `ctx.systemPrompt`, `ctx.tasks at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. |
|
||||||
| `@deepseek-ai/dsh-tool-goal` | `create_goal`, `get_goal`, `update_goal` | `ctx.tools`, `ctx.agents`, `ctx.goals`, `ctx.systemPrompt`, `a calling Agent in an authorized open turn` | `tool/call`, `goal/change for mutations`, `tool/result` | - | create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. |
|
| `@deepseek-ai/dsh-tool-goal` | `create_goal`, `get_goal`, `update_goal` | `ctx.tools`, `ctx.agents`, `ctx.goals`, `ctx.systemPrompt`, `a calling Agent in an authorized open turn` | `tool/call`, `goal/change for mutations`, `tool/result` | - | create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. |
|
||||||
@@ -485,6 +485,27 @@ Read a UTF-8 text file and return line-numbered content.
|
|||||||
|
|
||||||
Source: [`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
Source: [`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
|
### `read_image`
|
||||||
|
|
||||||
|
Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to the image file, resolved by the filesystem backend."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"file_path"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Source: [`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
### `write`
|
### `write`
|
||||||
|
|
||||||
Create or fully replace a UTF-8 text file.
|
Create or fully replace a UTF-8 text file.
|
||||||
@@ -511,7 +532,7 @@ Create or fully replace a UTF-8 text file.
|
|||||||
|
|
||||||
Source: [`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
Source: [`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin.
|
The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input.
|
||||||
|
|
||||||
## `@deepseek-ai/dsh-tool-fs-search`
|
## `@deepseek-ai/dsh-tool-fs-search`
|
||||||
|
|
||||||
|
|||||||
+23
-2
@@ -25,7 +25,7 @@
|
|||||||
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`、`cordis_mount`、`cordis_unmount` | `ctx.tools` | `tool/call`、`tool/result`、`process-local temporary Plugin lifecycle` | - | 不在任何随产品发布的树中,需要有意选择启用;临时 Plugin 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。由 cordis_mount 创建的插件在卸载或 DSH 重启之前可以注册**额外的**模型可见工具;发生这类工具集变更时,系统会记录完整且有变动的请求头。 |
|
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`、`cordis_mount`、`cordis_unmount` | `ctx.tools` | `tool/call`、`tool/result`、`process-local temporary Plugin lifecycle` | - | 不在任何随产品发布的树中,需要有意选择启用;临时 Plugin 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。由 cordis_mount 创建的插件在卸载或 DSH 重启之前可以注册**额外的**模型可见工具;发生这类工具集变更时,系统会记录完整且有变动的请求头。 |
|
||||||
| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`、`ctx.pty`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 |
|
| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`、`ctx.pty`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 |
|
||||||
| `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`、`ctx.fs` | `tool/call`、`fs/observed after view presence/absence, edit absence, or successful mutation`、`tool/result` | - | 基于文件系统 seam 的独立查看/创建/唯一字面量替换/按行插入工具;可与任何 shell 或终端接口组合。 |
|
| `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`、`ctx.fs` | `tool/call`、`fs/observed after view presence/absence, edit absence, or successful mutation`、`tool/result` | - | 基于文件系统 seam 的独立查看/创建/唯一字面量替换/按行插入工具;可与任何 shell 或终端接口组合。 |
|
||||||
| `@deepseek-ai/dsh-tool-fs` | `edit`、`read`、`write` | `ctx.tools`、`ctx.fs`、`ctx.systemPrompt` | `tool/call`、`fs/write-intent or fs/edit-intent for mutations`、`fs/observed after read presence/absence or successful mutation`、`tool/result` | - | 先读后写/编辑策略由 `@deepseek-ai/dsh-fs-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。无论是否加载策略插件,上述工具 schema 都完全相同。 |
|
| `@deepseek-ai/dsh-tool-fs` | `edit`、`read`、`read_image`、`write` | `ctx.tools`、`ctx.fs`、`ctx.systemPrompt`、`ctx.attachments (read_image registration)`、`ctx.llm + an image-capable route (read_image execution)` | `tool/call`、`fs/write-intent or fs/edit-intent for mutations`、`fs/observed after read presence/absence or successful file operation`、`durable attachment (read_image)`、`tool/result` | - | 先读后写/编辑策略由 `@deepseek-ai/dsh-fs-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments` 时 `read_image` 不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图像输入,否则拒绝。 |
|
||||||
| `@deepseek-ai/dsh-tool-fs-search` | `glob`、`grep` | `ctx.tools`、`ctx.subprocess`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。 |
|
| `@deepseek-ai/dsh-tool-fs-search` | `glob`、`grep` | `ctx.tools`、`ctx.subprocess`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。 |
|
||||||
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`、`terminal_list`、`terminal_open`、`terminal_read`、`terminal_send`、`terminal_signal` | `ctx.tools`、`ctx.pty`、`ctx.systemPrompt`、`ctx.tasks at call time for run_in_background` | `tool/call`、`tool/result` | - | 这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.tasks`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。 |
|
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`、`terminal_list`、`terminal_open`、`terminal_read`、`terminal_send`、`terminal_signal` | `ctx.tools`、`ctx.pty`、`ctx.systemPrompt`、`ctx.tasks at call time for run_in_background` | `tool/call`、`tool/result` | - | 这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.tasks`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。 |
|
||||||
| `@deepseek-ai/dsh-tool-goal` | `create_goal`、`get_goal`、`update_goal` | `ctx.tools`、`ctx.agents`、`ctx.goals`、`ctx.systemPrompt`、`a calling Agent in an authorized open turn` | `tool/call`、`goal/change for mutations`、`tool/result` | - | create、edit、pause 和 resume 要求直接来自人类的根权限;complete 和 blocked 也接受确切的当前 Goal Round。blocked 的默认下限是 3 个获准的 Round。 |
|
| `@deepseek-ai/dsh-tool-goal` | `create_goal`、`get_goal`、`update_goal` | `ctx.tools`、`ctx.agents`、`ctx.goals`、`ctx.systemPrompt`、`a calling Agent in an authorized open turn` | `tool/call`、`goal/change for mutations`、`tool/result` | - | create、edit、pause 和 resume 要求直接来自人类的根权限;complete 和 blocked 也接受确切的当前 Goal Round。blocked 的默认下限是 3 个获准的 Round。 |
|
||||||
@@ -489,6 +489,27 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
|
|||||||
|
|
||||||
来源:[`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
来源:[`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
|
### `read_image`
|
||||||
|
|
||||||
|
读取 PNG/JPEG/WebP/GIF 文件并返回图像本身。要求当前模型接受图像输入。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to the image file, resolved by the filesystem backend."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"file_path"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
来源:[`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
### `write`
|
### `write`
|
||||||
|
|
||||||
创建或完全替换 UTF-8 文本文件。
|
创建或完全替换 UTF-8 文本文件。
|
||||||
@@ -515,7 +536,7 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
|
|||||||
|
|
||||||
来源:[`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
来源:[`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts)
|
||||||
|
|
||||||
先读后写/编辑策略由 `@deepseek-ai/dsh-fs-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。无论是否加载策略插件,上述工具 schema 都完全相同。
|
先读后写/编辑策略由 `@deepseek-ai/dsh-fs-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments` 时 `read_image` 不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图像输入,否则拒绝。
|
||||||
|
|
||||||
## `@deepseek-ai/dsh-tool-fs-search`
|
## `@deepseek-ai/dsh-tool-fs-search`
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/user/develop/basic/index.md
|
# pnpm run verify-translation-pairing --write docs/user/develop/basic/index.md
|
||||||
index.md: fe525018011809abe4dd18ac5739c54cd330c8f6
|
index.md: e57a42b42690bd92450cc26876c13a1622bb80cc
|
||||||
index.zh.md: 0dbb7275be3c7147395041fb18d49a7f6ea69cdd
|
index.zh.md: 240623341618acd6501f2897ae2844fde0a5b73b
|
||||||
@@ -56,7 +56,7 @@ Create `scratch-plugin/cordis.yml` as a Web overlay that inserts the local plugi
|
|||||||
Start the Web UI with that overlay:
|
Start the Web UI with that overlay:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh web --patch ./scratch-plugin/cordis.yml
|
pnpm dsh web --patch ./scratch-plugin/cordis.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://127.0.0.1:3080`. The terminal prints `[hello-plugin] plugin loaded!` during startup.
|
Open `http://127.0.0.1:3080`. The terminal prints `[hello-plugin] plugin loaded!` during startup.
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function apply(ctx: Context) {
|
|||||||
使用该覆盖层启动 Web UI:
|
使用该覆盖层启动 Web UI:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh web --patch ./scratch-plugin/cordis.yml
|
pnpm dsh web --patch ./scratch-plugin/cordis.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
打开 `http://127.0.0.1:3080`。启动期间,终端会打印 `[hello-plugin] plugin loaded!`。
|
打开 `http://127.0.0.1:3080`。启动期间,终端会打印 `[hello-plugin] plugin loaded!`。
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/user/develop/basic/tool.md
|
# pnpm run verify-translation-pairing --write docs/user/develop/basic/tool.md
|
||||||
tool.md: f110bd2c10caf21ea8c87bc32fd43b01b209a9d1
|
tool.md: a149f6876c573c9ece4b066ef6c211f69b0b03a8
|
||||||
tool.zh.md: a237b6015de0ea6141a94c56aaa6f68c73fa3feb
|
tool.zh.md: 1f9a1172bcde33a1d7319492f15686c47de01174
|
||||||
@@ -40,7 +40,7 @@ export function apply(ctx: Context) {
|
|||||||
Restart the development command if it is not running:
|
Restart the development command if it is not running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh web --patch ./scratch-plugin/cordis.yml
|
pnpm dsh web --patch ./scratch-plugin/cordis.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://127.0.0.1:3080` and ask: `Use the greet tool to greet Ada.` The model can call `greet` and receives `Hello, Ada!` as the tool result.
|
Open `http://127.0.0.1:3080` and ask: `Use the greet tool to greet Ada.` The model can call `greet` and receives `Hello, Ada!` as the tool result.
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function apply(ctx: Context) {
|
|||||||
如果开发命令未在运行,请重新启动:
|
如果开发命令未在运行,请重新启动:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh web --patch ./scratch-plugin/cordis.yml
|
pnpm dsh web --patch ./scratch-plugin/cordis.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
打开 `http://127.0.0.1:3080`,然后输入:`Use the greet tool to greet Ada.` 模型可以调用 `greet`,并收到 `Hello, Ada!` 这一工具结果。
|
打开 `http://127.0.0.1:3080`,然后输入:`Use the greet tool to greet Ada.` 模型可以调用 `greet`,并收到 `Hello, Ada!` 这一工具结果。
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/user/guide/providers.md
|
# pnpm run verify-translation-pairing --write docs/user/guide/providers.md
|
||||||
providers.md: 4667e54161e77a62d454f3f78a8164d5c79b78c8
|
providers.md: 31b4be666bd6b7a402a0bae7bc8f810fd2d928d8
|
||||||
providers.zh.md: 15e0ccc826a5c285c71d4776793f8048c11f6254
|
providers.zh.md: bd95c63cacd9718b3ffc1f78e17270e7022b21e0
|
||||||
@@ -15,7 +15,7 @@ Adding a provider therefore rarely means editing `cordis.yml` — writing settin
|
|||||||
|
|
||||||
## Configure from the web UI
|
## Configure from the web UI
|
||||||
|
|
||||||
Start `pnpm run dsh web` and open **Settings → Models**.
|
Start `pnpm dsh web` and open **Settings → Models**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Harness 出厂自带 DeepSeek,同时预装了一个通用的多提供方适配
|
|||||||
|
|
||||||
## 在 Web 界面里配置
|
## 在 Web 界面里配置
|
||||||
|
|
||||||
启动 `pnpm run dsh web`,打开**设置 → 模型**。
|
启动 `pnpm dsh web`,打开**设置 → 模型**。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/user/guide/quickstart.md
|
# pnpm run verify-translation-pairing --write docs/user/guide/quickstart.md
|
||||||
quickstart.md: 8e883efd470fef329a7308f1017cab0b7afcdd67
|
quickstart.md: 635c0dffd6119f95c1951a441691f87f04396b4a
|
||||||
quickstart.zh.md: e0e3f4e4754bca588831b6d204f77d1c97a140b3
|
quickstart.zh.md: d0921382bcb0844fa0260c51546622c582867e68
|
||||||
@@ -22,7 +22,6 @@ pnpm -v
|
|||||||
git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git
|
git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git
|
||||||
cd deepseek-harness
|
cd deepseek-harness
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Create the gitignored repository-root `.env`:
|
Create the gitignored repository-root `.env`:
|
||||||
@@ -36,7 +35,7 @@ DEEPSEEK_API_KEY=sk-your-key-here
|
|||||||
Run a non-interactive task and print its final answer:
|
Run a non-interactive task and print its final answer:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh --profile headless "summarize the architecture of this workspace"
|
pnpm dsh --profile headless "summarize the architecture of this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
`dsh --profile headless` creates and persists a fresh session, prints the final assistant answer, and exits. It starts no Web server or listening port, and a successful run leaves stderr empty.
|
`dsh --profile headless` creates and persists a fresh session, prints the final assistant answer, and exits. It starts no Web server or listening port, and a successful run leaves stderr empty.
|
||||||
@@ -46,7 +45,7 @@ pnpm run dsh --profile headless "summarize the architecture of this workspace"
|
|||||||
Start the browser interface:
|
Start the browser interface:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh web
|
pnpm dsh web
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://127.0.0.1:3080`. The agent can read and write files, run commands, delegate subtasks, and track a plan. Try: `Create hello.js in the current directory, print "Hello from Harness!", and run it`.
|
Open `http://127.0.0.1:3080`. The agent can read and write files, run commands, delegate subtasks, and track a plan. Try: `Create hello.js in the current directory, print "Hello from Harness!", and run it`.
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ pnpm -v
|
|||||||
git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git
|
git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git
|
||||||
cd deepseek-harness
|
cd deepseek-harness
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在仓库根目录创建已被 Git 忽略的 `.env`:
|
在仓库根目录创建已被 Git 忽略的 `.env`:
|
||||||
@@ -36,7 +35,7 @@ DEEPSEEK_API_KEY=sk-your-key-here
|
|||||||
运行一个非交互式任务并打印最终回答:
|
运行一个非交互式任务并打印最终回答:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh --profile headless "summarize the architecture of this workspace"
|
pnpm dsh --profile headless "summarize the architecture of this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
`dsh --profile headless` 创建并持久化一个新会话,打印最终助手回答,然后退出。它不会启动 Web 服务器或监听端口;成功运行时 stderr 为空。
|
`dsh --profile headless` 创建并持久化一个新会话,打印最终助手回答,然后退出。它不会启动 Web 服务器或监听端口;成功运行时 stderr 为空。
|
||||||
@@ -46,7 +45,7 @@ pnpm run dsh --profile headless "summarize the architecture of this workspace"
|
|||||||
启动浏览器界面:
|
启动浏览器界面:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dsh web
|
pnpm dsh web
|
||||||
```
|
```
|
||||||
|
|
||||||
打开 `http://127.0.0.1:3080`。agent 可以读写文件、运行命令、分配子任务和跟踪计划。可以尝试:`Create hello.js in the current directory, print "Hello from Harness!", and run it`。
|
打开 `http://127.0.0.1:3080`。agent 可以读写文件、运行命令、分配子任务和跟踪计划。可以尝试:`Create hello.js in the current directory, print "Hello from Harness!", and run it`。
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Keyless replay for the read-image refusal scenario: identical to the
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
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]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 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'
|
||||||
|
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.
|
||||||
|
|
||||||
|
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||||
|
- insert:
|
||||||
|
- id: attachment-local
|
||||||
|
name: '@deepseek-ai/dsh-attachment-local'
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# 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 flash model. The replay catalog declares image input on
|
||||||
|
# flash, so the strict read_image gate accepts the route and the tool result
|
||||||
|
# carries the durable image block; the live DeepSeek route cannot record this.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
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, image]
|
||||||
|
- id: deepseek-v4-pro
|
||||||
|
inputModalities: [text]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 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 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'
|
||||||
|
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.
|
||||||
|
|
||||||
|
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||||
|
- insert:
|
||||||
|
- id: attachment-local
|
||||||
|
name: '@deepseek-ai/dsh-attachment-local'
|
||||||
@@ -38,6 +38,8 @@ const WORKSPACE_CONTEXT_CONFIG = fileURLToPath(new URL('../workspace-context.cor
|
|||||||
const ADVANCED_CONFIG = fileURLToPath(new URL('../advanced.cordis.yml', import.meta.url))
|
const ADVANCED_CONFIG = fileURLToPath(new URL('../advanced.cordis.yml', import.meta.url))
|
||||||
const FS_CONFIG = fileURLToPath(new URL('../fs.cordis.yml', import.meta.url))
|
const FS_CONFIG = fileURLToPath(new URL('../fs.cordis.yml', import.meta.url))
|
||||||
const SESSION_QUERY_CONFIG = fileURLToPath(new URL('../session-query.cordis.yml', import.meta.url))
|
const SESSION_QUERY_CONFIG = fileURLToPath(new URL('../session-query.cordis.yml', import.meta.url))
|
||||||
|
const IMAGE_CONFIG = fileURLToPath(new URL('../image.cordis.yml', import.meta.url))
|
||||||
|
const IMAGE_TEXT_ROUTE_CONFIG = fileURLToPath(new URL('../image-text-route.cordis.yml', import.meta.url))
|
||||||
const PTY_CONFIG = fileURLToPath(new URL('../pty.cordis.yml', import.meta.url))
|
const PTY_CONFIG = fileURLToPath(new URL('../pty.cordis.yml', import.meta.url))
|
||||||
const DEPTH_TWO_CONFIG = fileURLToPath(new URL('../depth-two.cordis.yml', import.meta.url))
|
const DEPTH_TWO_CONFIG = fileURLToPath(new URL('../depth-two.cordis.yml', import.meta.url))
|
||||||
const CHILD_QUESTION_CONFIG = fileURLToPath(new URL('../child-question.cordis.yml', import.meta.url))
|
const CHILD_QUESTION_CONFIG = fileURLToPath(new URL('../child-question.cordis.yml', import.meta.url))
|
||||||
@@ -180,6 +182,30 @@ const SCENARIOS: Scenario[] = [
|
|||||||
configPath: SESSION_QUERY_CONFIG,
|
configPath: SESSION_QUERY_CONFIG,
|
||||||
posixOnly: true,
|
posixOnly: true,
|
||||||
},
|
},
|
||||||
|
// Authored keyless replays through the assembled app: the replay catalog
|
||||||
|
// declares flash image-capable (success) or text-only (refusal), and the
|
||||||
|
// real read_image tool executes against the workspace fixture and the real
|
||||||
|
// attachment store. Both boot the same composed header (the tool registers
|
||||||
|
// with the attachment store, independent of route), so they share one class.
|
||||||
|
{
|
||||||
|
name: 'read-image',
|
||||||
|
hasModelTurn: true,
|
||||||
|
recorded: false,
|
||||||
|
pinsHeader: true,
|
||||||
|
headerClass: 'image',
|
||||||
|
// The overlay adds no prompt section (read_image carries no guidance), so
|
||||||
|
// the composed system prompt is byte-identical to the default class; only
|
||||||
|
// the tool-schema sidecar is class-specific.
|
||||||
|
systemPromptSource: 'text-turn',
|
||||||
|
configPath: IMAGE_CONFIG,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'read-image-text-route',
|
||||||
|
hasModelTurn: true,
|
||||||
|
recorded: false,
|
||||||
|
headerClass: 'image',
|
||||||
|
configPath: IMAGE_TEXT_ROUTE_CONFIG,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'pty-tools',
|
name: 'pty-tools',
|
||||||
hasModelTurn: true,
|
hasModelTurn: true,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "initialize"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "newSession"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "prompt",
|
||||||
|
"text": "Use read_image on red.png in the current directory. If the tool refuses because the current model is text-only, reply with exactly the single word UNAVAILABLE."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{"type":"session","version":0,"id":"33333333-3333-4333-8333-333333333333","createdAt":1783951000000,"cwd":"{{cwd}}","delegationDepth":0}
|
||||||
|
{"type":"agent/inbox/spliced","seq":0,"time":1783951000001,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use read_image on red.png in the current directory. If the tool refuses because the current model is text-only, reply with exactly the single word UNAVAILABLE."}],"source":{"kind":"user"},"role":"user","id":"0a0a0a0a-0000-4000-8000-000000000001"}]}}
|
||||||
|
{"type":"turn/start","seq":1,"time":1783951000002,"data":{"turn":1}}
|
||||||
|
{"type":"agent/inbox/spliced","seq":2,"time":1783951000002,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||||
|
{"type":"step/start","seq":3,"time":1783951000003,"data":{"turn":1,"step":1}}
|
||||||
|
{"type":"user/message","seq":4,"time":1783951000003,"data":{"content":[{"type":"text","text":"Use read_image on red.png in the current directory. If the tool refuses because the current model is text-only, reply with exactly the single word UNAVAILABLE."}],"source":{"kind":"user"},"role":"user","id":"0a0a0a0a-0000-4000-8000-000000000001"},"surfaceOp":"append"}
|
||||||
|
{"type":"user/message","seq":5,"time":1786344284632,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"11a08f07-014a-408b-bfc5-634770ce7179"},"surfaceOp":"append"}
|
||||||
|
{"type":"session/title","seq":6,"time":1786344284632,"data":{"title":"Use read_image on red.png in","messageSeqs":[4],"source":{"kind":"fallback"}}}
|
||||||
|
{"type":"request/header","seq":7,"time":1786344284632,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||||
|
{"type":"request/context","seq":8,"time":1786344284633,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||||
|
{"type":"assistant/chunk","seq":9,"time":1783951000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||||
|
{"type":"assistant/chunk","seq":10,"time":1786344284637,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"read-image-refused","name":"read_image","arguments":"{\"file_path\":\"red.png\"}"}}}}
|
||||||
|
{"type":"assistant/chunk","seq":11,"time":1786344284637,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||||
|
{"type":"assistant/chunk","seq":12,"time":1786344284637,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||||
|
{"type":"assistant/message","seq":13,"time":1786344284638,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"read-image-refused","name":"read_image","arguments":"{\"file_path\":\"red.png\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"9676ac40-f7a8-4a7b-9326-a45fef18f11e"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12],"surfaceOp":"append"}
|
||||||
|
{"type":"tool/call","seq":14,"time":1786344284638,"data":{"turn":1,"step":1,"callId":"read-image-refused","name":"read_image","arguments":"{\"file_path\":\"red.png\"}"}}
|
||||||
|
{"type":"tool/result","seq":15,"time":1786344284643,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"read-image-refused"},"content":[{"type":"tool-result","toolCallId":"read-image-refused","content":[{"type":"text","text":"Error: cannot read \"red.png\" as an image: model \"deepseek-v4-flash\" does not declare image input; switch to an image-capable model to read images"}],"isError":true}],"role":"user","id":"ee31751e-df5a-458e-8497-8113cf6107ef"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||||
|
{"type":"step/end","seq":16,"time":1786344284643,"data":{"turn":1,"step":1}}
|
||||||
|
{"type":"step/start","seq":17,"time":1786344284648,"data":{"turn":1,"step":2}}
|
||||||
|
{"type":"assistant/chunk","seq":18,"time":1783951000016,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||||
|
{"type":"assistant/chunk","seq":19,"time":1786344284652,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"UNAVAILABLE"}}}}
|
||||||
|
{"type":"assistant/chunk","seq":20,"time":1786344284652,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||||
|
{"type":"assistant/chunk","seq":21,"time":1786344284652,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||||
|
{"type":"assistant/message","seq":22,"time":1786344284652,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"UNAVAILABLE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1c15b391-a95a-4113-9d47-2a1dfc991cf9"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21],"surfaceOp":"append"}
|
||||||
|
{"type":"step/end","seq":23,"time":1786344284653,"data":{"turn":1,"step":2}}
|
||||||
|
{"type":"turn/end","seq":24,"time":1786344284653,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
|
||||||
|
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
|
||||||
|
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"UNAVAILABLE"}}}}
|
||||||
|
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 69 B |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "initialize"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "newSession"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "prompt",
|
||||||
|
"text": "Use read_image to look at red.png in the current directory, then reply with exactly the single word DONE."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{"type":"session","version":0,"id":"33333333-3333-4333-8333-333333333333","createdAt":1783951000000,"cwd":"{{cwd}}","delegationDepth":0}
|
||||||
|
{"type":"agent/inbox/spliced","seq":0,"time":1783951000001,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use read_image to look at red.png in the current directory, then reply with exactly the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"0a0a0a0a-0000-4000-8000-000000000001"}]}}
|
||||||
|
{"type":"turn/start","seq":1,"time":1783951000002,"data":{"turn":1}}
|
||||||
|
{"type":"agent/inbox/spliced","seq":2,"time":1783951000002,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||||
|
{"type":"step/start","seq":3,"time":1783951000003,"data":{"turn":1,"step":1}}
|
||||||
|
{"type":"user/message","seq":4,"time":1783951000003,"data":{"content":[{"type":"text","text":"Use read_image to look at red.png in the current directory, then reply with exactly the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"0a0a0a0a-0000-4000-8000-000000000001"},"surfaceOp":"append"}
|
||||||
|
{"type":"user/message","seq":5,"time":1786344283033,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"eecd1df6-153c-4a34-b198-42bfc9f9701e"},"surfaceOp":"append"}
|
||||||
|
{"type":"session/title","seq":6,"time":1786344283033,"data":{"title":"Use read_image to look at","messageSeqs":[4],"source":{"kind":"fallback"}}}
|
||||||
|
{"type":"request/header","seq":7,"time":1786344283034,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||||
|
{"type":"request/context","seq":8,"time":1786344283034,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||||
|
{"type":"assistant/chunk","seq":9,"time":1783951000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||||
|
{"type":"assistant/chunk","seq":10,"time":1786344283039,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"read-image-call","name":"read_image","arguments":"{\"file_path\":\"red.png\"}"}}}}
|
||||||
|
{"type":"assistant/chunk","seq":11,"time":1786344283039,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||||
|
{"type":"assistant/chunk","seq":12,"time":1786344283039,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||||
|
{"type":"assistant/message","seq":13,"time":1786344283039,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"read-image-call","name":"read_image","arguments":"{\"file_path\":\"red.png\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"41e9fb55-6edb-419d-b76c-554daa5a1c5d"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12],"surfaceOp":"append"}
|
||||||
|
{"type":"tool/call","seq":14,"time":1786344283039,"data":{"turn":1,"step":1,"callId":"read-image-call","name":"read_image","arguments":"{\"file_path\":\"red.png\"}"}}
|
||||||
|
{"type":"tool/result","seq":15,"time":1786344283069,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"read-image-call"},"content":[{"type":"tool-result","toolCallId":"read-image-call","content":[{"type":"text","text":"<path>{{cwd}}/red.png</path>\n<type>image</type>\n<content>\nimage/png image, 1x1 px, 69 bytes\n</content>"},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","bytes":69,"width":1,"height":1,"name":"red.png"}}],"isError":false}],"role":"user","id":"0b5779fc-523e-4275-9a32-8eb5e39f521e"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||||
|
{"type":"step/end","seq":16,"time":1786344283069,"data":{"turn":1,"step":1}}
|
||||||
|
{"type":"step/start","seq":17,"time":1786344283075,"data":{"turn":1,"step":2}}
|
||||||
|
{"type":"assistant/chunk","seq":18,"time":1783951000016,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||||
|
{"type":"assistant/chunk","seq":19,"time":1786344283078,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
|
||||||
|
{"type":"assistant/chunk","seq":20,"time":1786344283079,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||||
|
{"type":"assistant/chunk","seq":21,"time":1786344283079,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||||
|
{"type":"assistant/message","seq":22,"time":1786344283079,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"73a87a50-8e0b-42af-8c54-d9b6fbe375f1"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21],"surfaceOp":"append"}
|
||||||
|
{"type":"step/end","seq":23,"time":1786344283079,"data":{"turn":1,"step":2}}
|
||||||
|
{"type":"turn/end","seq":24,"time":1786344283079,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
|
||||||
|
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
|
||||||
|
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"DONE"}}}}
|
||||||
|
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}
|
||||||
@@ -0,0 +1,543 @@
|
|||||||
|
{
|
||||||
|
"initial": [
|
||||||
|
{
|
||||||
|
"name": "bash",
|
||||||
|
"description": "Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The bash command to execute."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."
|
||||||
|
},
|
||||||
|
"timeoutMs": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."
|
||||||
|
},
|
||||||
|
"workdir": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."
|
||||||
|
},
|
||||||
|
"run_in_background": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."
|
||||||
|
},
|
||||||
|
"sandbox_permissions": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.",
|
||||||
|
"enum": [
|
||||||
|
"workspace-write",
|
||||||
|
"danger-full-access"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"justification": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"command",
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "create_goal",
|
||||||
|
"description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objective": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The concrete completion objective inferred from the direct human request."
|
||||||
|
},
|
||||||
|
"max_goal_rounds": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Optional positive safe-integer limit on automatic continuation rounds."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"objective"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "edit",
|
||||||
|
"description": "Edit an existing UTF-8 text file by replacing literal text.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to edit, resolved by the filesystem backend."
|
||||||
|
},
|
||||||
|
"old_string": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Literal text to replace. Must match exactly."
|
||||||
|
},
|
||||||
|
"new_string": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Literal replacement text. Use an empty string to delete the match."
|
||||||
|
},
|
||||||
|
"replace_all": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
|
||||||
|
},
|
||||||
|
"sandbox_permissions": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.",
|
||||||
|
"enum": [
|
||||||
|
"workspace-write",
|
||||||
|
"danger-full-access"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"justification": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"file_path",
|
||||||
|
"old_string",
|
||||||
|
"new_string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get_goal",
|
||||||
|
"description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "interrupt_agent",
|
||||||
|
"description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"agent_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The agent id of the running agent to interrupt."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"agent_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "list_agents",
|
||||||
|
"description": "List your continuable background subagents by durable id and label. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and complete means it exists only in storage — a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"scope": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "children (default) lists direct children only; descendants walks the complete tree below you.",
|
||||||
|
"enum": [
|
||||||
|
"children",
|
||||||
|
"descendants"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ralph",
|
||||||
|
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objective": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The immutable completion objective for every fresh Ralph round."
|
||||||
|
},
|
||||||
|
"maxRounds": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Optional positive safe-integer round cap, bounded by the deployment ceiling."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"objective"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read",
|
||||||
|
"description": "Read a UTF-8 text file and return line-numbered content.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to read, resolved by the filesystem backend."
|
||||||
|
},
|
||||||
|
"offset": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "1-based first line to return. Defaults to 1."
|
||||||
|
},
|
||||||
|
"limit": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum number of lines to return. Defaults to 2000."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"file_path"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read_image",
|
||||||
|
"description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to the image file, resolved by the filesystem backend."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"file_path"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "send_message",
|
||||||
|
"description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"subagent_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The subagent id returned when the background subagent was started."
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The message to deliver to the subagent."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"subagent_id",
|
||||||
|
"message"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skill",
|
||||||
|
"description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The exact skill name from the available skills list."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "subagent",
|
||||||
|
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. Set `run_in_background: true` to start a background subagent that keeps its conversation: you receive only its subagent id, never its result, and it works on its own. Use this for work whose result you do not need returned by this call; `send_message` sends it more work.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A short (3-5 word) description of the delegated task, for display."
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||||
|
},
|
||||||
|
"run_in_background": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run as a background subagent that keeps its conversation and return only its subagent id. This call never returns its result; send it more work with send_message."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"description",
|
||||||
|
"prompt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "subagent_fork",
|
||||||
|
"description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn), returning only its final result. Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive only its final answer, not its intermediate steps. Set `run_in_background: true` to start a background subagent that keeps its conversation: you receive only its subagent id, never its result, and it works on its own. Use this for work whose result you do not need returned by this call; `send_message` sends it more work.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A short (3-5 word) description of the delegated task, for display."
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."
|
||||||
|
},
|
||||||
|
"run_in_background": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run as a background subagent that keeps its conversation and return only its subagent id. This call never returns its result; send it more work with send_message."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"description",
|
||||||
|
"prompt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_kill",
|
||||||
|
"description": "Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Task id returned by the tool that started the background work."
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional short reason, recorded in the log and forwarded to the task."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"task_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_list",
|
||||||
|
"description": "List your background tasks (running and finished) with their ids, kinds, and statuses.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_output",
|
||||||
|
"description": "Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Task id returned by the tool that started the background work."
|
||||||
|
},
|
||||||
|
"wait": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."
|
||||||
|
},
|
||||||
|
"timeout_ms": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"task_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "todo_write",
|
||||||
|
"description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"todos": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "The COMPLETE task list, replacing any previous list.",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the task is — a short imperative line."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "pending (not started) | in_progress (now) | completed (done).",
|
||||||
|
"enum": [
|
||||||
|
"pending",
|
||||||
|
"in_progress",
|
||||||
|
"completed"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"content",
|
||||||
|
"status"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"todos"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "update_goal",
|
||||||
|
"description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"goal_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Exact id returned by get_goal."
|
||||||
|
},
|
||||||
|
"revision": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Exact positive revision returned by get_goal."
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "edit | pause | resume | complete | blocked",
|
||||||
|
"enum": [
|
||||||
|
"edit",
|
||||||
|
"pause",
|
||||||
|
"resume",
|
||||||
|
"complete",
|
||||||
|
"blocked"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"objective": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Replacement objective; valid only with action edit."
|
||||||
|
},
|
||||||
|
"max_goal_rounds": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Replacement cap; valid only with action edit."
|
||||||
|
},
|
||||||
|
"blocked_reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Concrete blocking condition; required only with action blocked."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"goal_id",
|
||||||
|
"revision",
|
||||||
|
"action"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workflow",
|
||||||
|
"description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return <value>` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise<any>` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise<any[]>` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise<any[]>` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"script": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return <json-value>`)."
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The workflow identity block (plain JSON — never code).",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Short kebab-case workflow name."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "One-line description of what the workflow does."
|
||||||
|
},
|
||||||
|
"whenToUse": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional guidance on when this workflow applies."
|
||||||
|
},
|
||||||
|
"phases": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Optional phase declarations matched by phase() calls.",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The phase title phase() calls match by exact string."
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional one-line description of the phase."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional provider override this phase is expected to use."
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional model override this phase is expected to use."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"args": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"script",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "write",
|
||||||
|
"description": "Create or fully replace a UTF-8 text file.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to write, resolved by the filesystem backend."
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Full UTF-8 text content to write."
|
||||||
|
},
|
||||||
|
"sandbox_permissions": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.",
|
||||||
|
"enum": [
|
||||||
|
"workspace-write",
|
||||||
|
"danger-full-access"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"justification": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"file_path",
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"changes": []
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 69 B |
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write examples/headless-agent/README.md
|
# pnpm run verify-translation-pairing --write examples/headless-agent/README.md
|
||||||
README.md: 08b36e9c8e558db3b30175b20bd6e0af160ccef5
|
README.md: 2f854924d4bfd2bf66b3d6f47433136098d7b762
|
||||||
README.zh.md: 40d11ebeeb1631af1421b3c013a5e548fd3fd488
|
README.zh.md: 4128645da8a0f8a84b17d819fc614051b68f2a11
|
||||||
@@ -10,10 +10,10 @@ This directory owns the replay and real-model test composition for a headless co
|
|||||||
# repo root .env (gitignored) or exported env:
|
# repo root .env (gitignored) or exported env:
|
||||||
# DEEPSEEK_API_KEY=sk-…
|
# DEEPSEEK_API_KEY=sk-…
|
||||||
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
|
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
|
||||||
pnpm run dsh --profile headless "fix the failing test in this workspace"
|
pnpm dsh --profile headless "fix the failing test in this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
The product command is [`dsh --profile headless`](../../apps/cli/README.md): it accepts one nonblank task, creates and persists a fresh session, prints the final assistant text, and exits. The root `demo:headless` script is only an alias of that command.
|
The product command is [`dsh --profile headless`](../../apps/cli/README.md): it accepts one nonblank task, creates and persists a fresh session, prints the final assistant text, and exits.
|
||||||
|
|
||||||
Snapshot suites run this directory's configuration through [`tests/fixtures/headless-driver.ts`](tests/fixtures/headless-driver.ts), an unexported test-only process that emits canonical session events as JSONL before its result record. That stream is test infrastructure, not a supported CLI output format. Child sessions surface only through parent tool events and results.
|
Snapshot suites run this directory's configuration through [`tests/fixtures/headless-driver.ts`](tests/fixtures/headless-driver.ts), an unexported test-only process that emits canonical session events as JSONL before its result record. That stream is test infrastructure, not a supported CLI output format. Child sessions surface only through parent tool events and results.
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
# repo root .env (gitignored) or exported env:
|
# repo root .env (gitignored) or exported env:
|
||||||
# DEEPSEEK_API_KEY=sk-…
|
# DEEPSEEK_API_KEY=sk-…
|
||||||
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
|
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
|
||||||
pnpm run dsh --profile headless "fix the failing test in this workspace"
|
pnpm dsh --profile headless "fix the failing test in this workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
产品命令是 [`dsh --profile headless`](../../apps/cli/README.md):它接受一项非空任务,创建并持久化新会话,打印最终 assistant 文本,然后退出。根目录的 `demo:headless` 脚本只是该命令的别名。
|
产品命令是 [`dsh --profile headless`](../../apps/cli/README.md):它接受一项非空任务,创建并持久化新会话,打印最终 assistant 文本,然后退出。
|
||||||
|
|
||||||
快照套件通过 [`tests/fixtures/headless-driver.ts`](tests/fixtures/headless-driver.ts) 运行本目录的配置。这个未导出且仅供测试使用的进程会在结果记录之前,以 JSONL 发出规范会话事件。该事件流属于测试基础设施,不是受支持的 CLI(命令行界面)输出格式。子会话只通过父会话的工具事件和结果对外显示。
|
快照套件通过 [`tests/fixtures/headless-driver.ts`](tests/fixtures/headless-driver.ts) 运行本目录的配置。这个未导出且仅供测试使用的进程会在结果记录之前,以 JSONL 发出规范会话事件。该事件流属于测试基础设施,不是受支持的 CLI(命令行界面)输出格式。子会话只通过父会话的工具事件和结果对外显示。
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@deepseek-ai/dsh-agent-loop": "workspace:*",
|
"@deepseek-ai/dsh-agent-loop": "workspace:*",
|
||||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:*",
|
"@deepseek-ai/dsh-agent-spine-demo": "workspace:*",
|
||||||
"@deepseek-ai/dsh-app-boot": "workspace:*",
|
"@deepseek-ai/dsh-app-boot": "workspace:*",
|
||||||
|
"@deepseek-ai/dsh-attachment-local": "workspace:*",
|
||||||
"@deepseek-ai/dsh-bash": "workspace:*",
|
"@deepseek-ai/dsh-bash": "workspace:*",
|
||||||
"@deepseek-ai/dsh-bash-env": "workspace:*",
|
"@deepseek-ai/dsh-bash-env": "workspace:*",
|
||||||
"@deepseek-ai/dsh-bash-local": "workspace:*",
|
"@deepseek-ai/dsh-bash-local": "workspace:*",
|
||||||
@@ -27,8 +28,8 @@
|
|||||||
"@deepseek-ai/dsh-compact-tool-result-prune": "workspace:*",
|
"@deepseek-ai/dsh-compact-tool-result-prune": "workspace:*",
|
||||||
"@deepseek-ai/dsh-credentials-local": "workspace:*",
|
"@deepseek-ai/dsh-credentials-local": "workspace:*",
|
||||||
"@deepseek-ai/dsh-e2b": "workspace:*",
|
"@deepseek-ai/dsh-e2b": "workspace:*",
|
||||||
"@deepseek-ai/dsh-fs-local": "workspace:*",
|
|
||||||
"@deepseek-ai/dsh-fs-e2b": "workspace:*",
|
"@deepseek-ai/dsh-fs-e2b": "workspace:*",
|
||||||
|
"@deepseek-ai/dsh-fs-local": "workspace:*",
|
||||||
"@deepseek-ai/dsh-fs-policy": "workspace:*",
|
"@deepseek-ai/dsh-fs-policy": "workspace:*",
|
||||||
"@deepseek-ai/dsh-fs-sandbox": "workspace:^",
|
"@deepseek-ai/dsh-fs-sandbox": "workspace:^",
|
||||||
"@deepseek-ai/dsh-goal": "workspace:*",
|
"@deepseek-ai/dsh-goal": "workspace:*",
|
||||||
@@ -76,8 +77,8 @@
|
|||||||
"@deepseek-ai/dsh-subagent-dsh-sdk": "workspace:*",
|
"@deepseek-ai/dsh-subagent-dsh-sdk": "workspace:*",
|
||||||
"@deepseek-ai/dsh-subagent-fork": "workspace:*",
|
"@deepseek-ai/dsh-subagent-fork": "workspace:*",
|
||||||
"@deepseek-ai/dsh-subagent-spawn": "workspace:*",
|
"@deepseek-ai/dsh-subagent-spawn": "workspace:*",
|
||||||
"@deepseek-ai/dsh-subprocess-local": "workspace:*",
|
|
||||||
"@deepseek-ai/dsh-subprocess-e2b": "workspace:*",
|
"@deepseek-ai/dsh-subprocess-e2b": "workspace:*",
|
||||||
|
"@deepseek-ai/dsh-subprocess-local": "workspace:*",
|
||||||
"@deepseek-ai/dsh-system-prompt": "workspace:*",
|
"@deepseek-ai/dsh-system-prompt": "workspace:*",
|
||||||
"@deepseek-ai/dsh-tasks-local": "workspace:*",
|
"@deepseek-ai/dsh-tasks-local": "workspace:*",
|
||||||
"@deepseek-ai/dsh-time-context": "workspace:*",
|
"@deepseek-ai/dsh-time-context": "workspace:*",
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ pre-commit:
|
|||||||
- '.agents/notes/archived/**'
|
- '.agents/notes/archived/**'
|
||||||
run: node_modules/.bin/tsx scripts/verify-translation-pairing.ts --cached {staged_files}
|
run: node_modules/.bin/tsx scripts/verify-translation-pairing.ts --cached {staged_files}
|
||||||
|
|
||||||
|
- name: archived agent notes
|
||||||
|
glob: '.agents/notes/archived/**'
|
||||||
|
run: node_modules/.bin/tsx scripts/verify-archived-agent-notes.ts
|
||||||
|
|
||||||
- name: lint (staged)
|
- name: lint (staged)
|
||||||
glob: '*.{ts,tsx,mts,cts,mjs}'
|
glob: '*.{ts,tsx,mts,cts,mjs}'
|
||||||
exclude:
|
exclude:
|
||||||
@@ -41,6 +45,10 @@ pre-merge-commit:
|
|||||||
- '.agents/notes/archived/**'
|
- '.agents/notes/archived/**'
|
||||||
run: node_modules/.bin/tsx scripts/verify-translation-pairing.ts --cached {staged_files}
|
run: node_modules/.bin/tsx scripts/verify-translation-pairing.ts --cached {staged_files}
|
||||||
|
|
||||||
|
- name: archived agent notes
|
||||||
|
glob: '.agents/notes/archived/**'
|
||||||
|
run: node_modules/.bin/tsx scripts/verify-archived-agent-notes.ts
|
||||||
|
|
||||||
pre-push:
|
pre-push:
|
||||||
jobs:
|
jobs:
|
||||||
- name: typecheck
|
- name: typecheck
|
||||||
|
|||||||
+1
-3
@@ -127,12 +127,10 @@
|
|||||||
"release:pack": "tsx scripts/release/pack.ts",
|
"release:pack": "tsx scripts/release/pack.ts",
|
||||||
"release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts",
|
"release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts",
|
||||||
"release:publish": "tsx scripts/release/publish.ts",
|
"release:publish": "tsx scripts/release/publish.ts",
|
||||||
"dsh": "node --import tsx/esm apps/cli/src/bin.ts",
|
"dsh": "pnpm run build && node --import tsx/esm apps/cli/src/bin.ts",
|
||||||
"demo:headless": "node --import tsx/esm apps/cli/src/bin.ts --profile headless",
|
|
||||||
"demo:code-mode": "node scripts/demo-code-mode.mjs",
|
"demo:code-mode": "node scripts/demo-code-mode.mjs",
|
||||||
"demo:cordis": "node scripts/demo-cordis.mjs",
|
"demo:cordis": "node scripts/demo-cordis.mjs",
|
||||||
"demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml",
|
"demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml",
|
||||||
"demo:web": "npm run build && node --import tsx/esm apps/cli/src/bin.ts web",
|
|
||||||
"mock:llm": "node --import tsx packages/support/llm-mock-server/src/bin.ts",
|
"mock:llm": "node --import tsx packages/support/llm-mock-server/src/bin.ts",
|
||||||
"dev:web": "tsx scripts/dev-web.ts --poll",
|
"dev:web": "tsx scripts/dev-web.ts --poll",
|
||||||
"postinstall": "node scripts/install-lefthook.mjs"
|
"postinstall": "node scripts/install-lefthook.mjs"
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md
|
# pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md
|
||||||
README.md: 1d56b2b6d22c08574f8e361955bee1dbe2aca601
|
README.md: 9639f1c0a2ffe91fd509a2ffdf04be5f0895b700
|
||||||
README.zh.md: 5429a1322d0311f03c7c43946753a290e28cd936
|
README.zh.md: e8bf0374aad2be2311b6e72e91e41f02f403b48a
|
||||||
@@ -29,7 +29,7 @@ The Loader mounts entries concurrently, so a surface can already own the termina
|
|||||||
|
|
||||||
`cordis:group` is registered beside `cordis:include` so a composition can give one `isolate` realm to a provider and its consumers together. Both load through the ambient module pipeline rather than the included tree's own specifier resolution, which is what lets a composition outside this workspace — an agent preset under the Harness home — use a group row at all.
|
`cordis:group` is registered beside `cordis:include` so a composition can give one `isolate` realm to a provider and its consumers together. Both load through the ambient module pipeline rather than the included tree's own specifier resolution, which is what lets a composition outside this workspace — an agent preset under the Harness home — use a group row at all.
|
||||||
|
|
||||||
Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`, npm packages) resolve through the Cordis Loader's internal module loader. They resolve from the config directory by default; a closed runtime passes `bareModuleBaseUrl` to `boot` or `mountRootInclude` so its installed package tree remains authoritative even when the config lives inside another Node project. Relative specifiers always resolve against the config directory. Repository bins install Loader's optional `node-addon-require-builtin` peer; external callers must supply it or install plugins where plain Node import resolution can find them. The built `dsh-app-boot` artifact embeds the statically mounted Include implementation while leaving Loader external, so the include tree and host bind to one Loader peer. The `dsh` source launcher additionally maps manifest-declared workspace packages to their TypeScript source; its configuration gate requires every shipped raw/Web bare plugin to appear in the resolver manifest's `dependencies`.
|
Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`, npm packages) resolve through the Cordis Loader's internal module loader. They resolve from the config directory by default; a closed runtime passes `bareModuleBaseUrl` to `boot` or `mountRootInclude` so its installed package tree remains authoritative even when the config lives inside another Node project. Relative specifiers always resolve against the config directory. Repository bins install Loader's optional `node-addon-require-builtin` peer; external callers must supply it or install plugins where plain Node import resolution can find them. The built `dsh-app-boot` artifact embeds the statically mounted Include implementation while leaving Loader external, so the include tree and host bind to one Loader peer. The `pnpm dsh` source path additionally maps manifest-declared workspace packages to their TypeScript source; its configuration gate requires every shipped raw/Web bare plugin to appear in the resolver manifest's `dependencies`.
|
||||||
|
|
||||||
This package carries no loader hooks and no dev-mode surface. The [`dsh` app](../../../apps/cli/README.md) owns its Node source-launch hook and consumes these helpers for the boot sequence; built consumers continue to use plain Node package resolution.
|
This package carries no loader hooks and no dev-mode surface. The [`dsh` app](../../../apps/cli/README.md) owns its Node source-launch hook and consumes these helpers for the boot sequence; built consumers continue to use plain Node package resolution.
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Loader 并发挂载各个条目,因此当其他环节失败时,某个界面
|
|||||||
|
|
||||||
`cordis:group` 与 `cordis:include` 一并注册,使一份组装能把一个提供方与它的消费方放进同一个 `isolate` realm。两者都通过宿主的模块管线加载,而非被包含树自身的说明符解析,这正是让本工作区之外的组装——放在 Harness home 下的 agent preset——能够使用 group 行的原因。
|
`cordis:group` 与 `cordis:include` 一并注册,使一份组装能把一个提供方与它的消费方放进同一个 `isolate` realm。两者都通过宿主的模块管线加载,而非被包含树自身的说明符解析,这正是让本工作区之外的组装——放在 Harness home 下的 agent preset——能够使用 group 行的原因。
|
||||||
|
|
||||||
配置中的裸插件 specifier(`@deepseek-ai/dsh-*`、npm 包)通过 Cordis Loader 的内部模块 loader 解析。默认情况下,它们从配置目录解析;封闭运行时会向 `boot` 或 `mountRootInclude` 传入 `bareModuleBaseUrl`,使已安装包树保持权威,即使配置位于另一个 Node 项目中也不受遮蔽。相对 specifier 始终以配置目录为基准解析。仓库 bin 会安装 Loader 的可选 peer `node-addon-require-builtin`;外部调用方必须提供该组件,或者把插件安装到普通 Node import 解析可以找到的位置。构建后的 `dsh-app-boot` 产物内嵌静态挂载的 Include 实现,但仍将 Loader 保持为外部依赖,因此 include 树与宿主会绑定到同一个 Loader peer。`dsh` 源码启动器还会将 manifest(元数据清单)声明的 workspace 包映射到其 TypeScript 源码;其配置门禁要求每个随附的原始/Web 裸插件都出现在解析所用 manifest 的 `dependencies` 中。
|
配置中的裸插件 specifier(`@deepseek-ai/dsh-*`、npm 包)通过 Cordis Loader 的内部模块 loader 解析。默认情况下,它们从配置目录解析;封闭运行时会向 `boot` 或 `mountRootInclude` 传入 `bareModuleBaseUrl`,使已安装包树保持权威,即使配置位于另一个 Node 项目中也不受遮蔽。相对 specifier 始终以配置目录为基准解析。仓库 bin 会安装 Loader 的可选 peer `node-addon-require-builtin`;外部调用方必须提供该组件,或者把插件安装到普通 Node import 解析可以找到的位置。构建后的 `dsh-app-boot` 产物内嵌静态挂载的 Include 实现,但仍将 Loader 保持为外部依赖,因此 include 树与宿主会绑定到同一个 Loader peer。`pnpm dsh` 源码路径还会将 manifest(元数据清单)声明的 workspace 包映射到其 TypeScript 源码;其配置门禁要求每个随附的原始/Web 裸插件都出现在解析所用 manifest 的 `dependencies` 中。
|
||||||
|
|
||||||
此包不包含 loader 钩子,也不提供开发模式接口。[`dsh` 应用](../../../apps/cli/README.md) 持有自己的 Node 源码启动钩子,并在启动序列中使用这些 helper;构建后的消费方仍使用普通 Node 包解析。
|
此包不包含 loader 钩子,也不提供开发模式接口。[`dsh` 应用](../../../apps/cli/README.md) 持有自己的 Node 源码启动钩子,并在启动序列中使用这些 helper;构建后的消费方仍使用普通 Node 包解析。
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ class RecordingFileSystem extends FileSystem {
|
|||||||
return this.entries.get(target.targetKey)?.content ?? ''
|
return this.entries.get(target.targetKey)?.content ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async readBytes(_target: FsTarget, _signal: AbortSignal | undefined, _maxBytes: number): Promise<Uint8Array> {
|
||||||
|
throw new Error('not needed in workspace-context tests')
|
||||||
|
}
|
||||||
|
|
||||||
override async streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>> {
|
override async streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>> {
|
||||||
if (signal !== undefined) this.signals.push(signal)
|
if (signal !== undefined) this.signals.push(signal)
|
||||||
signal?.throwIfAborted()
|
signal?.throwIfAborted()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe('gen-tool-catalog collectToolCatalog', () => {
|
|||||||
it('boots every shipped tool package and harvests its model-facing schemas', async () => {
|
it('boots every shipped tool package and harvests its model-facing schemas', async () => {
|
||||||
const catalog = await collectToolCatalog()
|
const catalog = await collectToolCatalog()
|
||||||
const names = catalog.flatMap(entry => entry.schemas.map(s => s.name)).sort()
|
const names = catalog.flatMap(entry => entry.schemas.map(s => s.name)).sort()
|
||||||
expect(names).toEqual(['ask_user_question', 'bash', 'bash', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'interrupt_agent', 'list_agents', 'lsp', 'pwsh', 'ralph', 'read', 'report', 'run_code', 'send_message', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'str_replace_editor', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write'])
|
expect(names).toEqual(['ask_user_question', 'bash', 'bash', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'interrupt_agent', 'list_agents', 'lsp', 'pwsh', 'ralph', 'read', 'read_image', 'report', 'run_code', 'send_message', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'str_replace_editor', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write'])
|
||||||
// Every tool carries a JSON-Schema `parameters` object (what the model sees).
|
// Every tool carries a JSON-Schema `parameters` object (what the model sees).
|
||||||
for (const entry of catalog) {
|
for (const entry of catalog) {
|
||||||
for (const schema of entry.schemas) {
|
for (const schema of entry.schemas) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user