diff --git a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml index dcaeb8c34b..dad9c50a36 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md -2026-06-21-bounded-llm-request-recovery.md: e725a025f2d8b0d5e8eaf4137f07d8eab4448bf4 -2026-06-21-bounded-llm-request-recovery.zh.md: 9e2263b05888797eaaaeb82859730d1c4a728cea +2026-06-21-bounded-llm-request-recovery.md: eaed6c263cb062dafedb5a2b798a85e57ae9d01d +2026-06-21-bounded-llm-request-recovery.zh.md: 3b4caeabeb58ebfb85d0cf0fef4a86dc7656ebec diff --git a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md index e725a025f2..eaed6c263c 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md +++ b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md @@ -64,7 +64,7 @@ Before sleeping, `dsh-llm-retry` appends one non-surface `llm/retry` session eve The listener calls `next()` for a non-transient code, an exhausted policy budget, or an over-cap provider delay. This preserves composition with context-overflow recovery and later policy plugins. For an owned failure it records and awaits the delay, then returns `{ kind: 'retry' }` without delegating. Turn cancellation and plugin disposal end the wait without returning a retry; the loop's cancellation/disposal checks remain authoritative. -The agent-spine demo bundle loads the plugin so the shared stdio/TUI, one-shot CLI, ACP, and headless example compositions use the same provider-routed policy. The shipped Web composition also loads it, so browser and command-line requests use the same provider defaults. Library consumers retain explicit plugin composition: omitting the plugin leaves request failures terminal. +The `dsh-base` and `dsh-sdk-minimal` patches load the plugin as an explicit row, so base-backed profiles and the standalone SDK profile use the same provider-routed policy. Library consumers retain explicit plugin composition: omitting the plugin leaves request failures terminal. ### Make one layer own visible attempts diff --git a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md index 9e2263b058..3b4caeabeb 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md @@ -64,7 +64,7 @@ agent loop(智能体循环)会将终止 finish 的 `LlmFailure` 传给 `agen 对非暂时性 code、耗尽的策略预算或超出上限的提供方延迟,监听器会调用 `next()`。这保留了与上下文溢出恢复及后续策略插件的组合能力。对自身处理的失败,它会记录并等待延迟,然后在不委托的情况下返回 `{ kind: 'retry' }`。轮次取消和插件 dispose 会结束等待且不返回重试动作,此后仍以循环的取消/dispose 检查为准。 -agent-spine 演示组合包加载该插件,因此共享的 stdio/TUI、一次性 CLI(命令行界面)、ACP(Agent Client Protocol)和 headless 示例组合使用同一套按提供方路由的策略。随产品交付的 Web 组合也会加载该插件,因此浏览器请求与命令行请求使用相同的提供方默认值。库消费方仍需显式组合插件:省略该插件时,请求失败保持终态。 +`dsh-base` 与 `dsh-sdk-minimal` patch 将该插件作为显式配置行加载,因此基于 base 的 profile 与独立 SDK profile 使用同一套按提供方路由的策略。库消费方仍需显式组合插件:省略该插件时,请求失败保持终态。 ### 由单一层负责可见的尝试 diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml index 7c864f90ad..8e3a2bc01c 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md -2026-07-19-package-owned-invariant-service.md: f1918ec1d31f9d91538b6b92070d98217567c5c5 -2026-07-19-package-owned-invariant-service.zh.md: 81e8e0361e2ebe4d34dae1c064f928fb57c49b8a +2026-07-19-package-owned-invariant-service.md: 88fb870eac6f72307fa2bfeaa714fe59a6a9130d +2026-07-19-package-owned-invariant-service.zh.md: 46b7789034c4a359fed95744e403f0a3a4dee6de diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md b/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md index f1918ec1d3..88fb870eac 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md +++ b/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md @@ -74,7 +74,7 @@ The generated scoped-event subject resolver lives in `dsh-scope`, beside the con ### Example composition and SDK output -The example agent spine mounts the service and all four stateful companion subpaths, forwarding `enabled`, `package_allowlist`, and `package_blocklist` to the service. Generated SDK Cordis composition emits the same entries. A subpath entry adds its installable root npm package rather than treating the subpath as a package name. The shipped `dsh` TUI and Web config trees omit the service and companions under the [shipped-config decision](../simplification/2026-08-03-omit-invariants-from-shipped-config.md). +The `dsh-sdk-minimal` patch mounts the service and all four stateful companion subpaths as explicit rows. A subpath entry adds its installable root npm package rather than treating the subpath as a package name. The shipped base-backed config trees omit the service and companions under the [shipped-config decision](../simplification/2026-08-03-omit-invariants-from-shipped-config.md). Workspace constraints recognize the separate invariant bundle, and package exports, project references, build configuration, dependency declarations, and the lockfile describe the same publication metadata. Generated config catalogs, module graphs, and API documentation derive from those sources. diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md b/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md index 81e8e0361e..46b7789034 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md @@ -74,7 +74,7 @@ blocklist 匹配优先于 allowlist 匹配。每个条目都是区分大小写 ### 示例组合与 SDK 输出 -示例 agent 主干会挂载服务和四个有状态伴随子路径,并把 `enabled`、`package_allowlist` 与 `package_blocklist` 转发给服务。生成的 SDK Cordis 组合输出相同条目。子路径条目添加可安装的根 npm 包,而不会把子路径误当成包名。根据[交付配置决策](../simplification/2026-08-03-omit-invariants-from-shipped-config.zh.md),交付的 `dsh` TUI 与 Web 配置树会省略该服务及其伴随插件。 +`dsh-sdk-minimal` patch 将该服务与四个有状态配套子路径作为显式配置行挂载。子路径配置行会添加可安装的根 npm 包,而不会把子路径误当成包名。根据[交付配置决策](../simplification/2026-08-03-omit-invariants-from-shipped-config.zh.md),交付的、基于 base 的配置树会省略该服务及其配套插件。 Workspace 约束识别独立的不变式 bundle;包 exports、项目引用、构建配置、依赖声明和 lockfile 描述同一份发布元数据。生成的配置目录、模块图和 API 文档都从这些源派生。 diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml index d979bf13dc..4af7abd046 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md -2026-07-24-single-harness-home-resolver.md: 0caeed28c30d19dace21375b4794ce6cf93a5aa1 -2026-07-24-single-harness-home-resolver.zh.md: 2cb8245ebc8cae698556cecd12ed684159c6dbc5 +2026-07-24-single-harness-home-resolver.md: 2351766e73167a6afd241c87f733aeff326bc6ac +2026-07-24-single-harness-home-resolver.zh.md: b1c6db85941c440e4b34862ab11a4331fdd98753 diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md index 0caeed28c3..2351766e73 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md @@ -23,7 +23,7 @@ explicit configured path > $DSH_HOME > ~/.dsh An empty or whitespace-only `$DSH_HOME` is treated as unset; otherwise `resolve('')` would silently place the home at the current working directory. The harness keeps all user data under one root; there is no XDG config/data/cache split. `dshHomePath(...segments)` joins deployment-owned children onto that root, and `dsh-app-boot` exposes it to Loader `!!js` config expressions before mounting entries, so shipped compositions derive `sessions` and `storages` without copying the resolver. `dshHomeDisplay()` names a resolved root symbolically for user-facing paths — `~/.dsh` for the default home, `$DSH_HOME` for any configured home — so the user-global `AGENTS.md` label never leaks an absolute machine path. It replaces agent-instructions's bespoke default-vs-`$DSH_HOME` check. -`@deepseek-ai/dsh-home` is deleted. Its three importers (`dsh-tool-bash`, `dsh-skill-filesystem`, `dsh-agent-spine-demo`) import `resolveDshHome` from `dsh-home-paths`. +`@deepseek-ai/dsh-home` is deleted. Home-owning providers and boot packages import `resolveDshHome` from `dsh-home-paths`; composition bundles contain only the resolved configuration rows. `dsh-telemetry` and its separate home policy are absent under the [SDK project toolchain removal](../simplification/2026-08-11-remove-sdk-project-toolchain.md), leaving this resolver as the sole home policy. diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md index 2cb8245ebc..b1c6db8594 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md @@ -23,7 +23,7 @@ explicit configured path > $DSH_HOME > ~/.dsh 空或仅含空白的 `$DSH_HOME` 被当作未设置处理;否则,`resolve('')` 会悄悄把 home 落在当前工作目录。harness 把所有用户数据都放在同一个根目录下;不存在 XDG 的 config/data/cache 拆分。`dshHomePath(...segments)` 将部署负责的子路径拼接到该根目录下,`dsh-app-boot` 在挂载条目前向 Loader `!!js` 配置表达式暴露它,因此出厂组合无需复制解析器即可派生 `sessions` 和 `storages`。`dshHomeDisplay()` 为面向用户的路径以符号形式命名已解析的根目录——默认 home 显示为 `~/.dsh`,任何已配置的 home 显示为 `$DSH_HOME`——这样用户全局的 `AGENTS.md` 标签就绝不会泄露机器上的绝对路径。它取代了 agent-instructions 中自定义的「默认值 vs `$DSH_HOME`」判断。 -`@deepseek-ai/dsh-home` 被删除。它的三个引用方(`dsh-tool-bash`、`dsh-skill-filesystem`、`dsh-agent-spine-demo`)从 `dsh-home-paths` 导入 `resolveDshHome`。 +`@deepseek-ai/dsh-home` 被删除。拥有 home 配置的提供方与 boot 包从 `dsh-home-paths` 导入 `resolveDshHome`;组合包只包含解析后的配置行。 `dsh-telemetry` 及其独立 home 策略已随 [SDK 项目工具链移除](../simplification/2026-08-11-remove-sdk-project-toolchain.zh.md)一并消失,因此该解析器是唯一的 home 策略。 diff --git a/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.i18n.yaml index 4c1d7988e9..9c2d1f291f 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md -2026-07-26-job-registry-seam.md: fc344c9a9b24c13871475993dc52d7fdb92ed3be -2026-07-26-job-registry-seam.zh.md: 692937cfbae599b4dcaacdc31220a15f17a912aa +2026-07-26-job-registry-seam.md: 2040b3a40debd181ebbdb0f1a9916b1a10ddcc4b +2026-07-26-job-registry-seam.zh.md: 12785584f6cf9f121bcc6c6922994c06027fef58 diff --git a/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md b/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md index fc344c9a9b..2040b3a40d 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md +++ b/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md @@ -16,7 +16,7 @@ The [background-job runtime](2026-06-20-generic-long-running-tool-runtime.md) sh - **`@deepseek-ai/dsh-jobs-local` (Service Provider)** — `LocalJobRegistry`, the process-local registry: the in-memory store, per-kind id counters, waiter bookkeeping, `TASK_WAIT_TIMEOUT` deadline code, owner-cleanup effects, force-fail teardown, and the default-10 configurable admission policy. Admission derives `running` plus `stopping` capacity from the same records per exact owner, with one unowned bucket; it adds no public count or second state owner. The `dsh-timeout` dependency and Schemastery-owned provider config live here; the Service Definition package has no provider dependencies. - **`@deepseek-ai/dsh-tool-jobs` (Consumer)** — unchanged; it injects `'jobs'` and never imports provider types. -Compositions load `dsh-jobs-local` where they previously loaded `dsh-jobs` (the CLI cordis.yml row, `agent-spine-demo`, test harnesses, the tool-catalog generator boot). Producer misconfiguration diagnostics ("background jobs unavailable: load …") name `dsh-jobs` — the Service Definition package that declares the absent `ctx.jobs` service — and the Service Definition package's own APIs (its README and the direct-mount fence) point at Service Providers, so the producer message stays correct when another backend becomes the recommended default. Producers, `JobKindMap` declaration merges, and the controller keep importing `@deepseek-ai/dsh-jobs` only. +Compositions load `dsh-jobs-local` where they previously loaded `dsh-jobs` (`dsh-base`, `sdk-minimal`, test harnesses, and the tool-catalog generator boot). Producer misconfiguration diagnostics ("background jobs unavailable: load …") name `dsh-jobs` — the Service Definition package that declares the absent `ctx.jobs` service — and the Service Definition package's own APIs (its README and the direct-mount fence) point at Service Providers, so the producer message stays correct when another backend becomes the recommended default. Producers, `JobKindMap` declaration merges, and the controller keep importing `@deepseek-ai/dsh-jobs` only. The seam keeps the in-process contract semantics unchanged: `JobStart.run()` still passes callbacks and exact `Agent` objects, so a durable or cross-process backend still has design work to do before it can satisfy this Service Definition (identity, restart, ownership, observation). The split moves that future work out of every Consumer's dependency graph; it does not pre-design the backend. diff --git a/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.zh.md b/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.zh.md index 692937cfba..12785584f6 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.zh.md @@ -16,7 +16,7 @@ Status: implemented - **`@deepseek-ai/dsh-jobs-local`(Service Provider)**——`LocalJobRegistry`,即进程内注册表:内存存储、按 kind 划分的 id 计数器、等待方簿记、`TASK_WAIT_TIMEOUT` deadline 代码、所有者清理 effect、强制失败的拆除,以及默认值为 10 且可配置的准入策略。准入从同一组记录中按确切 owner 派生 `running` 加 `stopping` 容量,并为无 owner 任务使用一个共享桶;它不新增公开计数或第二个状态 owner。`dsh-timeout` 依赖与由 Schemastery 管理的 Service Provider 配置都位于此包;Service Definition 包不含任何提供方依赖。 - **`@deepseek-ai/dsh-tool-jobs`(Consumer)**——保持不变;它注入 `'jobs'`,从不导入提供方类型。 -各组合在原先加载 `dsh-jobs` 的位置改为加载 `dsh-jobs-local`:CLI(命令行界面)的 cordis.yml 配置项、`agent-spine-demo`、各测试 harness,以及工具目录生成器的启动流程。生产方的配置错误诊断信息(「background jobs unavailable: load …」)点名 `dsh-jobs`——即声明缺失的 `ctx.jobs` 服务的 Service Definition 包;Service Definition 包自身的 API(其 README 与直接挂载防线)会指向各 Service Provider,因此当另一个后端日后成为推荐默认时,生产方的消息依旧正确。生产方、`JobKindMap` 声明合并和控制器仍然只导入 `@deepseek-ai/dsh-jobs`。 +各组合在原先加载 `dsh-jobs` 的位置改为加载 `dsh-jobs-local`:`dsh-base`、`sdk-minimal`、各测试 harness,以及工具目录生成器的启动流程。生产方的配置错误诊断信息(「background jobs unavailable: load …」)点名 `dsh-jobs`——即声明缺失的 `ctx.jobs` 服务的 Service Definition 包;Service Definition 包自身的 API(其 README 与直接挂载防线)会指向各 Service Provider,因此当另一个后端日后成为推荐默认时,生产方的消息依旧正确。生产方、`JobKindMap` 声明合并和控制器仍然只导入 `@deepseek-ai/dsh-jobs`。 该 seam 保持进程内约定语义不变:`JobStart.run()` 仍然传入回调和确切的 `Agent` 对象,因此持久化或跨进程后端在能满足此 Service Definition 之前仍有设计工作要做(身份、重启、所有权、观察)。这次拆分把该项未来工作移出了每个 Consumer 的依赖图;它并不预先设计后端。 diff --git a/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.i18n.yaml index d0fe7a108e..549a861394 100644 --- a/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md -2026-08-24-standalone-sdk-minimal-profile.md: 9c8afbaaf1a8e522af119c1d42ca5ad714eaa879 -2026-08-24-standalone-sdk-minimal-profile.zh.md: b1cd2a348c4b77f197e30a658c13691b2f35d26e +2026-08-24-standalone-sdk-minimal-profile.md: 3c53ea86479742e5bfddd7c13f22d370af90f0e4 +2026-08-24-standalone-sdk-minimal-profile.zh.md: c6f4d705d9e2c3d7c4acd404f94aebc1e83b77f7 diff --git a/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md b/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md index 9c8afbaaf1..3c53ea8647 100644 --- a/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md +++ b/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md @@ -22,7 +22,7 @@ The bundle reuses `@deepseek-ai/dsh-sdk-app` for command help, stdin EOF, and bo ### Explicit composition -The bundle owns one DeepSeek adapter, SDK JSON-RPC serving, the executor-less agent spine, local subprocess and unrestricted filesystem providers, a platform-selected persistent shell, the string-replace editor, and uncompressed JSONL sessions under `$DSH_HOME/sessions`. Linux and macOS mount Bash; Windows mounts PowerShell. The SDK initialization request owns the model id; `DSH_CONTEXT_WINDOW` supplies fallback capacity for models outside the adapter's advisory catalog. The persona comes from `DSH_SYSTEM_PROMPT`, and the credential from `DEEPSEEK_API_KEY`. +The bundle owns one DeepSeek adapter, SDK JSON-RPC serving, the explicit agent core, local subprocess and unrestricted filesystem providers, a platform-selected persistent shell, the string-replace editor, and uncompressed JSONL sessions under `$DSH_HOME/sessions`. Linux and macOS mount Bash; Windows mounts PowerShell. The SDK initialization request owns the model id; `DSH_CONTEXT_WINDOW` supplies fallback capacity for models outside the adapter's advisory catalog. The persona comes from `DSH_SYSTEM_PROMPT`, and the credential from `DEEPSEEK_API_KEY`. Harness identity, runtime context, workspace instructions, skills, model-facing job controls, compaction, settings, managed credentials, telemetry, Web tools, subagents, and every other base row are absent rather than hidden. The profile pins `danger-full-access`, `maxTokensAsSuccess: false`, and startup-only patch loading. diff --git a/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md b/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md index b1cd2a348c..c6f4d705d9 100644 --- a/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md @@ -22,7 +22,7 @@ Status: implemented ### 显式组合 -该组合包拥有一个 DeepSeek 适配器、SDK JSON-RPC 服务、无执行器的 agent 主干、本地子进程与不受限文件系统提供方、按平台选择的持久 shell、字符串替换 editor,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 会话。Linux 与 macOS 挂载 Bash,Windows 挂载 PowerShell。SDK 初始化请求拥有模型 id;`DSH_CONTEXT_WINDOW` 为不在适配器建议目录中的模型提供后备容量。Persona 来自 `DSH_SYSTEM_PROMPT`,凭据来自 `DEEPSEEK_API_KEY`。 +该组合包拥有一个 DeepSeek 适配器、SDK JSON-RPC 服务、显式 agent 核心、本地子进程与不受限文件系统提供方、按平台选择的持久 shell、字符串替换 editor,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 会话。Linux 与 macOS 挂载 Bash,Windows 挂载 PowerShell。SDK 初始化请求拥有模型 id;`DSH_CONTEXT_WINDOW` 为不在适配器建议目录中的模型提供后备容量。Persona 来自 `DSH_SYSTEM_PROMPT`,凭据来自 `DEEPSEEK_API_KEY`。 Harness 身份、运行时上下文、workspace 指令、skills、面向模型的 job 控制、compaction、settings、托管凭据、遥测、Web 工具、subagent 与其他所有 base 配置项均不存在,而不是被隐藏。该 profile 固定使用 `danger-full-access`、`maxTokensAsSuccess: false` 与仅启动时 patch 加载。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.i18n.yaml index 491a5c2f68..f23c1ad75b 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.md -2026-08-11-bounded-background-job-admission.md: 8fa5bec07647947b730c436284da71b83deedb48 -2026-08-11-bounded-background-job-admission.zh.md: c30e15f26d42acd93eecbc90856d1db64bacba2e +2026-08-11-bounded-background-job-admission.md: c9c27ef6e3a063687cf3491d42270079d7720679 +2026-08-11-bounded-background-job-admission.zh.md: cdeb80a64956c9ccce5eb4c4c3229688fa5577f8 diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.md b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.md index 8fa5bec076..c9c27ef6e3 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.md +++ b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.md @@ -12,7 +12,7 @@ The process-local job registry already owns the exact job owner and the authorit ## Decision -`LocalJobRegistry` owns a `maxConcurrentJobsPerOwner` configuration field. It accepts positive safe integers, defaults to `10`, and is available through the provider's Cordis schema, the typed `agent-spine-demo` bundle, and the ACP app configuration. The bundle transports the value; the process-local provider owns its meaning. +`LocalJobRegistry` owns a `maxConcurrentJobsPerOwner` configuration field. It accepts positive safe integers, defaults to `10`, and is available through the provider's Cordis schema and the ACP app configuration. Profile compositions configure the provider row directly; the process-local provider owns the value's meaning. The [generic job runtime decision](../architecture/2026-06-20-generic-long-running-tool-runtime.md) owns the shared Task lifecycle and control API; this note owns the process-local admission policy. diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.zh.md b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.zh.md index c30e15f26d..cdeb80a649 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-job-admission.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -`LocalJobRegistry` 拥有 `maxConcurrentJobsPerOwner` 配置字段。它只接受正的安全整数,默认值为 `10`,并通过 Service Provider 的 Cordis schema、typed `agent-spine-demo` 组合包与 ACP 应用配置提供。组合包只传输该值;其含义归进程内 Service Provider 所有。 +`LocalJobRegistry` 拥有 `maxConcurrentJobsPerOwner` 配置字段。它只接受正的安全整数,默认值为 `10`,并通过 Service Provider 的 Cordis schema 与 ACP 应用配置提供。profile 组合直接配置提供方配置行;该值的含义归进程内 Service Provider 所有。 [通用任务运行时决策](../architecture/2026-06-20-generic-long-running-tool-runtime.zh.md)拥有共享 Task 生命周期与控制 API;本记录只拥有进程内准入策略。 diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml index ce361aadbd..fbba8fd85c 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-skill-system.md -2026-07-05-skill-system.md: 650ecc0a943d3fd5419481f13d14e5a3a46e94f2 -2026-07-05-skill-system.zh.md: d3330060efe5c9b15752206405efcd562fa2691b +2026-07-05-skill-system.md: 21ad2c899e0f0507b5b09c0d64e7a0c54c73ce59 +2026-07-05-skill-system.zh.md: 44dbce33b5e655ebe511fe10fab91c4e08763538 diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.md index 650ecc0a94..21ad2c899e 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.md @@ -12,7 +12,7 @@ DeepSeek Harness uses the same primitive so project-specific review, plugin-auth ## Decision -`@deepseek-ai/dsh-skill` is the pure provider registry (`ctx.skills`), `@deepseek-ai/dsh-skill-filesystem` is the shipped local filesystem provider, and `@deepseek-ai/dsh-tool-skill` owns the durable session catalog and model-facing loader tool. `dsh-agent-spine-demo` loads the registry, local provider, and consumer by default so TUI, headless, and ACP apps get the same behavior while embedded or remote providers contribute skills without changing the registry or consumer. Its `skills` config forwards `registry`, `local`, and `tool` branches to those owners. +`@deepseek-ai/dsh-skill` is the pure provider registry (`ctx.skills`), `@deepseek-ai/dsh-skill-filesystem` is the shipped local filesystem provider, and `@deepseek-ai/dsh-tool-skill` owns the durable session catalog and model-facing loader tool. `dsh-base` loads the registry, local provider, and consumer as separate rows so its profiles get the same behavior while embedded or remote providers contribute skills without changing the registry or consumer. Each row exposes only its owning package's configuration. Dedicated packaged providers can contribute immutable skills without filesystem discovery. The shipped CLI declares `@deepseek-ai/dsh-skill-badge` disabled by default; enabling its composition row contributes the official badge instructions through the same registry and consumer (see [the package contract](../../../../packages/skill/skill-badge/README.md)). diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md index d3330060ef..44dbce33b5 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md @@ -12,7 +12,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 ## 决策 -`@deepseek-ai/dsh-skill` 是纯提供方注册表(`ctx.skills`),`@deepseek-ai/dsh-skill-filesystem` 是随附的本地文件系统提供方,`@deepseek-ai/dsh-tool-skill` 负责持久化会话目录与面向模型的 loader 工具。`dsh-agent-spine-demo` 默认加载注册表、本地提供方和消费方,使 TUI、headless 与 ACP(Agent Client Protocol)应用获得相同行为,同时嵌入式或远程提供方可在不修改注册表或消费方的前提下贡献 skill。其 `skills` 配置将 `registry`、`local` 和 `tool` 分支分别转发给对应的所有者。 +`@deepseek-ai/dsh-skill` 是纯提供方注册表(`ctx.skills`),`@deepseek-ai/dsh-skill-filesystem` 是随附的本地文件系统提供方,`@deepseek-ai/dsh-tool-skill` 负责持久化会话目录与面向模型的 loader 工具。`dsh-base` 将注册表、本地提供方和消费方作为独立配置行加载,使其各 profile 获得相同行为,同时嵌入式或远程提供方可在不修改注册表或消费方的前提下贡献 skill。每个配置行只暴露其所属包的配置。 专用的随包提供方可以贡献不可变的 skill,无需文件系统发现。交付的 CLI(命令行界面)默认将 `@deepseek-ai/dsh-skill-badge` 声明为禁用;启用其组合配置行,就会通过同一个注册表和消费方贡献官方徽章指令(见[包约定](../../../../packages/skill/skill-badge/README.zh.md))。 diff --git a/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.i18n.yaml index 69724a8400..a76283f9e0 100644 --- a/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md -2026-07-06-explicit-tool-order.md: 517e144e2cc2b7e438bb254a0d6962fbad75504c -2026-07-06-explicit-tool-order.zh.md: bbc29e154f1fe27abb6a793319e6ee6e0bbad065 +2026-07-06-explicit-tool-order.md: 735818ba5bbaabe6c3d33df844ca4b1fb29ad026 +2026-07-06-explicit-tool-order.zh.md: cb5fa1f7fb130b1e6819d07f64a5c8bd6fcd167b diff --git a/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md b/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md index 517e144e2c..735818ba5b 100644 --- a/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md +++ b/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md @@ -23,7 +23,7 @@ The system-prompt assembly owns the canonical model-facing tool order, exactly w Scope is deliberately narrow: this fixes the REGISTRATION-ORDER race, not plugin behavior. A `system-prompt/assemble` listener may still add, remove, or rearrange tools — same as it may edit sections after their sort — and owns the determinism of what it emits; the waterfall contract already demands deterministic listeners (the reconstructability invariant would catch a listener that diverges between build and replay). -Config plumbing follows the `persona` precedent, and `toolOrder` sits beside it: the TUI, Headless, and ACP app configs accept the key and forward it through `dsh-agent-spine-demo` (whose schema is the intersection of the owners' schemas) to the `SystemPrompt` child. One schemastery footnote is load-bearing: a schemastery array defaults to `[]`, but an omitted `toolOrder` must stay ABSENT (= lexicographic) rather than become an explicitly-configured empty list (invalid — it lacks the rest entry), so every schema on the chain forces the default to `undefined`. +Config plumbing follows the `persona` precedent, and `toolOrder` sits beside it on each composition's `dsh-system-prompt` row. One schemastery footnote is load-bearing: a schemastery array defaults to `[]`, but an omitted `toolOrder` must stay ABSENT (= lexicographic) rather than become an explicitly configured empty list (invalid — it lacks the rest entry), so every schema that accepts the field forces the default to `undefined`. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.zh.md b/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.zh.md index bbc29e154f..cb5fa1f7fb 100644 --- a/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.zh.md @@ -23,7 +23,7 @@ Status: implemented 范围刻意收窄:本 Agent Note 修复的是注册顺序竞态,而非插件行为。`system-prompt/assemble` 的监听器仍然可以添加、移除或重排工具——正如它可以在 section 排序之后编辑 section——并对自身输出的确定性负责;waterfall 约定已经要求监听器是确定性的(可重建性不变式会捕获在构建与回放之间行为不一致的监听器)。 -配置传递沿用 `persona` 的先例,`toolOrder` 与之并列:TUI、Headless 和 ACP 应用配置接受该键,并通过 `dsh-agent-spine-demo`(其 schema 是各所有者 schema 的交集)转发给 `SystemPrompt` 子服务。有一个 schemastery 细节至关重要:schemastery 数组默认为 `[]`,但省略的 `toolOrder` 必须保持 ABSENT(= 字典序),而不是变成一个显式配置的空列表(无效——缺少 rest 条目),因此链路上每个 schema 都将默认值强制为 `undefined`。 +配置传递沿用 `persona` 的先例,`toolOrder` 与之并列,位于每个组合的 `dsh-system-prompt` 配置行。有一个 schemastery 细节至关重要:schemastery 数组默认为 `[]`,但省略的 `toolOrder` 必须保持 ABSENT(= 字典序),而不是变成一个显式配置的空列表(无效——缺少 rest 条目),因此每个接受该字段的 schema 都将默认值强制为 `undefined`。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml index d850964aa9..f661b717ad 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md -2026-07-16-harness-level-loop.md: 9f9ee6fc3f6fbf089db7036aec08c400e7062cff -2026-07-16-harness-level-loop.zh.md: 718fceef0c54cc28690e63eec7b25ad7c4cf136d +2026-07-16-harness-level-loop.md: e9b0f16b17695aadaeb224760adbed4d78c7a87c +2026-07-16-harness-level-loop.zh.md: 8f21261e81a251caa4953b324dc726eb7d94c35c diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md index 9f9ee6fc3f..e9b0f16b17 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md +++ b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md @@ -70,7 +70,7 @@ The human UX follows the compact Codex shape in the [public OpenAI Codex TUI dis The model receives only `get_goal`, `create_goal`, and `update_goal`. It may create a goal when a direct human request clearly asks for substantial multi-round work, and it may infer that intent in any language. It must not turn routine one-turn work into a goal. Code requires a direct human message in the current live root-agent turn; semantic interpretation remains model judgment. An autonomous goal round may report `complete` or `blocked` for the exact current goal round but cannot edit, pause, resume, or replace the human objective. -TUI mounts the shared command registry and complete goal stack by default and exposes `/goal` through one producer. ACP mounts the goal domain, model tools, and same-session driver but deliberately omits the human command plane. Every effective registered command is discoverable and invocable through every composed command adapter; a plugin incompatible with an application omits its command producer from that composition rather than relying on registry-level surface masks. The UI-less agent spine is opt-in so one-shot callers do not silently become multi-round operations. The headless CLI and JSON-RPC entry points do not consume the command plane; ordinary human text can still authorize model goal tools when that stack is composed. +Base-backed profiles mount the shared command registry and complete goal stack by default and expose `/goal` through one producer. ACP mounts the goal domain, model tools, and same-session driver but deliberately omits the human command plane. Every effective registered command is discoverable and invocable through every composed command adapter; a plugin incompatible with an application omits its command producer from that composition rather than relying on registry-level surface masks. The standalone `sdk-minimal` tree omits the complete goal stack so one-shot callers do not silently become multi-round operations. Headless CLI and JSON-RPC entry points do not consume the command plane; ordinary human text can still authorize model goal tools when that stack is composed. ### Fresh-agent Ralph execution diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md index 718fceef0c..8f21261e81 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md +++ b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md @@ -70,7 +70,7 @@ Goal Round 驱动器为每个特定的实时 agent 至多拥有一个待定预 模型只接收 `get_goal`、`create_goal` 和 `update_goal`。当直接人类请求清楚要求大量多 Round 工作时,模型可以创建目标,并且可以从任何语言推断该意图。它不得把日常单轮次工作变成目标。代码要求当前实时根 agent 轮次中有一条人类直接发送的消息;语义解释仍是模型判断。自治目标 Round 可以为确切的当前 Goal Round 报告 `complete` 或 `blocked`,但不能编辑、暂停、恢复或替换人类目标。 -TUI 默认挂载共享命令注册表和完整目标栈,并通过一个生产方暴露 `/goal`。ACP(Agent Client Protocol)挂载目标领域、模型工具和同会话驱动器,但有意省略人类命令平面。每条有效已注册命令都能被每个已组合的命令适配器发现和调用;若插件与某应用不兼容,该应用组合会省略其命令生产方,而不是依赖注册表层面的表面掩码。无 UI 的 agent 主干要求显式选择加入,以免单次调用方静默变成多 Round 操作。无头 CLI(命令行界面)与 JSON-RPC 运行入口不消费命令平面;挂载目标栈后,普通人类文本仍可授权模型目标工具。 +基于 base 的 profile 默认挂载共享命令注册表和完整目标栈,并通过一个生产方暴露 `/goal`。ACP(Agent Client Protocol)挂载目标领域、模型工具和同会话驱动器,但有意省略人类命令平面。每条有效已注册命令都能被每个已组合的命令适配器发现和调用;若插件与某应用不兼容,该应用组合会省略其命令生产方,而不是依赖注册表层面的表面掩码。独立的 `sdk-minimal` 配置树省略完整 goal 栈,以免单次调用方静默变成多 Round 操作。无头 CLI(命令行界面)与 JSON-RPC 运行入口不消费命令平面;挂载 goal 栈后,普通人类文本仍可授权模型 goal 工具。 ### 全新 agent Ralph 执行 diff --git a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml index 6aaf55599a..b65f7950b4 100644 --- a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-human-goal-command.md -2026-07-19-human-goal-command.md: b4e47aa687d60aa5ea4d30ad08826fbaa3978393 -2026-07-19-human-goal-command.zh.md: fcb34d5acc02d44dcb68a1f3c511cbde495673ea +2026-07-19-human-goal-command.md: b87d52f2aa07dbd51248488804558a1760d4a30f +2026-07-19-human-goal-command.zh.md: 18d5829dafb326d034091b9fc46537f8aed3aa27 diff --git a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md index b4e47aa687..b87d52f2aa 100644 --- a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md +++ b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md @@ -38,7 +38,7 @@ Generic slash input, status text, and errors are not persisted. Successful goal ### App composition -`agent-spine-demo` accepts an optional `goals` composition object containing the goal-domain and model-tool owner configs. Omission or `false` leaves the stack unmounted. This explicit opt-in is important for headless one-shot callers: their result API settles one correlated physical turn and must not silently become a long-running logical goal operation. +`dsh-base` mounts the goal domain and model-tool owners as explicit rows, while the standalone `sdk-minimal` tree omits the complete stack. This explicit composition choice is important for SDK one-shot callers: their result API settles one correlated physical turn and must not silently become a long-running logical goal operation. The TUI app bundle makes the opposite product choice. It defaults `goals` to the owner defaults and mounts the goal domain, model tools, same-session driver, command registry, and this producer; `goals: false` removes the stack coherently. The Web bundle keeps the goal domain and driver on the host for remote access, disables the host command producer, and mounts the producer in the `standard`, `code`, and `cordis` agent presets; `minimal` omits both the command and model goal tools. A preset switch does not mutate host-owned goal state, and the Web GoalBar retains direct edit, pause, resume, and clear controls. The [ACP automation app](../simplification/2026-07-23-acp-automation-only-protocol.md) also defaults the goal domain and model tools but deliberately omits command services. The Python SDK runtime closure ships this producer, commands, and the goal stack so an external `cordis.yml` can compose the same command. diff --git a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md index fcb34d5acc..18d5829daf 100644 --- a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md @@ -38,7 +38,7 @@ Status: implemented ### 应用组合 -`agent-spine-demo` 接受可选的 `goals` 组合对象,其中包含目标领域与模型工具的所有者配置。省略或设为 `false` 时不会挂载该栈。对无头单次调用方而言,明确选择加入非常重要:它们的结果 API 会在与调用关联的一个物理轮次后结束,不能静默变成长时间运行的逻辑目标操作。 +`dsh-base` 将 goal 领域与模型工具所有者作为显式配置行挂载,独立的 `sdk-minimal` 配置树则省略完整栈。这项显式组合选择对 SDK 单次调用方很重要:它们的结果 API 会在与调用关联的一个物理轮次后结束,不能静默变成长时间运行的逻辑目标操作。 TUI 应用包作出相反的产品选择。它默认让 `goals` 使用所有者默认值,并挂载目标领域、模型工具、同会话驱动器、命令注册表与本生产方;`goals: false` 会一致地移除整个栈。Web 组合包把 goal 领域与驱动器保留在 host 中以供远程访问,停用 host 命令生产方,并在 `standard`、`code` 与 `cordis` agent preset 中挂载该生产方;`minimal` 会同时省略命令与模型 goal 工具。切换 preset 不会改变 host 所拥有的 goal 状态,Web GoalBar 仍保留直接 edit、pause、resume 与 clear 控制。[ACP(Agent Client Protocol)自动化应用](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)也默认挂载目标领域与模型工具,但有意省略命令服务。Python SDK 运行时闭包交付本生产方、命令与目标栈,使外部 `cordis.yml` 能组合相同命令。 diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml index 8fe2b69e2e..8a975fd9bd 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md -2026-07-19-plugin-command-registration.md: f2ea6fca14ef5a0d7057d2652ee5f511fc6c15e4 -2026-07-19-plugin-command-registration.zh.md: 69a86982b18479db4880a70f821bdfd6e4f9a575 +2026-07-19-plugin-command-registration.md: f5033c6ac52dd09371d67ba5b54f0aa2cdba8024 +2026-07-19-plugin-command-registration.zh.md: 55f3a58f0e1f4c1ee6dfd7105e968a1a466f7806 diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md index f2ea6fca14..f5033c6ac5 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md @@ -12,7 +12,7 @@ A shared mechanism must remain a UI concern rather than a model tool or agent-lo ## Decision -`@deepseek-ai/dsh-commands` in `packages/interaction/commands/` is the product command registry. The TUI app bundle mounts it beside its consuming front end; the [automation-only ACP app](../simplification/2026-07-23-acp-automation-only-protocol.md) and the executor-less, UI-less agent spine omit it. TUI injects the service, while command producers depend only on the registry and any domain they operate. +`@deepseek-ai/dsh-commands` in `packages/interaction/commands/` is the product command registry. `dsh-base` mounts it for consuming front ends; the [automation-only ACP app](../simplification/2026-07-23-acp-automation-only-protocol.md) and standalone `sdk-minimal` tree omit it. UI surfaces inject the service, while command producers depend only on the registry and any domain they operate. ### Registry contract @@ -50,7 +50,7 @@ TUI tests exercise all migrated built-ins, live plugin discovery, help/autocompl - **Keep adapter-local switches** — rejected because optional plugins cannot contribute discovery and behavior without editing the TUI. - **Represent human commands as model tools** — rejected because discovery and direct invocation are human UI behavior; routing through the model adds latency, token cost, and reinterpretation. -- **Put the registry in the core agent spine** — rejected because UI-less entry points do not consume it, while TUI can compose it explicitly. +- **Put the registry in the mandatory agent core** — rejected because UI-less entry points do not consume it, while UI profiles can compose it explicitly. - **Make `dsh-agent-loop` inject commands** — rejected because the loop does not execute or discover human commands. Agent-scoped producers declare the UI dependency in a child plugin instead. - **Attach adapter masks to each definition** — rejected because support is a composition fact, not command-domain state. Every composed adapter exposes a registered command; an incompatible plugin omits registration in that deployment. - **Send unknown slash input to the model** — rejected because typoed or unavailable direct actions must fail predictably rather than change execution planes. diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md index 69a86982b1..55f3a58f0e 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md @@ -12,7 +12,7 @@ TUI 拥有斜杠命令。如果命令名、帮助文本、自动补全、分派 ## 决策 -位于 `packages/interaction/commands/` 的 `@deepseek-ai/dsh-commands` 是产品命令注册表。TUI 应用组合包把它挂载在消费该服务的前端旁;[仅面向自动化的 ACP(Agent Client Protocol)应用](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)和无执行器、无 UI 的 agent spine 都省略该服务。TUI 注入该服务,命令生产者只依赖注册表及其操作的领域。 +位于 `packages/interaction/commands/` 的 `@deepseek-ai/dsh-commands` 是产品命令注册表。`dsh-base` 为消费该服务的前端挂载它;[仅面向自动化的 ACP(Agent Client Protocol)应用](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)和独立的 `sdk-minimal` 配置树都省略该服务。UI 界面注入该服务,命令生产者只依赖注册表及其操作的领域。 ### 注册表约定 @@ -50,7 +50,7 @@ TUI 测试覆盖全部迁移后的内置命令、实时插件发现、帮助与 - **保留适配器本地 switch**——不予采纳,因为可选插件无法贡献发现与行为,除非修改 TUI。 - **把人类命令表示为模型工具**——不予采纳,因为发现与直接调用属于人类 UI 行为;经由模型路由会增加延迟、token 成本和重新解释。 -- **把注册表放入核心 agent spine**——不予采纳,因为无 UI 运行入口不消费它,而 TUI 可以显式组合它。 +- **把注册表放入必需 agent 核心**——不予采纳,因为无 UI 运行入口不消费它,而 UI profile 可以显式组合它。 - **让 `dsh-agent-loop` 注入 commands**——不予采纳,因为循环不执行也不发现人类命令。agent 作用域生产者改为在子插件中声明 UI 依赖。 - **为每个定义附加适配器掩码**——不予采纳,因为支持能力是组合事实,而不是命令领域状态。每个已组合适配器都暴露已注册命令;不兼容插件不会在该部署中注册。 - **把未知斜杠输入发送给模型**——不予采纳,因为输入错误或不可用的直接操作必须可预测地失败,而不能改变执行平面。 diff --git a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml index 66c807b563..7e46759300 100644 --- a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md -2026-07-21-log-backed-session-titles.md: 82733dfe9b7406ec677d43b9b7aa63abed8b3293 -2026-07-21-log-backed-session-titles.zh.md: 305cb7993846e8e1895b66c2caba3abea91feb95 +2026-07-21-log-backed-session-titles.md: d857cc5befa01365460895095c97610e84ec5cb8 +2026-07-21-log-backed-session-titles.zh.md: 0246a084cf11e61f20865edf9bf36c4c96a742c0 diff --git a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md index 82733dfe9b..d857cc5bef 100644 --- a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md +++ b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md @@ -12,7 +12,7 @@ Session identity metadata is immutable, and the event log is the replay and fork ## Decision -The [`session-title` capability family](../../../../packages/session/README.md) owns title state and generation policy. `@deepseek-ai/dsh-session-title` provides `ctx.sessionTitle`, a deterministic first-prompt fallback, and a registry for at most one optional asynchronous provider. `@deepseek-ai/dsh-session-title-llm` owns the common auxiliary-model request policy; separate first-prompt and all-prompts plugins choose input cadence. The shared agent spine mounts only the fallback service. The Web host mounts that service plus the first-prompt model provider with explicit overridable limits, so a fresh Web session gains an immediate fallback and then a non-blocking model summary. Other compositions choose either model provider explicitly. +The [`session-title` capability family](../../../../packages/session/README.md) owns title state and generation policy. `@deepseek-ai/dsh-session-title` provides `ctx.sessionTitle`, a deterministic first-prompt fallback, and a registry for at most one optional asynchronous provider. `@deepseek-ai/dsh-session-title-llm` owns the common auxiliary-model request policy; separate first-prompt and all-prompts plugins choose input cadence. `dsh-sdk-minimal` mounts only the fallback service. `dsh-base` mounts that service plus the first-prompt model provider with explicit overridable limits, so a fresh base-backed session gains an immediate fallback and then a non-blocking model summary. Other compositions choose either model provider explicitly. ### Event ownership and folding diff --git a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md index 305cb79938..0246a084cf 100644 --- a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md +++ b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -[`session-title` 能力包族](../../../../packages/session/README.zh.md)负责标题状态和生成策略。`@deepseek-ai/dsh-session-title` 提供 `ctx.sessionTitle`、确定性的首消息回退方案,以及一个至多接受单个可选异步提供方的注册表。`@deepseek-ai/dsh-session-title-llm` 负责通用的辅助模型请求策略;首消息插件和全部用户消息插件分别选择输入调度方式。共享 agent 主干只挂载回退服务。Web host 会挂载该服务和首消息模型提供方,并显式设置可覆盖的限制,因此新建的 Web 会话会立即获得回退标题,随后在不阻塞主响应的情况下获得模型摘要。其他组合需显式选择任一模型提供方。 +[`session-title` 能力包族](../../../../packages/session/README.zh.md)负责标题状态和生成策略。`@deepseek-ai/dsh-session-title` 提供 `ctx.sessionTitle`、确定性的首消息回退方案,以及一个至多接受单个可选异步提供方的注册表。`@deepseek-ai/dsh-session-title-llm` 负责通用的辅助模型请求策略;首消息插件和全部用户消息插件分别选择输入调度方式。`dsh-sdk-minimal` 只挂载回退服务。`dsh-base` 挂载该服务和首消息模型提供方,并显式设置可覆盖的限制,因此新建的、基于 base 的会话会立即获得回退标题,随后在不阻塞主响应的情况下获得模型摘要。其他组合需显式选择任一模型提供方。 ### 事件归属与折叠 diff --git a/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.i18n.yaml index d8c836c682..0c7262015b 100644 --- a/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.md -2026-07-27-skill-catalog-hot-refresh.md: fcca04e685c258eb927319c4f999160a1a0d3699 -2026-07-27-skill-catalog-hot-refresh.zh.md: a7d060a3c683dde789f07403e3f55a9f2c6643c4 +2026-07-27-skill-catalog-hot-refresh.md: 21221761380f8873002254e89b703596e14389b7 +2026-07-27-skill-catalog-hot-refresh.zh.md: 89a5ea446fed3604b00e2c8fb5bf6fe9664eff7c diff --git a/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.md b/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.md index fcca04e685..2122176138 100644 --- a/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.md +++ b/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.md @@ -26,7 +26,7 @@ Instruction bodies keep progressive disclosure. Every `skill(name)` call asks th ## Verification -Registry tests pin registration-scoped invalidation, revocation, signal abort, contained observer failures, incomplete candidates, bounded generation retries, and stale-name rejection. Local-provider tests cover bundle and flat-file creation, removal, rename, root creation/deletion/recreation including an unobserved root `unlinkDir`, description changes, body-only edits, first-party observation, symlinks, polling options, persistent watcher failures with loadable candidates, event coalescing, bounded projects, teardown, and transient reads. Tool tests pin full replacement messages, empty tombstones, digest stability for body-only edits, incomplete-state retention, visibility, and resume metadata. TUI tests pin last-complete retention, authoritative empty removal, latest-wins refresh, teardown, and the already-open slash-draft race; a real Loader/PTY smoke adds a local skill after startup and observes its completion without restarting. A keyless assembled agent-spine snapshot creates a project skill through model-facing filesystem tools, observes its replacement catalog on the next request, and loads its current body with the real `skill` tool. +Registry tests pin registration-scoped invalidation, revocation, signal abort, contained observer failures, incomplete candidates, bounded generation retries, and stale-name rejection. Local-provider tests cover bundle and flat-file creation, removal, rename, root creation/deletion/recreation including an unobserved root `unlinkDir`, description changes, body-only edits, first-party observation, symlinks, polling options, persistent watcher failures with loadable candidates, event coalescing, bounded projects, teardown, and transient reads. Tool tests pin full replacement messages, empty tombstones, digest stability for body-only edits, incomplete-state retention, visibility, and resume metadata. TUI tests pin last-complete retention, authoritative empty removal, latest-wins refresh, teardown, and the already-open slash-draft race; a real Loader/PTY smoke adds a local skill after startup and observes its completion without restarting. A keyless `dsh-base` expected test observes the composed catalog and loads the current packaged skill body, while Web preset coverage proves project-skill discovery through the same provider and tool. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.zh.md b/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.zh.md index a7d060a3c6..89a5ea446f 100644 --- a/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.zh.md +++ b/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.zh.md @@ -26,7 +26,7 @@ TUI 将同一失效通知作为界面状态而非会话历史来消费。`skills ## 验证 -注册表测试固定了注册作用域内的失效、能力撤销、信号中止、监听器失败隔离、不完整候选项、有界 generation 重试和陈旧名称拒绝。local-provider 测试覆盖 bundle 与平铺文件的创建、移除和重命名,以及根目录创建/删除/重建(包括未观测到根目录 `unlinkDir` 事件的情形)、描述变更、仅正文编辑、第一方观察、符号链接、轮询选项、候选项仍可加载的持续 watcher 失败、事件合并、项目 watcher 容量上限、资源销毁和暂时读取。工具测试固定了完整替换消息、空 tombstone、仅修改正文时 digest 稳定、不完整状态保留、可见性和恢复元数据。TUI 测试固定了上一份完整结果保留、权威空结果清除、刷新时以最新结果为准、资源销毁和已打开斜杠草稿的竞态;一项使用真实 Loader/PTY 的 smoke 测试会在启动后添加本地 skill,并观察其补全项出现,而无需重启。一个无密钥、装配完成的 agent-spine 快照测试通过面向模型的文件系统工具创建项目 skill,观察下一次请求中的替换目录,并使用真实 `skill` 工具加载当前正文。 +注册表测试固定了注册作用域内的失效、能力撤销、信号中止、监听器失败隔离、不完整候选项、有界 generation 重试和陈旧名称拒绝。local-provider 测试覆盖 bundle 与平铺文件的创建、移除和重命名,以及根目录创建/删除/重建(包括未观测到根目录 `unlinkDir` 事件的情形)、描述变更、仅正文编辑、第一方观察、符号链接、轮询选项、候选项仍可加载的持续 watcher 失败、事件合并、项目 watcher 容量上限、资源销毁和暂时读取。工具测试固定了完整替换消息、空 tombstone、仅修改正文时 digest 稳定、不完整状态保留、可见性和恢复元数据。TUI 测试固定了上一份完整结果保留、权威空结果清除、刷新时以最新结果为准、资源销毁和已打开斜杠草稿的竞态;一项使用真实 Loader/PTY 的 smoke 测试会在启动后添加本地 skill,并观察其补全项出现,而无需重启。一项无密钥 `dsh-base` 预期输出测试观察组合后的目录,并加载当前随包 skill 正文;Web preset 覆盖则通过同一提供方与工具证明项目 skill 发现。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml index 1bceee13a9..bee4fef9cd 100644 --- a/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-tmux-location-context.md -2026-07-27-tmux-location-context.md: b8cffc7e3761dfe58b0d24a5489fc1e0d498c1c9 -2026-07-27-tmux-location-context.zh.md: 07d2c23a938402ab3bf736361bf8a3bf944a94fe +2026-07-27-tmux-location-context.md: fa7ec8a76d68500aaf90747fa91e270cae3e6251 +2026-07-27-tmux-location-context.zh.md: 63ae1328cdc72a4c481dd61e22024ef1ffc72c62 diff --git a/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.md b/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.md index b8cffc7e37..fa7ec8a76d 100644 --- a/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.md +++ b/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.md @@ -12,7 +12,7 @@ tmux exposes this without a daemon: `$TMUX_PANE` names the process's pane, and ` ## Decision -`@deepseek-ai/dsh-tmux-context` is an opt-in function plugin in `packages/context/tmux-context/`, alongside the other bounded request-context enrichments that define neither a tool nor a service. The shipped TUI mounts it because terminal-multiplexer context is specific to that surface; `dsh-agent-spine-demo` and the Web/headless surfaces stay silent. +`@deepseek-ai/dsh-tmux-context` is an opt-in function plugin in `packages/context/tmux-context/`, alongside the other bounded request-context enrichments that define neither a tool nor a service. Shipped profile trees do not mount it by default because terminal-multiplexer context is surface-specific; a composition that needs it adds its row explicitly. **Pull on the first step of each turn, not a tmux push.** The plugin prepends an `agent/pre-step` listener and acts only when `step === 1`. A pull model needs no background process, no hook installation in the user's tmux, and no teardown; it re-reads current state each turn so a moved, renamed, or re-laid-out pane is picked up naturally. Gating on the first step makes the reading per-turn: a location is stable within a turn, and re-querying every step would add cost without new information. A pane moved mid-turn is reflected on the next turn, which is the accepted tradeoff for the simpler design. diff --git a/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.zh.md b/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.zh.md index 07d2c23a93..63ae1328cd 100644 --- a/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.zh.md +++ b/.agents/notes/implemented/feature/2026-07-27-tmux-location-context.zh.md @@ -12,7 +12,7 @@ tmux 无需守护进程即可暴露这些信息:`$TMUX_PANE` 标识进程所 ## 决策 -`@deepseek-ai/dsh-tmux-context` 是位于 `packages/context/tmux-context/` 的可选启用型函数插件,与其他既不定义工具也不定义服务的有界请求上下文增强并列。已交付的 TUI 会挂载它,因为终端复用器上下文是该界面特有的;`dsh-agent-spine-demo` 与 Web/无头界面保持沉默。 +`@deepseek-ai/dsh-tmux-context` 是位于 `packages/context/tmux-context/` 的可选启用型函数插件,与其他既不定义工具也不定义服务的有界请求上下文增强并列。由于终端复用器上下文与界面相关,交付的 profile 配置树默认不挂载它;需要它的组合会显式添加其配置行。 **在每轮的第一个步骤拉取,而非 tmux 推送。** 插件前置注册一个 `agent/pre-step` 监听器,仅在 `step === 1` 时动作。拉取模型无需后台进程、无需在用户的 tmux 中安装 hook、也无需清理;它每轮重新读取当前状态,因此被移动、改名或重新布局的 pane 都会被自然感知。以第一个步骤为门槛使读数按轮次生成:位置在一轮内是稳定的,逐步骤重复查询只会增加成本而不带来新信息。轮次中途移动的 pane 会在下一轮反映,这是换取更简单设计所接受的取舍。 diff --git a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml index 6d0a022eb0..5a32ed9ae3 100644 --- a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md -2026-07-29-persistent-bash-str-replace-editor.md: 1cab6e1b37a642dcbb07c4006a8c7851a8cf592c -2026-07-29-persistent-bash-str-replace-editor.zh.md: 0edaa0c4d594a94d2860c552504d12f3cc7fdc63 +2026-07-29-persistent-bash-str-replace-editor.md: e6265ca0f8eb5430ba490b9d046a726f152a2892 +2026-07-29-persistent-bash-str-replace-editor.zh.md: 2fb8a3b3cadefd101d90c52a5ed01e8244a58f07 diff --git a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md index 1cab6e1b37..e6265ca0f8 100644 --- a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md +++ b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md @@ -14,7 +14,7 @@ Some deployments need a one-call Bash schema whose shell state survives across m `@deepseek-ai/dsh-tool-str-replace-editor` independently consumes `ctx.fs` and registers `str_replace_editor` with `view`, `create`, `str_replace`, and `insert`. It provides numbered text views, filtered two-level directory listings, unique literal replacement, canonical insertion boundaries, and bounded output. Paths are absolute; file views preserve content tabs so copied text remains valid literal replacement input; mutations preserve tabs outside the requested edit; and the public schema and failures use only `old_str`. Command-specific fields accept `null` placeholders: execution treats them as omitted when the selected command does not use them, preserves required-field checks, treats `view_range: null` as a full view, and rejects `str_replace.new_str: null` so only omission requests deletion. The plugin can compose with persistent Bash, one-shot Bash, sandboxed Bash, or no shell. -`dsh-system-prompt` accepts `includeHarnessIdentity: false`, while `dsh-agent-spine-demo` forwards that setting and accepts `toolBash: false`. A deployment can therefore own an exact persona and replace the spine's native Bash without duplicate prompt or tool registrations. Existing defaults remain unchanged. +`dsh-system-prompt` accepts `includeHarnessIdentity: false`, and profile compositions own that row together with their selected shell-tool rows. `sdk-minimal` sets the value to `false` and mounts only its platform-selected persistent shell, so a deployment can own an exact persona without duplicate prompt or tool registrations. Existing defaults remain unchanged. Both plugins are included in the Python runtime closure. The persistent Bash closure also includes the PTY service/local backend and the sandbox services required by that backend. Because `node-pty` executes a native `spawn-helper` on macOS, each packaged macOS runtime executable ships with a `-spawn-helper` sibling; Linux uses `forkpty` directly. A pinned `node-pty` patch checks `DSH_NODE_PTY_SPAWN_HELPER` first, so it remains a true override for a current external consumer that supplies a non-sibling helper. When the override is unset, the patch resolves the packaged executable sibling if present and otherwise preserves upstream lookup in ordinary Node runs. The macOS builders fail before publication when the helper is absent or not executable. diff --git a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md index 0edaa0c4d5..2fb8a3b3ca 100644 --- a/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md +++ b/.agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.zh.md @@ -14,7 +14,7 @@ Status: implemented `@deepseek-ai/dsh-tool-str-replace-editor` 独立消费 `ctx.fs`,注册包含 `view`、`create`、`str_replace` 与 `insert` 的 `str_replace_editor`。它提供带行号文本查看、过滤后的两层目录列表、唯一字面量替换、规范插入边界和有界输出。路径必须为绝对路径;文件查看会保留内容中的制表符,因此复制的文本仍可作为有效的字面量替换输入;变更会保留请求编辑范围之外的制表符;公开 schema 与错误则只使用 `old_str`。命令专属字段接受 `null` 占位参数:当前命令不使用该字段时,执行会将其视为未提供;必填检查保持不变;`view_range: null` 表示查看完整文件;`str_replace.new_str: null` 会被拒绝,只有省略该字段才表示删除。它可以与持久 Bash、一次性 Bash、沙箱 Bash 或无 shell 组合。 -`dsh-system-prompt` 接受 `includeHarnessIdentity: false`;`dsh-agent-spine-demo` 会转发该设置,并接受 `toolBash: false`。因此部署可以拥有精确 persona,并替换 spine 的原生 Bash,而不会重复注册提示词或工具。既有默认值不变。 +`dsh-system-prompt` 接受 `includeHarnessIdentity: false`,profile 组合同时拥有该配置行与所选 shell 工具配置行。`sdk-minimal` 将该值设为 `false`,且只挂载按平台选择的持久 shell,因此部署可以拥有精确 persona,而不会重复注册提示词或工具。既有默认值不变。 两个插件都进入 Python runtime 闭包。持久 Bash 的闭包还包含 PTY 服务/本地后端,以及该后端要求的沙箱服务。由于 `node-pty` 在 macOS 上会执行原生 `spawn-helper`,每个打包后的 macOS 运行时可执行文件都会携带一个 `-spawn-helper` 伴随文件;Linux 直接使用 `forkpty`。固定版本的 `node-pty` 补丁会先检查 `DSH_NODE_PTY_SPAWN_HELPER`,因此对当前提供非伴随 helper 的外部消费方而言,该变量仍是真正的覆盖项。未设置该覆盖时,补丁会在打包可执行文件的伴随文件存在时解析它,否则在普通 Node 运行中保留上游查找方式。若 helper 缺失或不可执行,macOS 构建器会在发布前失败。 diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml index 635851d4b8..9640e9d10e 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md -2026-07-23-acp-automation-only-protocol.md: 4bd02298d2a27809099efa90312762aaa1341075 -2026-07-23-acp-automation-only-protocol.zh.md: aa23c816802eae824e251f76b1d5cc6646e08dec +2026-07-23-acp-automation-only-protocol.md: d3d6a7abb6f347b04fdbfcea2940e8df0432b806 +2026-07-23-acp-automation-only-protocol.zh.md: d332e3f7c4798f391fe34b1e50bd63276f0121b8 diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md index 4bd02298d2..d3d6a7abb6 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md @@ -24,7 +24,7 @@ The bridge emits only committed semantic facts. A per-session promise chain pres One-shot `session/request_permission` remains. It is a machine policy channel for bridge-owned agents, not a human approval UI: the answerer accepts only an exact agent object in the bridge's live session map, delegates foreign or call-less requests, and maps failed RPCs to the fail-closed unavailable outcome. The client chooses allow once, reject once, or cancel, and the bridge never turns that response into a durable grant. Asking policy stays in the approval seam and its producers; [`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.md) uses this channel programmatically. -The app composition contains the agent spine, persistence, checkpoint policy, derived session query, and ACP transport. The ACP bridge reads persistence directly for standard resumable summaries; it does not expose command, session-reference, plan-mode, permission-picker, or user-question presentation surfaces. +The app composition contains the mandatory agent core, persistence, checkpoint policy, derived session query, and ACP transport. The ACP bridge reads persistence directly for standard resumable summaries; it does not expose command, session-reference, plan-mode, permission-picker, or user-question presentation surfaces. The transport programs interface-level agent, session, and approval services rather than the concrete agent loop. Tool execution stays inside the harness; ACP never delegates shell execution to an editor. stdout carries framed JSON-RPC only, so the app mounts no stdout logger and the bridge does not monkey-patch process output. diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md index aa23c81680..d332e3f7c4 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md @@ -24,7 +24,7 @@ ACP 仍有一个有用的职责:另一个 agent(智能体)或自动化控 保留一次性 `session/request_permission`。它是为桥接层拥有的 agent 提供的机器策略通道,而不是面向人类的审批 UI:应答者只接受桥接层当前会话映射中登记的同一 agent 对象;不属于桥接层当前 agent 的请求或未关联具体调用的请求会继续委派;RPC 失败则映射为故障时默认拒绝的 `unavailable` 结果。客户端可选择允许一次、拒绝一次或取消,桥接层绝不会将该响应转换为持久授权。询问策略仍归审批 seam 及其生产者所有;[`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.zh.md) 会以程序化方式使用该通道。 -应用组装包含 agent 主干、持久化、检查点策略、派生会话查询和 ACP 传输层。ACP 桥接层直接读取持久化以生成标准可恢复摘要;它不公开命令、会话引用、plan mode、权限选择器或用户问题展示接口。 +应用组装包含必需 agent 核心、持久化、检查点策略、派生会话查询和 ACP 传输层。ACP 桥接层直接读取持久化以生成标准可恢复摘要;它不公开命令、会话引用、plan mode、权限选择器或用户问题展示接口。 传输层调用 agent、会话和审批的接口服务,而不依赖具体的 agent loop(智能体循环)。工具执行仍留在 harness 内;ACP 绝不会把 shell 执行委派给编辑器。stdout 只承载分帧 JSON-RPC,因此 app 不挂载 stdout logger,桥接层也不会 monkey-patch 进程输出。 diff --git a/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.i18n.yaml index 6f8c6f0191..9eff6f5ba0 100644 --- a/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md -2026-08-08-remove-cli-demo.md: 0959660d1dbef80303ce37ca625a548558f09b28 -2026-08-08-remove-cli-demo.zh.md: fbda28f719b5888ef47f9618d8e32d6124640cbc +2026-08-08-remove-cli-demo.md: 1bfb4cdf8dc9c228fa538104daadc9e12fd1e657 +2026-08-08-remove-cli-demo.zh.md: 9e84831aa586ccbe374be785a5d1f05fcdfb4db6 diff --git a/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md b/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md index 0959660d1d..1bfb4cdf8d 100644 --- a/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md +++ b/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.md @@ -14,7 +14,7 @@ The replay suites still need canonical session events to pin assembled backend b 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. -`apps/cli/tests/profiles/headless` owns explicit profile test compositions. Their 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 and test-only driver; the driver renders canonical events as JSONL without defining a supported product output format. +`apps/cli/tests/profiles/headless` loads the shipped `base` and `headless` bundle layers, then applies narrow overlays for replay or mock providers, isolated persistence, and the behavior under test. The support-tier `@deepseek-ai/dsh-loader-smoke` package owns the shipped-profile composition helper, the shared direct-agent turn helper, and the test-only driver; the driver renders canonical events as JSONL without defining a supported product output format. ## Alternatives considered diff --git a/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.zh.md b/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.zh.md index fbda28f719..9e84831aa5 100644 --- a/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-08-remove-cli-demo.zh.md @@ -14,7 +14,7 @@ Status: implemented 彻底删除 `@deepseek-ai/dsh-cli-demo`:包括它的包、bin、解析器、应用插件、输出格式、测试、workspace 引用、生成目录条目和现行文档。不保留别名或兼容包。源码用户通过 `pnpm dsh --profile headless` 调用产品命令;stdout 上的最终文本、stderr 上的失败诊断、持久化、退出状态和关闭行为均由该命令负责。 -`apps/cli/tests/profiles/headless` 负责显式 profile 测试组装。其 Loader 配置把 `@deepseek-ai/dsh-agent-spine-demo`、一个根 agent(智能体)、JSONL 持久化和检查点策略挂载为独立配置行,不再将其隐藏在应用组合包之后。支持层的 `@deepseek-ai/dsh-loader-smoke` 包负责共享的直接 agent 轮次 helper 与仅限测试的 driver;该 driver 将规范事件渲染为 JSONL,但不定义受支持的产品输出格式。 +`apps/cli/tests/profiles/headless` 加载已交付的 `base` 与 `headless` 组合包层,然后只对回放或模拟提供方、隔离持久化及被测行为应用窄 overlay。支持层的 `@deepseek-ai/dsh-loader-smoke` 包负责交付 profile 组装 helper、共享的直接 agent 轮次 helper 与仅限测试的 driver;该 driver 将规范事件渲染为 JSONL,但不定义受支持的产品输出格式。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.i18n.yaml new file mode 100644 index 0000000000..3c0e7990c9 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.i18n.yaml @@ -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-26-remove-agent-spine-demo.md +2026-08-26-remove-agent-spine-demo.md: 47c9341ed9a52c558e5e33e897314a3d94b92abf +2026-08-26-remove-agent-spine-demo.zh.md: 5b48f71d3b35f3c048aef20b2971fb9c66aa6c3a diff --git a/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.md b/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.md new file mode 100644 index 0000000000..47c9341ed9 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.md @@ -0,0 +1,31 @@ +# Agent Note: Remove the agent-spine demo package + +Status: implemented + +English | [中文](2026-08-26-remove-agent-spine-demo.zh.md) + +## Problem + +`@deepseek-ai/dsh-agent-spine-demo` was named and located as an example but exposed a public composition plugin with a large merged configuration. Its only shipped consumer was `dsh-sdk-minimal`, where one plugin row hid the mandatory agent runtime from a bundle whose purpose is a complete explicit tree. Every other consumer was a test. The package also duplicated composition policy already owned by `dsh-base` without providing a capability that could evolve independently. + +## Decision + +The package, its public configuration, and the `packages/examples` group are deleted without an alias. `dsh-sdk-minimal` now declares timer, LLM, session, title, system-prompt, tools, agent, retry, local jobs, invariant registry and companions, and agent-loop rows directly in its patch. It still omits workspace instructions, skills, model-facing job tools, goals, subagents, settings, and the other `dsh-base` features. + +Profile integration tests load the shipped profile and bundle patches through `loadProfile`, then pass the production patches and narrow test `*.patch.yml` files to the same root `cordis:include` used by application boot. Test patches cover a mock provider or model, isolated persistence, and the plugin under test without recreating the mandatory agent tree. SDK server unit tests that do not exercise profile integration mount `dsh-agent-loop-testkit` and `dsh-agent-loop` locally. + +## Verification + +The `sdk-minimal` bundle test checks its exact row inventory and owner configurations, and the built CLI test checks the emitted profile tree. Real-Loader fixtures exercise the shipped bundle layers with test overlays, while SDK server tests exercise the package-local testkit composition. Configuration, package, invariant, generated-document, build, hygiene, and snapshot gates verify that no live product or test imports the deleted package. + +## Alternatives considered + +**Replace the package with `dsh-base`.** Rejected because `dsh-base` is the full product foundation, while `sdk-minimal` deliberately omits its settings, credentials, workspace context, skill, goal, compaction, telemetry, subagent, and broad tool rows. Replacing the package with base would change the profile instead of removing indirection. + +**Rename and move the public composition package.** Rejected because no two shipped products share that composition. A renamed package would preserve the merged configuration forwarding and hide the row owners from profile patches. + +**Keep a private mandatory-runtime composition for tests.** Rejected because a private TypeScript tree still duplicates production composition policy and can drift from the shipped profile. Test overlays remain explicit without owning the rows they do not change. + +## Consequences + +The pre-release package import and its merged configuration are removed. `sdk-minimal` becomes longer but every mounted feature is individually visible and patchable. `dsh-base` remains the full product composition and cannot substitute for the smaller tree. Profile integration tests now change when the shipped base or mode bundle changes, so their Loader and recorded-session coverage reflects the production tree instead of a parallel test composition. diff --git a/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.zh.md b/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.zh.md new file mode 100644 index 0000000000..5b48f71d3b --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-26-remove-agent-spine-demo.zh.md @@ -0,0 +1,31 @@ +# Agent Note: 删除 agent-spine demo 包 + +Status: implemented + +[English](2026-08-26-remove-agent-spine-demo.md) | 中文 + +## 问题 + +`@deepseek-ai/dsh-agent-spine-demo` 的名称与位置将它描述为示例,但它实际暴露了带大型合并配置的公开组合插件。其唯一交付消费方是 `dsh-sdk-minimal`,其中一个插件配置行隐藏了必需的 agent(智能体)运行时,而该组合包的目的本是提供完整、显式的配置树。其他消费方全部是测试。该包还重复了已归 `dsh-base` 所有的组合策略,却没有提供可独立演进的能力。 + +## 决策 + +删除该包、其公开配置与 `packages/examples` 分组,不保留别名。`dsh-sdk-minimal` 现在直接在 patch 中声明 timer、LLM(大语言模型)、会话、标题、系统提示词、工具、agent、重试、本地后台任务、不变量注册表与配套入口,以及 agent loop(智能体循环)配置行。它仍然省略工作区指令、skill(技能)、面向模型的后台任务工具、goal、subagent、settings 与 `dsh-base` 的其他功能。 + +Profile 集成测试通过 `loadProfile` 加载已交付 profile 与组合包 patch,再把生产 patch 和窄测试 `*.patch.yml` 文件交给应用启动使用的同一个根 `cordis:include`。测试 patch 覆盖模拟提供方或模型、隔离持久化和被测插件,不会重新创建必需 agent 配置树。不覆盖 profile 集成的 SDK server 单元测试在本地挂载 `dsh-agent-loop-testkit` 与 `dsh-agent-loop`。 + +## 验证 + +`sdk-minimal` 组合包测试检查其精确配置行清单与所有方配置,构建后的 CLI 测试检查输出的 profile 配置树。真实 Loader fixture 以测试 overlay 覆盖已交付组合包层,SDK server 测试则覆盖包内 testkit 组合。配置、包、不变量、生成文档、构建、hygiene 与快照门禁验证没有现行产品或测试导入已删除包。 + +## 考虑过的替代方案 + +**用 `dsh-base` 替换该包。** 拒绝,因为 `dsh-base` 是完整产品基础,而 `sdk-minimal` 刻意省略其 settings、凭据、工作区上下文、skill、goal、压缩、遥测、subagent 与广泛工具配置行。用 base 替换会改变 profile,而不是移除间接层。 + +**重命名并移动公开组合包。** 拒绝,因为没有两个交付产品共享该组合。重命名后的包仍会保留合并配置转发,并在 profile patch 中隐藏配置行所有方。 + +**为测试保留私有的必需运行时组合。** 拒绝,因为私有 TypeScript 配置树仍然会重复生产组合策略,并可能偏离已交付 profile。测试 overlay 可以保持显式,同时不拥有其未修改的配置行。 + +## 后果 + +预发布包导入与合并配置被删除。`sdk-minimal` 变得更长,但每个挂载功能都单独可见、可 patch。`dsh-base` 仍是完整产品组合,不能替代更小的配置树。已交付 base 或模式组合包发生变化时,profile 集成测试现在也会随之变化,因此其 Loader 与 recorded-session 覆盖反映生产配置树,而非平行的测试组合。 diff --git a/AGENTS.md b/AGENTS.md index 3249baf19a..02b8778e6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,6 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// interaction/ approval/interaction capabilities, permission, commands, ask-user boot/ shared profile/application boot glue sdk/ JSON-RPC protocol + TypeScript client/server - examples/ reusable composition bundles (agent-spine) experimental/ private prototypes excluded from official releases support/ dev/test infrastructure util/ zero-dependency utilities diff --git a/apps/cli/package.json b/apps/cli/package.json index 004317b9be..bcfbe6adbf 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -65,6 +65,7 @@ "@deepseek-ai/dsh-sdk-minimal": "workspace:^", "@deepseek-ai/dsh-session-projection": "workspace:^", "@deepseek-ai/dsh-session-reference": "workspace:^", + "@deepseek-ai/dsh-shell-env": "workspace:^", "@deepseek-ai/dsh-skill": "workspace:^", "@deepseek-ai/dsh-skill-filesystem": "workspace:^", "@deepseek-ai/dsh-terminal": "workspace:^", @@ -103,7 +104,6 @@ "@agentclientprotocol/sdk": "1.4.0", "@deepseek-ai/dsh-acp": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-attachment-local": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-credentials-local": "workspace:^", diff --git a/apps/cli/tests/built-bin.e2e.ts b/apps/cli/tests/built-bin.e2e.ts index 5c36ca5456..b3a3ef6a52 100644 --- a/apps/cli/tests/built-bin.e2e.ts +++ b/apps/cli/tests/built-bin.e2e.ts @@ -957,7 +957,21 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', ['terminal-bash', '@deepseek-ai/dsh-terminal-bash'], ['terminal-pwsh', '@deepseek-ai/dsh-terminal-bash'], ['fs-local', '@deepseek-ai/dsh-fs-local'], - ['agent-spine', '@deepseek-ai/dsh-agent-spine-demo'], + ['timer', '@deepseek-ai/cordis-plugin-timer'], + ['llm', '@deepseek-ai/dsh-llm'], + ['session', '@deepseek-ai/dsh-session'], + ['session-title', '@deepseek-ai/dsh-session-title'], + ['system-prompt', '@deepseek-ai/dsh-system-prompt'], + ['tools', '@deepseek-ai/dsh-tools'], + ['agent', '@deepseek-ai/dsh-agent'], + ['llm-retry', '@deepseek-ai/dsh-llm-retry'], + ['jobs', '@deepseek-ai/dsh-jobs-local'], + ['invariants', '@deepseek-ai/dsh-invariants'], + ['session-invariant', '@deepseek-ai/dsh-session/invariant'], + ['agent-invariant', '@deepseek-ai/dsh-agent/invariant'], + ['scope-invariant', '@deepseek-ai/dsh-scope/invariant'], + ['agent-loop-invariant', '@deepseek-ai/dsh-agent-loop/invariant'], + ['agent-loop', '@deepseek-ai/dsh-agent-loop'], ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], ['persistent-pwsh', '@deepseek-ai/dsh-tool-pwsh-persistent'], ['str-replace-editor', '@deepseek-ai/dsh-tool-str-replace-editor'], diff --git a/apps/cli/tests/profiles/headless/cordis.yml b/apps/cli/tests/profiles/headless/cordis.yml deleted file mode 100644 index ea31466a70..0000000000 --- a/apps/cli/tests/profiles/headless/cordis.yml +++ /dev/null @@ -1,178 +0,0 @@ -# One-shot coding agent with format-pure stdout. The app bin loads the -# gitignored root `.env` into the process environment; entry configs here are -# the composition base, while user-plane values resolve per request through -# the two providers below. - -# User-settings document (`$DSH_HOME/settings.yaml`, hot-reloaded): a -# `llm-deepseek:` section there overrides the adapter entry below without a -# restart. -- id: settings - name: '@deepseek-ai/dsh-settings-file' - -# Credential store: the live process environment over `$DSH_HOME/.credentials.yaml` -# (owner-only file, hot-reloaded). The adapter resolves `DEEPSEEK_API_KEY` -# through it at each request, so no key is inlined in this file. -- id: credentials - name: '@deepseek-ai/dsh-credentials-local' - -- id: deepseek-llm-api-extensions - name: '@deepseek-ai/dsh-deepseek-llm-api-extensions' - -- id: session-log-deepseek - name: '@deepseek-ai/dsh-session-log-deepseek' - -- id: plugin-package-inventory-deepseek - name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek' - -# The DeepSeek adapter. Swap to '@deepseek-ai/dsh-llm-pi-ai' for the pi-ai-backed -# twin (a `providers` dict keyed by route; `reasoning: high` replaces -# thinking/reasoningEffort). Shipped default: full thinking at max effort on -# every request. Exact-model resolution materializes request defaults before -# the request header is logged. -- id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - config: - thinking: enabled - reasoningEffort: max - models: - - id: deepseek-v4-pro - contextWindow: 128000 - - id: deepseek-v4-flash - contextWindow: 128000 - -# Managed child-process groups for the bash executor (spawn/kill/output plumbing). -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: bash - name: '@deepseek-ai/dsh-bash-local' - config: - timeoutMs: 60000 - -# The example composition pre-creates one fresh `main` agent for its test driver. -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: deepseek-official - # Stays on flash: the goal/ralph replay corpora were recorded on it, and - # their nested-include overlays cannot re-pin the app config (a config - # patch cannot target an entry behind a nested include). - model: deepseek-v4-flash - cwd: !!js process.cwd() - workspaceContext: - maxBytes: 65536 - persona: | - You are headless-agent, a coding assistant powered by the {{model}} model. - - Verify your work by running the code or tests. Keep answers brief and - factual. - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' - -# Summarize an older range when derived history approaches the context window. -- id: token-meter - name: '@deepseek-ai/dsh-token-meter' - -- id: compaction-basic - name: '@deepseek-ai/dsh-compaction-basic' - config: - thresholdRatio: 0.8 - retainRatio: 0.16 - maxTokens: 8192 - compactionRetries: 1 - -# Projection registry: durable subagent identity (mode/label) folds through -# its registered units; subagent catalog reads fail loud without the capability. -- id: session-projection - name: '@deepseek-ai/dsh-session-projection' - -# Expose fresh-child `spawn` and completed-prefix `fork` through independent -# in-process backends. -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -- id: subagent-spawn-in-process - name: '@deepseek-ai/dsh-subagent-spawn-in-process' - config: - providerName: spawn - -- id: subagent-fork-in-process - name: '@deepseek-ai/dsh-subagent-fork-in-process' - config: - providerName: fork - -# Continuable background children are selected per delegation tool. The -# separately loaded control registers global `send_message`; `report` is -# installed only in continuable child scopes. -- id: tool-subagent-control - name: '@deepseek-ai/dsh-tool-subagent-control' - -- id: tool-subagent-report - name: '@deepseek-ai/dsh-tool-subagent-report' - -- id: tool-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: spawn - toolName: subagent - backgroundMode: continuable - maxDepth: 1 - -# Fork omits model selection so provider/model stay equal to the parent and the -# inherited history remains eligible for KV Cache reuse. It stays one-shot because -# a continuable child's `report` tool and prompt section precede that history and -# invalidate the same prefix. `run_in_background` is off as an explicit -# foreground-only choice even though agent-spine-demo mounts the generic Job runtime. -# See .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md -# and .agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md. -- id: tool-subagent-fork - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: fork - toolName: subagent_fork - backgroundMode: one-shot - enableRunInBackground: false - maxDepth: 1 - -# The worker-thread workflow engine fans a model-written JavaScript script's -# `agent()` calls out through the spawn backend. -- id: workflow-worker-thread - name: '@deepseek-ai/dsh-workflow-worker-thread' - config: - provider: spawn - -- id: tool-workflow - name: '@deepseek-ai/dsh-tool-workflow' - -# A separate fixed consumer demonstrates fresh-agent Ralph iteration without -# changing the workflow tool or same-session goal behavior. -- id: tool-ralph - name: '@deepseek-ai/dsh-tool-ralph' - -# `todo_write` replaces the logged whole list. -- id: tool-todo - name: '@deepseek-ai/dsh-tool-todo' - config: - allowParallelInProgress: true - -# Policy loads before the model-facing filesystem tools so writes and edits -# require an observed file. Relative paths resolve from the process cwd. -- id: fs-local - name: '@deepseek-ai/dsh-fs-local' - config: - cwd: !!js process.cwd() - -- id: fs-observation-policy - name: '@deepseek-ai/dsh-fs-observation-policy' - -- id: tool-fs - name: '@deepseek-ai/dsh-tool-fs' diff --git a/apps/cli/tests/profiles/headless/credentials-snapshot.patch.yml b/apps/cli/tests/profiles/headless/credentials-snapshot.patch.yml new file mode 100644 index 0000000000..79757baa74 --- /dev/null +++ b/apps/cli/tests/profiles/headless/credentials-snapshot.patch.yml @@ -0,0 +1,29 @@ +# Missing-credential patch over the shipped headless profile. The replacement +# adapter registers the production route but cannot reach its endpoint. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: llm-deepseek-keyless + name: '@deepseek-ai/dsh-llm-deepseek' + config: + baseURL: 'http://127.0.0.1:9' diff --git a/apps/cli/tests/profiles/headless/credentials.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/credentials.cordis.snapshot.yml deleted file mode 100644 index 7f1cb2b1f2..0000000000 --- a/apps/cli/tests/profiles/headless/credentials.cordis.snapshot.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Keyless dynamic-configuration composition: the base settings and credentials -# providers see only the isolated run home, no API key exists anywhere, and -# the deepseek-official route still registers — so the prompt fails with the actionable -# MISSING_CREDENTIAL guidance this snapshot pins as first-run UX. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - # The endpoint is never dialed: credential resolution fails first. - - id: llm-deepseek-keyless - name: '@deepseek-ai/dsh-llm-deepseek' - config: - baseURL: 'http://127.0.0.1:9' diff --git a/apps/cli/tests/profiles/headless/goal-real-model.patch.yml b/apps/cli/tests/profiles/headless/goal-real-model.patch.yml new file mode 100644 index 0000000000..68fdb3983d --- /dev/null +++ b/apps/cli/tests/profiles/headless/goal-real-model.patch.yml @@ -0,0 +1,19 @@ +# Real-model goal patch over the shipped headless profile. The base bundle +# supplies the goal domain and model-facing tools. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' diff --git a/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml b/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml new file mode 100644 index 0000000000..220969cfef --- /dev/null +++ b/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml @@ -0,0 +1,26 @@ +# Replay-backed goal patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' diff --git a/apps/cli/tests/profiles/headless/goal.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/goal.cordis.snapshot.yml deleted file mode 100644 index 00c34e272e..0000000000 --- a/apps/cli/tests/profiles/headless/goal.cordis.snapshot.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Replay counterpart to goal.cordis.yml. It includes cordis.yml directly because -# a config patch cannot target an entry behind a nested include, then restates -# the goal overlay while replacing the live model with keyless replay. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: goal - name: '@deepseek-ai/dsh-goal' - - id: tool-goal - name: '@deepseek-ai/dsh-tool-goal' - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' diff --git a/apps/cli/tests/profiles/headless/goal.cordis.yml b/apps/cli/tests/profiles/headless/goal.cordis.yml deleted file mode 100644 index fe84de7bf5..0000000000 --- a/apps/cli/tests/profiles/headless/goal.cordis.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Add the persisted goal domain and its model-facing tools to the real one-shot app. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - insert: - - id: goal - name: '@deepseek-ai/dsh-goal' - - id: tool-goal - name: '@deepseek-ai/dsh-tool-goal' diff --git a/apps/cli/tests/profiles/headless/real-model.patch.yml b/apps/cli/tests/profiles/headless/real-model.patch.yml new file mode 100644 index 0000000000..e0471e17d5 --- /dev/null +++ b/apps/cli/tests/profiles/headless/real-model.patch.yml @@ -0,0 +1,30 @@ +# Test-only agent and persistence patch over the shipped base + headless +# profile. The shared driver owns the turn, so the product runner stays off. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + config: + thinking: enabled + reasoningEffort: max + models: + - id: deepseek-v4-pro + contextWindow: 128000 + - id: deepseek-v4-flash + contextWindow: 128000 + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" diff --git a/apps/cli/tests/profiles/headless/retry-snapshot.patch.yml b/apps/cli/tests/profiles/headless/retry-snapshot.patch.yml new file mode 100644 index 0000000000..ff13fa3303 --- /dev/null +++ b/apps/cli/tests/profiles/headless/retry-snapshot.patch.yml @@ -0,0 +1,26 @@ +# Deterministic provider-retry patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: retry-snapshot-backend + name: './tests/fixtures/retry-snapshot-backend.mjs' diff --git a/apps/cli/tests/profiles/headless/retry.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/retry.cordis.snapshot.yml deleted file mode 100644 index 30a67f45f6..0000000000 --- a/apps/cli/tests/profiles/headless/retry.cordis.snapshot.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Keyless provider-retry composition for the headless stream-json snapshot. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: retry-snapshot-backend - name: './tests/fixtures/retry-snapshot-backend.mjs' diff --git a/apps/cli/tests/profiles/headless/semantic-checkpoint-snapshot.patch.yml b/apps/cli/tests/profiles/headless/semantic-checkpoint-snapshot.patch.yml new file mode 100644 index 0000000000..3e637e28aa --- /dev/null +++ b/apps/cli/tests/profiles/headless/semantic-checkpoint-snapshot.patch.yml @@ -0,0 +1,25 @@ +# Patch for resuming one seeded semantic-checkpoint session over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: replay + name: '@deepseek-ai/dsh-llm-replay' + config: + file: !!js process.env.DSH_SNAPSHOT_FILE + overrideFile: !!js process.env.DSH_SNAPSHOT_OVERRIDE + + # Publish the persisted agent before the driver selects the single root. + - id: resumed-agent + name: './tests/fixtures/semantic-checkpoint-agent.ts' diff --git a/apps/cli/tests/profiles/headless/semantic-checkpoint.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/semantic-checkpoint.cordis.snapshot.yml deleted file mode 100644 index 938278b435..0000000000 --- a/apps/cli/tests/profiles/headless/semantic-checkpoint.cordis.snapshot.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Keyless real-Loader composition for the semantic-checkpoint recovery snapshot. -# The headless driver resumes the seeded interrupted session and emits its next -# turn over stream-json; the replay adapter supplies the deterministic response. - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: none - -- id: checkpoint - name: '@deepseek-ai/dsh-session-checkpoint-policy' - -- id: replay - name: '@deepseek-ai/dsh-llm-replay' - config: - file: !!js process.env.DSH_SNAPSHOT_FILE - overrideFile: !!js process.env.DSH_SNAPSHOT_OVERRIDE - -# Managed child-process groups for the bash executor (spawn/kill/output plumbing). -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: bash - name: '@deepseek-ai/dsh-bash-local' - config: - timeoutMs: 60000 - -- id: agent - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: [] - workspaceContext: false - skills: - enabled: false - toolJobs: false - goals: false - -# Await the persisted resume before the headless driver inspects root agents. -- id: resumed-agent - name: './tests/fixtures/semantic-checkpoint-agent.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-diagnostic-snapshot.patch.yml b/apps/cli/tests/profiles/headless/subagent-diagnostic-snapshot.patch.yml new file mode 100644 index 0000000000..ac47e8d5a2 --- /dev/null +++ b/apps/cli/tests/profiles/headless/subagent-diagnostic-snapshot.patch.yml @@ -0,0 +1,28 @@ +# Patch for resuming one seeded corrupt-child diagnostic over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: session-query-sqlite + disabled: true + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: session-query + name: './tests/fixtures/subagent-diagnostic-query.ts' + + - id: replay + name: '@deepseek-ai/dsh-llm-replay' + + # Publish the persisted agent before the driver selects the single root. + - id: resumed-agent + name: './tests/fixtures/subagent-diagnostic-agent.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-diagnostic.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/subagent-diagnostic.cordis.snapshot.yml deleted file mode 100644 index 4d59334e96..0000000000 --- a/apps/cli/tests/profiles/headless/subagent-diagnostic.cordis.snapshot.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Keyless real-Loader composition for the descriptor-less cold-child -# diagnostic snapshot. The seeded parent owns one session-backed child whose -# log carries `origin: 'subagent'` but no descriptor event, so the projection -# fold produces no identity and `list_agents` must surface the child as a -# `[diagnostic: corrupt]` row instead of silently dropping it. - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: none - -- id: session-query - name: './tests/fixtures/subagent-diagnostic-query.ts' - -# file/override both default to their DSH_SNAPSHOT_* env vars. -- id: replay - name: '@deepseek-ai/dsh-llm-replay' - -# This scenario probes the subagent catalog only, so the shell/filesystem -# stacks are absent; the bundle must opt out of the tools that would wait -# forever for executors this tree never mounts. -- id: agent - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: [] - workspaceContext: false - skills: - enabled: false - toolBash: false - toolJobs: false - goals: false - -# Projection registry: the cold child's identity fold runs through it; the -# catalog read fails loud when the capability is absent. -- id: session-projection - name: '@deepseek-ai/dsh-session-projection' - -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -- id: tool-subagent-list-agents - name: '@deepseek-ai/dsh-tool-subagent-control/list-agents' - -# Await the persisted resume before the headless driver inspects root agents. -- id: resumed-agent - name: './tests/fixtures/subagent-diagnostic-agent.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml b/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml new file mode 100644 index 0000000000..352841f745 --- /dev/null +++ b/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml @@ -0,0 +1,34 @@ +# Patch for resuming one read-only parent over the shipped headless profile. The profile's +# workspace-write deployment default must lose to the session override in its child. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: sandbox-policy + config: + mode: workspace-write + workspaceRoot: !!js process.cwd() + +- id: tool-subagent + config: + provider: spawn + toolName: subagent + maxDepth: 1 + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: replay + name: '@deepseek-ai/dsh-llm-replay' + + # Publish the persisted agent before the driver selects the single root. + - id: resumed-agent + name: './tests/fixtures/subagent-inheritance-agent.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-inheritance.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/subagent-inheritance.cordis.snapshot.yml deleted file mode 100644 index 7baa1cdd0c..0000000000 --- a/apps/cli/tests/profiles/headless/subagent-inheritance.cordis.snapshot.yml +++ /dev/null @@ -1,68 +0,0 @@ -# Keyless real-Loader composition for the parent-only override inheritance -# snapshot. The deployment default stays WIDE (workspace-write) while the -# seeded parent session carries a session-scoped read-only override; the -# resumed parent delegates, and only the inheritance capture can confine the -# child — remove it and the child writes successfully under the deployment -# default, so this scenario is the assembled-app red/green anchor for the -# delegation bypass. - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: none - -# file/override/childFiles all default to their DSH_SNAPSHOT_* env vars. -- id: replay - name: '@deepseek-ai/dsh-llm-replay' - -# The confining filesystem stack: the wide deployment default lives on the -# shared policy home; the seeded parent's read-only override must beat it -# INSIDE the child for the scenario to deny. -- id: sandbox-policy - name: '@deepseek-ai/dsh-sandbox-policy' - config: - mode: workspace-write - workspaceRoot: !!js process.cwd() - -- id: fs-sandbox - name: '@deepseek-ai/dsh-fs-sandbox' - -- id: fs-observation-policy - name: '@deepseek-ai/dsh-fs-observation-policy' - -- id: tool-fs - name: '@deepseek-ai/dsh-tool-fs' - -# This scenario probes filesystem confinement only, so the bash stack is absent: -# without it the bundle's `toolBash: false` is required, because `tool-bash` would -# otherwise wait forever for a `bash` executor this tree never mounts. -- id: agent - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: [] - workspaceContext: false - skills: - enabled: false - toolBash: false - toolJobs: false - goals: false - -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -- id: subagent-spawn-in-process - name: '@deepseek-ai/dsh-subagent-spawn-in-process' - config: - providerName: spawn - -- id: tool-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: spawn - toolName: subagent - maxDepth: 1 - -# Await the persisted resume before the headless driver inspects root agents. -- id: resumed-agent - name: './tests/fixtures/subagent-inheritance-agent.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml b/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml new file mode 100644 index 0000000000..85a412e994 --- /dev/null +++ b/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml @@ -0,0 +1,30 @@ +# Replay-backed continuable child settlement patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + + # Hold the parent's second step until the manager notice enters its inbox. + - id: settlement-fence + name: './tests/fixtures/subagent-settlement-fence.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-settlement.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/subagent-settlement.cordis.snapshot.yml deleted file mode 100644 index 4812d676a2..0000000000 --- a/apps/cli/tests/profiles/headless/subagent-settlement.cordis.snapshot.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Keyless assembled-app coverage for continuable child settlement delivery. The -# replay child deliberately never calls report; the parent can reach its final -# answer only if the continuation manager places the child's closing message in -# the parent turn without list_agents, send_message, or a Task collector. - -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - -# Prevent platform scheduling from choosing a streamed-chunk interleave. The -# fence releases only after the real manager notice enters the parent inbox. -- id: settlement-fence - name: './tests/fixtures/subagent-settlement-fence.ts' diff --git a/apps/cli/tests/profiles/headless/team-snapshot.patch.yml b/apps/cli/tests/profiles/headless/team-snapshot.patch.yml new file mode 100644 index 0000000000..ed0c4d3eb7 --- /dev/null +++ b/apps/cli/tests/profiles/headless/team-snapshot.patch.yml @@ -0,0 +1,65 @@ +# Deterministic Agent Teams patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- id: tool-subagent-control + disabled: true + +- id: tool-subagent-list-agents + disabled: true + +- id: tool-subagent-report + disabled: true + +- id: tool-subagent + config: + provider: spawn + toolName: subagent + backgroundMode: one-shot + maxDepth: 1 + +- id: tool-subagent-fork + config: + provider: fork + toolName: subagent_fork + backgroundMode: one-shot + enableRunInBackground: false + maxDepth: 1 + +- insert: + - id: agent-team + name: '@deepseek-ai/dsh-experimental-agent-team' + config: + maxMembers: 8 + maxTasks: 256 + maxPendingMessagesPerMember: 64 + maxMessageBytes: 65536 + disposalTimeoutMs: 5000 + + - id: tool-agent-team + name: '@deepseek-ai/dsh-experimental-tool-agent-team' + config: + freshProvider: spawn + forkProvider: fork + + - id: team-fixture-llm + name: './tests/fixtures/team-llm.mjs' diff --git a/apps/cli/tests/profiles/headless/team.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/team.cordis.snapshot.yml deleted file mode 100644 index 06b1788910..0000000000 --- a/apps/cli/tests/profiles/headless/team.cordis.snapshot.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Keyless Agent Teams composition over the real headless app and deterministic fixture adapter. -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: tool-subagent-control - name: '@deepseek-ai/dsh-tool-subagent-control' - disabled: true - - id: tool-subagent-list-agents - name: '@deepseek-ai/dsh-tool-subagent-control/list-agents' - disabled: true - - id: tool-subagent-report - name: '@deepseek-ai/dsh-tool-subagent-report' - disabled: true - - id: tool-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: spawn - toolName: subagent - backgroundMode: one-shot - maxDepth: 1 - - id: tool-subagent-fork - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: fork - toolName: subagent_fork - backgroundMode: one-shot - enableRunInBackground: false - maxDepth: 1 - - insert: - - id: agent-team - name: '@deepseek-ai/dsh-experimental-agent-team' - config: - maxMembers: 8 - maxTasks: 256 - maxPendingMessagesPerMember: 64 - maxMessageBytes: 65536 - disposalTimeoutMs: 5000 - - id: tool-agent-team - name: '@deepseek-ai/dsh-experimental-tool-agent-team' - config: - freshProvider: spawn - forkProvider: fork - - id: team-fixture-llm - name: './tests/fixtures/team-llm.mjs' diff --git a/apps/cli/tests/profiles/headless/tests/expected/goal-tools/replay.override.json b/apps/cli/tests/profiles/headless/tests/expected/goal-tools/replay.override.json index c4716ba7b0..d9f191ef00 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/goal-tools/replay.override.json +++ b/apps/cli/tests/profiles/headless/tests/expected/goal-tools/replay.override.json @@ -9,6 +9,16 @@ { "type": "finish", "reason": { "kind": "tool-calls" } } ] }, + { + "kind": "chunks", + "chunks": [ + { "type": "block-start", "index": 0, "blockType": "text" }, + { "type": "text-delta", "index": 0, "text": "Goal tool snapshot" }, + { "type": "block-end", "index": 0, "block": { "type": "text", "text": "Goal tool snapshot" } }, + { "type": "usage", "usage": { "inputTokens": 8, "outputTokens": 3 } }, + { "type": "finish", "reason": { "kind": "stop" } } + ] + }, { "kind": "chunks", "chunks": [ diff --git a/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl index cf36d97e79..081447dfb4 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl @@ -1,48 +1,51 @@ -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":1,"time":0,"data":{"turn":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"Probe strict-schema fillers against miss","messageSeqs":[4],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_goal_probe","name":"update_goal","argumentsDelta":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":15,"outputTokens":6}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":15,"outputTokens":6}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_goal_probe"},"content":[{"type":"tool-result","toolCallId":"call_goal_probe","content":[{"type":"text","text":"Error: no current goal"}],"isError":true}],"role":"user","id":"{{sessionId}}"},"error":{"name":"GoalError","code":"GOAL_NOT_FOUND"}},"sourceEventSeqs":[14],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"call_goal_create","name":"create_goal","argumentsDelta":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":20,"outputTokens":8}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":23,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":20,"outputTokens":8}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":24,"time":0,"data":{"turn":1,"step":2,"callId":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"goal/change","seq":25,"time":0,"data":{"kind":"goal/change","version":1,"operation":"create","goal":{"id":"goal-{{sessionId}}","revision":1,"objective":"Finish the headless goal-tool snapshot proof","phase":"active","maxGoalRounds":7},"roundsStarted":0,"createdAt":0,"updatedAt":0}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":26,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_goal_create"},"content":[{"type":"tool-result","toolCallId":"call_goal_create","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the headless goal-tool snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":7},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":27,"time":0,"data":{"turn":1,"step":2}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":28,"time":0,"data":{"turn":1,"step":3}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"call_goal_get","name":"get_goal","argumentsDelta":"{}"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_get","name":"get_goal","arguments":"{}"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":30,"outputTokens":4}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":34,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_get","name":"get_goal","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":30,"outputTokens":4}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":35,"time":0,"data":{"turn":1,"step":3,"callId":"call_goal_get","name":"get_goal","arguments":"{}"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":36,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_goal_get"},"content":[{"type":"tool-result","toolCallId":"call_goal_get","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the headless goal-tool snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":7},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[35],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":37,"time":0,"data":{"turn":1,"step":3}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":38,"time":0,"data":{"turn":1,"step":4}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":39,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":40,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"text-delta","index":0,"text":"GOAL READY"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":41,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"GOAL READY"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":35,"outputTokens":2}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":44,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":35,"outputTokens":2}},"sourceEventSeqs":[39,40,41,42,43],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":45,"time":0,"data":{"turn":1,"step":4}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":46,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":7,"time":0,"data":{"content":[{"type":"text","text":"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":9,"time":0,"data":{"title":"Probe strict-schema fillers against miss","messageSeqs":[7],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":12,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[7],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":7,\"text\":\"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it.\"}]"}],"source":{"kind":"plugin","plugin":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_goal_probe","name":"update_goal","argumentsDelta":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":15,"outputTokens":6}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":18,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":15,"outputTokens":6}},"sourceEventSeqs":[13,14,15,16,17],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":19,"time":0,"data":{"turn":1,"step":1,"callId":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":20,"time":0,"data":{"title":"Goal tool snapshot","messageSeqs":[7],"source":{"kind":"provider","provider":"session-title-first-prompt-llm","model":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":21,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_goal_probe"},"content":[{"type":"tool-result","toolCallId":"call_goal_probe","content":[{"type":"text","text":"Error: no current goal"}],"isError":true}],"role":"user","id":"{{sessionId}}"},"error":{"name":"GoalError","code":"GOAL_NOT_FOUND"}},"sourceEventSeqs":[19],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":22,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":23,"time":0,"data":{"turn":1,"step":2}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":24,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":25,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"call_goal_create","name":"create_goal","argumentsDelta":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":20,"outputTokens":8}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":29,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":20,"outputTokens":8}},"sourceEventSeqs":[24,25,26,27,28],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":30,"time":0,"data":{"turn":1,"step":2,"callId":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the headless goal-tool snapshot proof\",\"max_goal_rounds\":7}"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"goal/change","seq":31,"time":0,"data":{"kind":"goal/change","version":1,"operation":"create","goal":{"id":"goal-{{sessionId}}","revision":1,"objective":"Finish the headless goal-tool snapshot proof","phase":"active","maxGoalRounds":7},"roundsStarted":0,"createdAt":0,"updatedAt":0}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":32,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_goal_create"},"content":[{"type":"tool-result","toolCallId":"call_goal_create","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the headless goal-tool snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":7},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[30],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":33,"time":0,"data":{"turn":1,"step":2}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":34,"time":0,"data":{"turn":1,"step":3}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":35,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":36,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"call_goal_get","name":"get_goal","argumentsDelta":"{}"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":37,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_get","name":"get_goal","arguments":"{}"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":38,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":30,"outputTokens":4}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":39,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":40,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_get","name":"get_goal","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":30,"outputTokens":4}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":41,"time":0,"data":{"turn":1,"step":3,"callId":"call_goal_get","name":"get_goal","arguments":"{}"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":42,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_goal_get"},"content":[{"type":"tool-result","toolCallId":"call_goal_get","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the headless goal-tool snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":7},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[41],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":43,"time":0,"data":{"turn":1,"step":3}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":44,"time":0,"data":{"turn":1,"step":4}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":45,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":46,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"text-delta","index":0,"text":"GOAL READY"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":47,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"GOAL READY"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":35,"outputTokens":2}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":50,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":35,"outputTokens":2}},"sourceEventSeqs":[45,46,47,48,49],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":51,"time":0,"data":{"turn":1,"step":4}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":52,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"GOAL READY","usage":{"inputTokens":100,"outputTokens":20}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl index f521487e42..709e2faa53 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl @@ -1,12 +1,14 @@ -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":1,"time":0,"data":{"turn":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"say pong","messageSeqs":[4],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":9,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":10,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":7,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":9,"time":0,"data":{"title":"say pong","messageSeqs":[7],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":12,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[7],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":7,\"text\":\"say pong\"}]"}],"source":{"kind":"plugin","plugin":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":14,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":15,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}} {"type":"result","sessionId":"{{sessionId}}","output":""} diff --git a/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl index 0870c833f1..18e0b7171a 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl @@ -1,12 +1,14 @@ -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":1,"time":0,"data":{"turn":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"say pong","messageSeqs":[4],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":9,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":10,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":7,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":9,"time":0,"data":{"title":"say pong","messageSeqs":[7],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":12,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[7],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":7,\"text\":\"say pong\"}]"}],"source":{"kind":"plugin","plugin":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":14,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":15,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}} {"type":"result","sessionId":"{{sessionId}}","output":""} diff --git a/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl index 3c6e45c243..1c89d8195b 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl @@ -1,20 +1,22 @@ -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"retry the transient provider failure"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":1,"time":0,"data":{"turn":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"retry the transient provider failure"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"retry the transient provider failure","messageSeqs":[4],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot transient failure","code":"RATE_LIMIT","status":429}}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"llm/retry","seq":9,"time":0,"data":{"retryId":"{{sessionId}}","turn":1,"step":1,"provider":"deepseek-official","mode":"normal","policyKey":"[\"normal\",1,[\"RATE_LIMIT\"],1,1,0]","retry":1,"maxRetries":1,"delayMs":1,"failure":{"message":"snapshot transient failure","code":"RATE_LIMIT","status":429}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"llm/retry-started","seq":10,"time":0,"data":{"retryId":"{{sessionId}}","turn":1,"step":1,"retry":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RETRY_OK"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RETRY_OK"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":4,"outputTokens":2}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":16,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RETRY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":4,"outputTokens":2}},"sourceEventSeqs":[11,12,13,14,15],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":17,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":18,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"retry the transient provider failure"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":7,"time":0,"data":{"content":[{"type":"text","text":"retry the transient provider failure"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":9,"time":0,"data":{"title":"retry the transient provider failure","messageSeqs":[7],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":12,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[7],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":7,\"text\":\"retry the transient provider failure\"}]"}],"source":{"kind":"plugin","plugin":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot transient failure","code":"RATE_LIMIT","status":429}}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"llm/retry","seq":14,"time":0,"data":{"retryId":"{{sessionId}}","turn":1,"step":1,"provider":"deepseek-official","mode":"normal","policyKey":"[\"normal\",1,[\"RATE_LIMIT\"],1,1,0]","retry":1,"maxRetries":1,"delayMs":1,"failure":{"message":"snapshot transient failure","code":"RATE_LIMIT","status":429}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"llm/retry-started","seq":15,"time":0,"data":{"retryId":"{{sessionId}}","turn":1,"step":1,"retry":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RETRY_OK"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RETRY_OK"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":4,"outputTokens":2}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":21,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RETRY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":4,"outputTokens":2}},"sourceEventSeqs":[16,17,18,19,20],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":22,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":23,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"RETRY_OK","usage":{"inputTokens":4,"outputTokens":2}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl index e3196fe731..221c2a522d 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl @@ -8,11 +8,15 @@ {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"interrupted"}}} {"type":"session/end-seed","data":{}} +{"type":"permission/preset","data":{"preset":"workspace-write"}} +{"type":"sandbox/mode","data":{"mode":"workspace-write"}} +{"type":"approval/policy","data":{"policy":"ask"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue safely from the interrupted operation."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Continue safely from the interrupted operation."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"session/title","data":{"title":"Perform one side-effecting remote mutati","messageSeqs":[1],"source":{"kind":"fallback"}}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} @@ -20,6 +24,6 @@ {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"I will verify the external state before deciding whether to retry the side-effecting operation."}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"I will verify the external state before deciding whether to retry the side-effecting operation."}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"I will verify the external state before deciding whether to retry the side-effecting operation."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[17,18,19,20],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"I will verify the external state before deciding whether to retry the side-effecting operation."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[21,22,23,24],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl index d258caf66b..6db6d26f41 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl @@ -3,11 +3,15 @@ {"type":"user/message","data":{"content":[{"type":"text","text":"Start a background job."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","data":{}} +{"type":"permission/preset","data":{"preset":"workspace-write"}} +{"type":"sandbox/mode","data":{"mode":"workspace-write"}} +{"type":"approval/policy","data":{"policy":"ask"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Call list_agents once and report what it shows."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Call list_agents once and report what it shows."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"session/title","data":{"title":"Start a background job.","messageSeqs":[1],"source":{"kind":"fallback"}}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} @@ -16,9 +20,9 @@ {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[16,17,18,19,20],"surfaceOp":"append"} {"type":"tool/call","data":{"turn":2,"step":1,"callId":"list-once","name":"list_agents","arguments":"{}"}} -{"type":"tool/result","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool","callId":"list-once"},"content":[{"type":"tool-result","toolCallId":"list-once","content":[{"type":"text","text":"{{sessionId}} [diagnostic: corrupt]"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool","callId":"list-once"},"content":[{"type":"tool-result","toolCallId":"list-once","content":[{"type":"text","text":"{{sessionId}} [diagnostic: corrupt]"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"step/start","data":{"turn":2,"step":2}} {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} @@ -26,6 +30,6 @@ {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The stored subagent is unreadable. PARENT_DONE"}}}} {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The stored subagent is unreadable. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[22,23,24,25,26],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The stored subagent is unreadable. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[26,27,28,29,30],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":2}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl index 286ae33dcc..22dec79be0 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl @@ -1,13 +1,14 @@ {"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{sessionId}}","origin":"subagent","delegationDepth":1} {"type":"sandbox/mode","data":{"mode":"read-only","source":"delegation"}} +{"type":"approval/policy","data":{"policy":"never","source":"delegation"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"subagent/descriptor","data":{"version":3,"mode":"one-shot","provider":"spawn","label":"Delegated write probe"}} {"type":"step/start","data":{"turn":1,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"session/title","data":{"title":"Use the write tool exactly","messageSeqs":[6],"source":{"kind":"fallback"}}} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\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`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"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`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"session/title","data":{"title":"Use the write tool exactly","messageSeqs":[7],"source":{"kind":"fallback"}}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"low"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} @@ -15,9 +16,9 @@ {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}}}} {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[11,12,13,14,15],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"child-write"},"content":[{"type":"tool-result","toolCallId":"child-write","content":[{"type":"text","text":"Error: [sandbox: file access denied under read-only mode]\n[sandbox: escalation available — retry this exact operation once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]"}],"isError":true}],"role":"user","id":"{{sessionId}}"},"error":{"name":"FsError","code":"FS_SANDBOX_DENIED"}},"sourceEventSeqs":[17],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"child-write"},"content":[{"type":"tool-result","toolCallId":"child-write","content":[{"type":"text","text":"Error: [sandbox: file access denied under read-only mode]\n[sandbox: escalation available — retry this exact operation once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]"}],"isError":true}],"role":"user","id":"{{sessionId}}"},"error":{"name":"FsError","code":"FS_SANDBOX_DENIED"}},"sourceEventSeqs":[18],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} @@ -25,6 +26,6 @@ {"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}}}} {"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[21,22,23,24,25],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[22,23,24,25,26],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":2}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl index 332cf1be07..c8e2424126 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl @@ -5,12 +5,14 @@ {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"low"}},"reason":"initial"}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","data":{}} +{"type":"permission/preset","data":{"preset":"read-only"}} +{"type":"approval/policy","data":{"policy":"ask"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Delegate the write probe to a subagent."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Delegate the write probe to a subagent."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"session/title","data":{"title":"Tighten this session to read-only.","messageSeqs":[1],"source":{"kind":"fallback"}}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"low"},"system":"{{system}}","tools":"{{tools}}"},"reason":"resume"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} @@ -19,9 +21,9 @@ {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[15,16,17,18,19],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[17,18,19,20,21],"surfaceOp":"append"} {"type":"tool/call","data":{"turn":2,"step":1,"callId":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}} -{"type":"tool/result","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool","callId":"delegate-write"},"content":[{"type":"tool-result","toolCallId":"delegate-write","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[21],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool","callId":"delegate-write"},"content":[{"type":"tool-result","toolCallId":"delegate-write","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"step/start","data":{"turn":2,"step":2}} {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} @@ -29,6 +31,6 @@ {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The delegated child was denied by the sandbox. PARENT_DONE"}}}} {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The delegated child was denied by the sandbox. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The delegated child was denied by the sandbox. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[27,28,29,30,31],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":2}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl index d2ac2d31c6..1bb907bcf3 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl @@ -1,13 +1,15 @@ {"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{sessionId}}","origin":"subagent","delegationDepth":1} {"type":"subagent/descriptor","data":{"version":3,"mode":"continuable","provider":"spawn","label":"Return child result","agentProvider":"deepseek-official","agentModel":"deepseek-v4-flash"}} {"type":"session/end-seed","data":{}} +{"type":"sandbox/mode","data":{"mode":"workspace-write","source":"delegation"}} +{"type":"approval/policy","data":{"policy":"never","source":"delegation"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly CHILD_RESULT and nothing else. Do not call report."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly CHILD_RESULT and nothing else. Do not call report."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"session/title","data":{"title":"Reply with exactly CHILD_RESULT and","messageSeqs":[6],"source":{"kind":"fallback"}}} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\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`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"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`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"session/title","data":{"title":"Reply with exactly CHILD_RESULT and","messageSeqs":[8],"source":{"kind":"fallback"}}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} @@ -15,6 +17,6 @@ {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CHILD_RESULT"}}}} {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}} {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[11,12,13,14,15],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[13,14,15,16,17],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl index a271b39400..3ca26cf807 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl @@ -1,38 +1,35 @@ -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":1,"time":0,"data":{"turn":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"Start one continuable background subagen","messageSeqs":[4],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"start-child","name":"subagent","argumentsDelta":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"start-child"},"content":[{"type":"tool-result","toolCallId":"start-child","content":[{"type":"text","text":"started subagent {{sessionId}}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":17,"time":0,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"}]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":18,"time":0,"data":{"turn":1,"step":2}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"STARTED"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"STARTED"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":23,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":24,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"STARTED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":25,"time":0,"data":{"turn":1,"step":2}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":26,"time":0,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":27,"time":0,"data":{"turn":1,"step":3}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":28,"time":0,"data":{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"text-delta","index":0,"text":"PARENT_RECEIVED_CHILD_RESULT"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_RECEIVED_CHILD_RESULT"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":34,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_RECEIVED_CHILD_RESULT"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":7,"time":0,"data":{"content":[{"type":"text","text":"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":9,"time":0,"data":{"title":"Start one continuable background subagen","messageSeqs":[7],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":12,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[7],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":7,\"text\":\"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list.\"}]"}],"source":{"kind":"plugin","plugin":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"start-child","name":"subagent","argumentsDelta":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":18,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[13,14,15,16,17],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":19,"time":0,"data":{"turn":1,"step":1,"callId":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call report.\"}"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":20,"time":0,"data":{"title":"STARTED","messageSeqs":[7],"source":{"kind":"provider","provider":"session-title-first-prompt-llm","model":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":21,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"start-child"},"content":[{"type":"tool-result","toolCallId":"start-child","content":[{"type":"text","text":"started subagent {{sessionId}}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":22,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":23,"time":0,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":24,"time":0,"data":{"turn":1,"step":2}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":25,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"PARENT_RECEIVED_CHILD_RESULT"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_RECEIVED_CHILD_RESULT"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":30,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_RECEIVED_CHILD_RESULT"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":31,"time":0,"data":{"turn":1,"step":2}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":32,"time":0,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":33,"time":0,"data":{"turn":1,"step":3}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":34,"time":0,"data":{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":35,"time":0,"data":{"turn":1,"step":3}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":36,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} -{"type":"result","sessionId":"{{sessionId}}","output":"PARENT_RECEIVED_CHILD_RESULT","usage":{"inputTokens":30,"outputTokens":15}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":36,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-replay: script exhausted — session requested model call #4 but its script has only 3; re-record the scenario","code":"UNKNOWN"}}}}} +{"type":"result","sessionId":"{{sessionId}}","output":"PARENT_RECEIVED_CHILD_RESULT","usage":{"inputTokens":20,"outputTokens":10}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl index 61ccf66eb5..ba2e593e50 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl @@ -4,19 +4,23 @@ {"type":"user/message","data":{"content":[{"type":"text","text":"\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nOld workspace instruction.\n"}],"source":{"kind":"agent-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"AGENTS.md\",\"CLAUDE.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"ba65bdb41810f4d0129129dcbd6cadcd643c069d"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","data":{}} +{"type":"permission/preset","data":{"preset":"workspace-write"}} +{"type":"sandbox/mode","data":{"mode":"workspace-write"}} +{"type":"approval/policy","data":{"policy":"ask"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"\nUpdated instructions from: AGENTS.md\n\nThis file changed after it was loaded. Use the following content instead of the previously loaded instructions from this file.\n\nNew workspace instruction after offline edit.\n\n"}],"source":{"kind":"agent-instructions","form":"instructions","changes":[{"action":"replace","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"d8375b516f158718bd3463bc8eb7ed42c011b29f"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"session/title","data":{"title":"Remember the workspace instruction.","messageSeqs":[1],"source":{"kind":"fallback"}}} -{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}"},"reason":"initial"}} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RESUME_DONE"}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RESUME_DONE"}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RESUME_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[14,15,16,17],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RESUME_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[18,19,20,21],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl index d65c290b35..0e793a4480 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl @@ -4,19 +4,23 @@ {"type":"user/message","data":{"content":[{"type":"text","text":"\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: CLAUDE.md\n\nOld CLAUDE rule.\n\nInstructions from: AGENTS.md\n\nOld AGENTS rule.\n"}],"source":{"kind":"agent-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"CLAUDE.md\",\"AGENTS.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000CLAUDE.md","path":"CLAUDE.md","digest":"b525eb8a6d3660b732dad4b0aff1b7c63ab32890"},{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"3113bd093ae91976207dcef7390bdc0b2bfcfa10"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","data":{}} +{"type":"permission/preset","data":{"preset":"workspace-write"}} +{"type":"sandbox/mode","data":{"mode":"workspace-write"}} +{"type":"approval/policy","data":{"policy":"ask"}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} {"type":"user/message","data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"\nThis complete workspace instruction baseline replaces all earlier workspace instruction baselines. The following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nCurrent AGENTS rule.\n\n\nInstructions from: CLAUDE.md\n\nCurrent CLAUDE rule.\n\n"}],"source":{"kind":"agent-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"AGENTS.md\",\"CLAUDE.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"7f53d2327837129750aef117f9754a001c46cf68"},{"action":"set","scope":".\u0000CLAUDE.md","path":"CLAUDE.md","digest":"5b1e9e3fd759eee6b43ceff899e47fb10c64701a"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"session/title","data":{"title":"Remember the workspace instruction.","messageSeqs":[1],"source":{"kind":"fallback"}}} -{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}"},"reason":"initial"}} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RESUME_DONE"}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RESUME_DONE"}}}} {"type":"assistant/chunk","data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RESUME_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[14,15,16,17],"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RESUME_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[18,19,20,21],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/cli.cordis.yml b/apps/cli/tests/profiles/headless/tests/fixtures/cli.cordis.yml deleted file mode 100644 index e44093e25f..0000000000 --- a/apps/cli/tests/profiles/headless/tests/fixtures/cli.cordis.yml +++ /dev/null @@ -1,29 +0,0 @@ -- id: cli-mock-llm - name: '../../../../../../../packages/test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts' - -- 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: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: cli-mock - model: cli-mock - cwd: !!js process.cwd() - workspaceContext: false - dshHome: './.dsh-home' - skills: - filesystem: - agentsHome: './.agents-home' - persona: 'Keyless headless-agent smoke.' - - id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/cli.patch.yml b/apps/cli/tests/profiles/headless/tests/fixtures/cli.patch.yml new file mode 100644 index 0000000000..76a138452d --- /dev/null +++ b/apps/cli/tests/profiles/headless/tests/fixtures/cli.patch.yml @@ -0,0 +1,37 @@ +# Keyless model and isolated-path patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: cli-mock + model: cli-mock + cwd: !!js process.cwd() + +- id: shell-env + config: + dshHome: './.dsh-home' + +- id: agent-instructions + disabled: true + +- id: skill-filesystem + config: + dshHome: './.dsh-home' + agentsHome: './.agents-home' + +- id: session-persistence-jsonl + config: + root: './.sessions' + +- insert: + - id: cli-mock-llm + name: '../../../../../../../packages/test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts' diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.cordis.yml b/apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.cordis.yml deleted file mode 100644 index 7f1c0b3e9e..0000000000 --- a/apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.cordis.yml +++ /dev/null @@ -1,23 +0,0 @@ -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ../../cordis.yml - patches: - - id: llm-deepseek - config: - baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL - thinking: enabled - reasoningEffort: low - streamIdleTimeoutMs: 150 - - id: agent-spine - config: - agents: - - id: main - provider: deepseek-official - model: deepseek-v4-flash - cwd: !!js process.cwd() - workspaceContext: false - persona: 'Keyless DeepSeek adapter defaults snapshot.' - - id: persistence - config: - root: './.sessions' diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.patch.yml b/apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.patch.yml new file mode 100644 index 0000000000..6809f21a93 --- /dev/null +++ b/apps/cli/tests/profiles/headless/tests/fixtures/deepseek-defaults.patch.yml @@ -0,0 +1,29 @@ +# Local endpoint and isolated-path patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + config: + baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL + thinking: enabled + reasoningEffort: low + streamIdleTimeoutMs: 150 + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek-official + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: agent-instructions + disabled: true + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/headless-profile.cordis.yml b/apps/cli/tests/profiles/headless/tests/fixtures/headless-profile.patch.yml similarity index 76% rename from apps/cli/tests/profiles/headless/tests/fixtures/headless-profile.cordis.yml rename to apps/cli/tests/profiles/headless/tests/fixtures/headless-profile.patch.yml index 8cef88baef..c6311dce7b 100644 --- a/apps/cli/tests/profiles/headless/tests/fixtures/headless-profile.cordis.yml +++ b/apps/cli/tests/profiles/headless/tests/fixtures/headless-profile.patch.yml @@ -1,3 +1,4 @@ +# Product headless profile patch for the keyless CLI snapshots. - id: agent-default-model config: provider: cli-mock diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/pi-ai-defaults.cordis.yml b/apps/cli/tests/profiles/headless/tests/fixtures/pi-ai-defaults.cordis.yml deleted file mode 100644 index c04a761062..0000000000 --- a/apps/cli/tests/profiles/headless/tests/fixtures/pi-ai-defaults.cordis.yml +++ /dev/null @@ -1,33 +0,0 @@ -- id: base - name: '@deepseek-ai/cordis-plugin-include' - config: - path: ../../cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - insert: - - id: llm-pi-ai - name: '@deepseek-ai/dsh-llm-pi-ai' - config: - providers: - deepseek: - apiKeyEnv: DEEPSEEK_API_KEY - baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL - reasoning: low - modelOverrides: - deepseek-v4-flash: - maxTokens: 1024 - streamIdleTimeoutMs: 1000 - - id: agent-spine - config: - agents: - - id: main - provider: deepseek - model: deepseek-v4-flash - cwd: !!js process.cwd() - workspaceContext: false - persona: 'Keyless pi-ai DeepSeek compatibility snapshot.' - - id: persistence - config: - root: './.sessions' diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/pi-ai-defaults.patch.yml b/apps/cli/tests/profiles/headless/tests/fixtures/pi-ai-defaults.patch.yml new file mode 100644 index 0000000000..ec5e74d781 --- /dev/null +++ b/apps/cli/tests/profiles/headless/tests/fixtures/pi-ai-defaults.patch.yml @@ -0,0 +1,37 @@ +# pi-ai route and isolated-path patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: llm-pi-ai + config: + providers: + deepseek: + apiKeyEnv: DEEPSEEK_API_KEY + baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL + reasoning: low + modelOverrides: + deepseek-v4-flash: + maxTokens: 1024 + streamIdleTimeoutMs: 1000 + +- id: agent-loop + config: + agents: + - id: main + provider: deepseek + model: deepseek-v4-flash + cwd: !!js process.cwd() + +- id: agent-instructions + disabled: true + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/startup-activation-error/activation-error.patch.yml b/apps/cli/tests/profiles/headless/tests/fixtures/startup-activation-error/activation-error.patch.yml new file mode 100644 index 0000000000..ce6c7ef949 --- /dev/null +++ b/apps/cli/tests/profiles/headless/tests/fixtures/startup-activation-error/activation-error.patch.yml @@ -0,0 +1,10 @@ +# Activation-failure patch over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- insert: + - id: activation-error + name: ./activation-error.mjs diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/startup-activation-error/cordis.yml b/apps/cli/tests/profiles/headless/tests/fixtures/startup-activation-error/cordis.yml deleted file mode 100644 index 2738e4a924..0000000000 --- a/apps/cli/tests/profiles/headless/tests/fixtures/startup-activation-error/cordis.yml +++ /dev/null @@ -1,2 +0,0 @@ -- id: activation-error - name: ./activation-error.mjs diff --git a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts index 89724891a3..b87618c6ad 100644 --- a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts @@ -19,27 +19,28 @@ import { describe, expect, it } from 'vitest' const goldensDir = fileURLToPath(new URL('./expected/', import.meta.url)) const goalScenarioDir = join(goldensDir, 'goal-tools') -const goalConfigPath = fileURLToPath(new URL('../goal.cordis.snapshot.yml', import.meta.url)) +const goalConfigPath = fileURLToPath(new URL('../goal-snapshot.patch.yml', import.meta.url)) const retryScenarioDir = join(goldensDir, 'provider-retry') -const retryConfigPath = fileURLToPath(new URL('../retry.cordis.snapshot.yml', import.meta.url)) +const retryConfigPath = fileURLToPath(new URL('../retry-snapshot.patch.yml', import.meta.url)) const credentialsScenarioDir = join(goldensDir, 'missing-credential') -const credentialsConfigPath = fileURLToPath(new URL('../credentials.cordis.snapshot.yml', import.meta.url)) +const credentialsConfigPath = fileURLToPath(new URL('../credentials-snapshot.patch.yml', import.meta.url)) // Same keyless composition as the missing-credential scenario: the endpoint is // never dialed either way, because a supplied-but-unusable key fails credential // resolution exactly where an absent one does. const invalidCredentialScenarioDir = join(goldensDir, 'invalid-credential') const settlementScenarioDir = join(goldensDir, 'subagent-settlement') -const settlementConfigPath = fileURLToPath(new URL('../subagent-settlement.cordis.snapshot.yml', import.meta.url)) -const teamConfigPath = fileURLToPath(new URL('../team.cordis.snapshot.yml', import.meta.url)) -const startupFailureConfigPath = fileURLToPath(new URL('./fixtures/startup-activation-error/cordis.yml', import.meta.url)) +const settlementConfigPath = fileURLToPath(new URL('../subagent-settlement-snapshot.patch.yml', import.meta.url)) +const teamConfigPath = fileURLToPath(new URL('../team-snapshot.patch.yml', import.meta.url)) +const startupFailureConfigPath = fileURLToPath(new URL('./fixtures/startup-activation-error/activation-error.patch.yml', import.meta.url)) +const startupFailurePluginUrl = new URL('./fixtures/startup-activation-error/activation-error.mjs', import.meta.url).href const startupFailureExpected = join(goldensDir, 'startup-activation-error', 'stderr.expected.txt') const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const dshBinScript = fileURLToPath(new URL('../../../../src/bin.ts', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) -const reasoningConfigPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url)) -const deepseekDefaultsConfigPath = fileURLToPath(new URL('./fixtures/deepseek-defaults.cordis.yml', import.meta.url)) -const piAiDefaultsConfigPath = fileURLToPath(new URL('./fixtures/pi-ai-defaults.cordis.yml', import.meta.url)) -const headlessOverlayPath = fileURLToPath(new URL('./fixtures/headless-profile.cordis.yml', import.meta.url)) +const reasoningConfigPath = fileURLToPath(new URL('./fixtures/cli.patch.yml', import.meta.url)) +const deepseekDefaultsConfigPath = fileURLToPath(new URL('./fixtures/deepseek-defaults.patch.yml', import.meta.url)) +const piAiDefaultsConfigPath = fileURLToPath(new URL('./fixtures/pi-ai-defaults.patch.yml', import.meta.url)) +const headlessOverlayPath = fileURLToPath(new URL('./fixtures/headless-profile.patch.yml', import.meta.url)) const headlessSessionExpected = join(goldensDir, 'headless-profile', 'session.expected.jsonl') const headlessReasoningExpected = join(goldensDir, 'headless-profile', 'reasoning.stderr.expected.txt') const headlessFailureExpected = join(goldensDir, 'headless-profile', 'stderr.expected.txt') @@ -260,7 +261,8 @@ describe('headless stream-json snapshots', () => { expectedExitCode: 1, }) expect(result.stdout).toBe('') - await expect(result.stderr).toMatchFileSnapshot(startupFailureExpected) + await expect(result.stderr.replace(startupFailurePluginUrl, './activation-error.mjs')) + .toMatchFileSnapshot(startupFailureExpected) }, LOADER_SMOKE_TEST_TIMEOUT_MS) it('retries a transient provider failure through the one-shot app', async () => { @@ -444,9 +446,11 @@ describe('headless stream-json snapshots', () => { }) expect(result.stderr).toBe('') - expect(server.requests).toHaveLength(1) - expect(server.requests[0]?.max_tokens).toBe(256_000) - expect(server.requests[0]?.reasoning_effort).toBe('low') + expect(server.requests).toHaveLength(2) + const agentRequest = server.requests.find(request => request.max_tokens === 256_000) + const titleRequest = server.requests.find(request => request.max_tokens === 64) + expect(agentRequest?.reasoning_effort).toBe('low') + expect(titleRequest).toBeDefined() const header = (parseJsonl(result.stdout) .map(record => record.event) .find((event): event is JsonObject => ( @@ -495,9 +499,11 @@ describe('headless stream-json snapshots', () => { }) expect(result.stderr).toBe('') - expect(server.requests).toHaveLength(1) - expect(server.requests[0]?.max_tokens).toBe(1024) - expect(server.requests[0]).not.toHaveProperty('max_completion_tokens') + expect(server.requests).toHaveLength(2) + const agentRequest = server.requests.find(request => request.max_tokens === 1024) + const titleRequest = server.requests.find(request => request.max_tokens === 64) + expect(agentRequest).not.toHaveProperty('max_completion_tokens') + expect(titleRequest).toBeDefined() const header = (parseJsonl(result.stdout) .map(record => record.event) .find((event): event is JsonObject => ( diff --git a/apps/cli/tests/profiles/headless/tests/keyless-smoke.e2e.ts b/apps/cli/tests/profiles/headless/tests/keyless-smoke.e2e.ts index a3541a8508..26dfc19a25 100644 --- a/apps/cli/tests/profiles/headless/tests/keyless-smoke.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/keyless-smoke.e2e.ts @@ -8,12 +8,12 @@ import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-l import type { SessionEvent } from '@deepseek-ai/dsh-session' const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) -const configPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('./fixtures/cli.patch.yml', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) const decompress = promisify(zstdDecompress) describe('headless-agent keyless smoke', () => { - it('boots the real Loader tree, runs a real bash tool round trip, and persists the turn', async () => { + it('boots the real Loader tree, runs the production shell tool, and persists the turn', async () => { let persistedHeader: Record | undefined const { stdout, stderr } = await runLoaderSmoke({ label: 'headless-agent', @@ -24,10 +24,11 @@ describe('headless-agent keyless smoke', () => { binArgs: [configPath, 'prove the tool path'], tsconfigPath, inspect: async (cwd) => { - const files = await readdir(cwd, { recursive: true }) + const sessionsDir = join(cwd, '.sessions') + const files = await readdir(sessionsDir, { recursive: true }) const relativePath = files.find(file => file.endsWith('.jsonl.zstd')) if (relativePath === undefined) return - const compressed = await readFile(join(cwd, relativePath)) + const compressed = await readFile(join(sessionsDir, relativePath)) expect(compressed.subarray(0, 4).toString('hex')).toBe('28b52ffd') persistedHeader = JSON.parse((await decompress(compressed)).toString()) as Record }, @@ -36,7 +37,8 @@ describe('headless-agent keyless smoke', () => { const events = lines.slice(0, -1).map(line => line['event'] as SessionEvent) const result = lines.at(-1) expect(stderr).toBe('') - expect(events.some(event => event.type === 'tool/call' && event.data.name === 'bash')).toBe(true) + const shellTool = process.platform === 'win32' ? 'pwsh' : 'bash' + expect(events.some(event => event.type === 'tool/call' && event.data.name === shellTool)).toBe(true) const toolResult = events.find(event => event.type === 'tool/result') expect(JSON.stringify(toolResult)).toContain('CLI_TOOL_ROUND_TRIP') expect(result).toMatchObject({ diff --git a/apps/cli/tests/profiles/headless/tests/real-model.e2e.ts b/apps/cli/tests/profiles/headless/tests/real-model.e2e.ts index 7705c7a37c..947b2031e6 100644 --- a/apps/cli/tests/profiles/headless/tests/real-model.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/real-model.e2e.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest' import { runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) -const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('../real-model.patch.yml', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) const hasKey = Boolean(process.env.DEEPSEEK_API_KEY) diff --git a/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts index 031ec836ee..0d80cba144 100644 --- a/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts @@ -13,7 +13,7 @@ const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), 'expected/seman const replayFixture = join(fixtureDir, 'replay.jsonl') const replayOverride = join(fixtureDir, 'replay.override.json') const sessionExpected = join(fixtureDir, 'session.expected.jsonl') -const configPath = fileURLToPath(new URL('../semantic-checkpoint.cordis.snapshot.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('../semantic-checkpoint-snapshot.patch.yml', import.meta.url)) const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) const sessionId = SessionId('semantic-checkpoint-unknown-outcome') diff --git a/apps/cli/tests/profiles/headless/tests/session-format-guard.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/session-format-guard.expected.e2e.ts index 8b36e431a2..aafe9af344 100644 --- a/apps/cli/tests/profiles/headless/tests/session-format-guard.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/session-format-guard.expected.e2e.ts @@ -21,7 +21,7 @@ import { describe, expect, it } from 'vitest' const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), 'expected/workspace-context-resume/offline-edit') const replayFixture = join(fixtureDir, 'replay.jsonl') -const configPath = fileURLToPath(new URL('../workspace-context-resume.cordis.snapshot.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('../workspace-context-resume-snapshot.patch.yml', import.meta.url)) const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) // The resumed-agent fixture in the shared config resumes exactly this id. diff --git a/apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts index e9e977eb3e..22bca50b83 100644 --- a/apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts @@ -18,7 +18,7 @@ import { describe, expect, it } from 'vitest' const fixtureDir = fileURLToPath(new URL('./expected/subagent-diagnostic', import.meta.url)) const replayOverride = join(fixtureDir, 'replay.override.json') const parentExpected = join(fixtureDir, 'parent.expected.jsonl') -const configPath = fileURLToPath(new URL('../subagent-diagnostic.cordis.snapshot.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('../subagent-diagnostic-snapshot.patch.yml', import.meta.url)) const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) const parentId = SessionId('subagent-diagnostic-parent') diff --git a/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts index 504cef65cb..3536082391 100644 --- a/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts @@ -19,7 +19,7 @@ const replayOverride = join(fixtureDir, 'replay.override.json') const childReplay = join(fixtureDir, 'child.replay.jsonl') const parentExpected = join(fixtureDir, 'parent.expected.jsonl') const childExpected = join(fixtureDir, 'child.expected.jsonl') -const configPath = fileURLToPath(new URL('../subagent-inheritance.cordis.snapshot.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('../subagent-inheritance-snapshot.patch.yml', import.meta.url)) const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) const sessionId = SessionId('subagent-inheritance-parent') diff --git a/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts index fd2a5127a6..043c7f4514 100644 --- a/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts @@ -27,7 +27,7 @@ const replayFixture = join(fixtureDir, 'replay.jsonl') const replayOverride = join(fixtureDir, 'replay.override.json') const sessionExpected = join(fixtureDir, 'session.expected.jsonl') const precedenceExpected = join(dirname(fixtureDir), 'precedence-change/session.expected.jsonl') -const configPath = fileURLToPath(new URL('../workspace-context-resume.cordis.snapshot.yml', import.meta.url)) +const configPath = fileURLToPath(new URL('../workspace-context-resume-snapshot.patch.yml', import.meta.url)) const binScript = fileURLToPath(new URL('../../../../../../packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)) const sessionId = SessionId('workspace-context-resume') diff --git a/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml b/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml new file mode 100644 index 0000000000..7e8d306cc3 --- /dev/null +++ b/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml @@ -0,0 +1,30 @@ +# Patch for resuming one seeded workspace-instruction session over the shipped headless profile. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: llm-deepseek + disabled: true + +- id: agent-instructions + config: + maxBytes: 65536 + dshHome: !!js process.cwd() + '/.dsh' + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: replay + name: '@deepseek-ai/dsh-llm-replay' + config: + file: !!js process.env.DSH_SNAPSHOT_FILE + overrideFile: !!js process.env.DSH_SNAPSHOT_OVERRIDE + + # Publish the persisted agent before the driver selects the single root. + - id: resumed-agent + name: './tests/fixtures/workspace-context-resume-agent.ts' diff --git a/apps/cli/tests/profiles/headless/workspace-context-resume.cordis.snapshot.yml b/apps/cli/tests/profiles/headless/workspace-context-resume.cordis.snapshot.yml deleted file mode 100644 index 267ea0fb03..0000000000 --- a/apps/cli/tests/profiles/headless/workspace-context-resume.cordis.snapshot.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Keyless real-Loader composition for workspace-instruction resume -# reconciliation. The test seeds one persisted baseline, changes AGENTS.md -# while the session is offline, then resumes through the public agent service. - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: none - -- id: replay - name: '@deepseek-ai/dsh-llm-replay' - config: - file: !!js process.env.DSH_SNAPSHOT_FILE - overrideFile: !!js process.env.DSH_SNAPSHOT_OVERRIDE - -- id: fs-local - name: '@deepseek-ai/dsh-fs-local' - config: - cwd: !!js process.cwd() - -- id: agent - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: [] - workspaceContext: - maxBytes: 65536 - dshHome: !!js process.cwd() + '/.dsh' - skills: - enabled: false - toolBash: false - toolJobs: false - goals: false - -# Await the persisted resume before the headless driver inspects root agents. -- id: resumed-agent - name: './tests/fixtures/workspace-context-resume-agent.ts' diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index e004cf0452..ff8b88ba60 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/capability-seams.md -capability-seams.md: 5c3ba40df35f40e211de2bf90037f998eeb5a15d -capability-seams.zh.md: 47402e1b6d9514a77e8fff6c4f0d0a8bbb34586d +capability-seams.md: db2e3966b4cc4f0a1afa49bf4d7963ac69b1826d +capability-seams.zh.md: a5f0873cf80d22cf06dec8b1272942309a9bc0d6 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 5c3ba40df3..db2e3966b4 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -124,7 +124,8 @@ flowchart LR svc_agentDefaultModel["ctx.agentDefaultModel
Default Agent model selection"] pkg_headless["headless"] svc_agentLoop["ctx.agentLoop
Concrete loop driver"] - pkg_agent_spine_demo["agent-spine-demo"] + pkg_base["base"] + pkg_sdk_minimal["sdk-minimal"] pkg_goal["goal"] svc_goals["ctx.goals
Same-session goal domain"] pkg_e2b["e2b"] @@ -335,7 +336,8 @@ flowchart LR pkg_workspace --> svc_workspaceRegistry svc_agentDefaultModel --> pkg_api_session_controller svc_agentDefaultModel --> pkg_headless - svc_agentLoop --> pkg_agent_spine_demo + svc_agentLoop --> pkg_base + svc_agentLoop --> pkg_sdk_minimal svc_agentTeams --> pkg_experimental_client_ui_agent_team svc_agentTeams --> pkg_experimental_tool_agent_team svc_agents --> pkg_acp @@ -504,7 +506,7 @@ flowchart LR | `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | | `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. | | `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`api-session-controller`](../packages/api/session-controller), [`headless`](../packages/bundle/headless) | - | Layers the default ModelSelection through settings so direct and Host-backed Agent entry points share one state owner. | -| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | +| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`base`](../packages/bundle/base), [`sdk-minimal`](../packages/bundle/sdk-minimal) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. | | `ctx.e2b` | `core` | [`e2b`](../packages/e2b/e2b) | - | [`fs-e2b`](../packages/e2b/fs-e2b), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | - | Owns one shared E2B SDK handle, remote working directory, and final sandbox disposition so both fundamental E2B providers inhabit the same Linux runtime. | | `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | The bash executors, the PTY shell backend, the LSP host, and the out-of-process ACP, Codex, and Claude Code subagent backends spawn through ctx.subprocess; the service owns process coordinates, tree/session lifetime, stdio dispositions, terminal mechanics, and kill escalation. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 47402e1b6d..a5f0873cf8 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -126,7 +126,8 @@ flowchart LR svc_agentDefaultModel["ctx.agentDefaultModel
Default Agent model selection"] pkg_headless["headless"] svc_agentLoop["ctx.agentLoop
Concrete loop driver"] - pkg_agent_spine_demo["agent-spine-demo"] + pkg_base["base"] + pkg_sdk_minimal["sdk-minimal"] pkg_goal["goal"] svc_goals["ctx.goals
Same-session goal domain"] pkg_e2b["e2b"] @@ -337,7 +338,8 @@ flowchart LR pkg_workspace --> svc_workspaceRegistry svc_agentDefaultModel --> pkg_api_session_controller svc_agentDefaultModel --> pkg_headless - svc_agentLoop --> pkg_agent_spine_demo + svc_agentLoop --> pkg_base + svc_agentLoop --> pkg_sdk_minimal svc_agentTeams --> pkg_experimental_client_ui_agent_team svc_agentTeams --> pkg_experimental_tool_agent_team svc_agents --> pkg_acp @@ -506,7 +508,7 @@ flowchart LR | `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem) | [`tool-skill`](../packages/skill/tool-skill) | - | 合并提供方的 skill(技能)目录;tool-skill 渲染会话前缀目录,并加载完整的 skill 正文。 | | `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | - | 拥有实时 Agent 句柄、创建/恢复工厂 seam,以及进程本地的发起方传播。 | | `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`api-session-controller`](../packages/api/session-controller), [`headless`](../packages/bundle/headless) | - | 通过 settings 分层默认 `ModelSelection`,让直接入口与 Host 支撑的 Agent 入口共享同一个状态所有者。 | -| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | 唯一的具体循环插件;扩展包依赖 dsh-agent 的事件和服务,而不依赖此包。 | +| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`base`](../packages/bundle/base), [`sdk-minimal`](../packages/bundle/sdk-minimal) | - | 唯一的具体循环插件;扩展包依赖 dsh-agent 的事件和服务,而不依赖此包。 | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | 从会话日志折叠带修订版本的目标状态,并将实时延续激活保留在进程本地。 | | `ctx.e2b` | `core` | [`e2b`](../packages/e2b/e2b) | - | [`fs-e2b`](../packages/e2b/fs-e2b), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | - | 拥有一个共享的 E2B SDK 句柄、远程工作目录和最终沙箱处置,使两个基础 E2B 提供方处于同一个 Linux 运行时中。 | | `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | Bash 执行器、PTY shell 后端、LSP Host,以及进程外 ACP、Codex 和 Claude Code subagent 后端都通过 ctx.subprocess 执行 spawn;该服务负责进程坐标、进程树/会话生命周期、stdio 处置、终端机制和 kill 升级。 | diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 0eaec1e292..f702637696 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/config-catalog.md -config-catalog.md: c01b1dadbe29e7ca044aa47990edac575850cacf -config-catalog.zh.md: bdcf6fca49b9cc3832d9f25eb85264fb33e0ef89 +config-catalog.md: a50c9d6b148bb0bcf05edf198b24278aaf4c62dd +config-catalog.zh.md: 902a41105afe48b4ab4075d671667a22fc170250 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index c01b1dadbe..a50c9d6b14 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -159,99 +159,6 @@ export type PresetTrust = 'system' | 'user' Source: [`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts) - - -## `@deepseek-ai/dsh-agent-spine-demo` - -```ts config-catalog -/** - * Bundle config: each field forwarded verbatim to the child that owns it — - * `agents` to the agent loop (an app that pre-creates no agents, like the ACP - * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`, - * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener, - * dynamic-context policy, deployment persona, and explicit model-facing tool - * order), the `tools` object to the tool - * registry (its presentation `mode`), - * `dshHome` to bash environment and local skill discovery, `sessionTitle` to - * the fallback title service, `skills` to the - * skill registry/local provider/tool consumer, `workspaceContext` to the - * agent-instructions loader, `jobs` to the process-local job provider, and - * `toolBash`/`toolJobs` to the model-facing tool plugins this bundle owns. - * Provider adapters own their `retryPolicy`; this bundle always mounts its - * executor. - * `goals` opts into and configures the persisted goal domain plus its model tool - * and same-session driver; `invariants` configures global and package-filtered - * relational checks. Owner schemas supply defaults for optional input; - * workspace context instead requires an explicit byte budget or `false` because - * it changes model-visible input. Producer opt-in stays producer-local: - * `toolBash` configures bash only; independently composed producers keep their - * own config. Set `toolBash: false` when another plugin owns the model-facing - * `bash` name. - */ -export interface Config { - /** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */ - agents?: AgentLoopConfig['agents'] - /** Agent-loop concurrency cap; `1` is serial. */ - maxParallelToolCalls?: AgentLoopConfig['maxParallelToolCalls'] - /** Whether the system prompt includes the fixed Harness identity (default true). */ - includeHarnessIdentity?: SystemPromptConfig['includeHarnessIdentity'] - /** Whether model history includes dynamic runtime-context snapshots (default true). */ - includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext'] - /** The deployment persona (see dsh-system-prompt's `Config`). */ - persona?: SystemPromptConfig['persona'] - /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */ - toolOrder?: SystemPromptConfig['toolOrder'] - /** The tool registry's config — its presentation `mode` (see dsh-tools' `Config`). */ - tools?: ToolsConfig - /** DeepSeek Harness home directory shared by shell context and local skill discovery. */ - dshHome?: string - /** Deterministic fallback and accepted-title limits; omission uses the bundle's example policy. */ - sessionTitle?: SessionTitleConfig - /** Workspace-context loader controls with an explicit byte budget; set `false` for hermetic prompts. */ - workspaceContext: workspaceContext.Config | false - /** - * Skill registry, local provider, and model-facing consumer config. - * Skills use `enabled` because one nested config controls a provider stack; - * single model-tool plugins use `Config | false` to disable that one consumer. - */ - skills?: SkillConfig - /** Model-facing bash tool config, or false when another plugin owns `bash`. */ - toolBash?: toolBash.Config | false - /** Process-local background-job admission config. */ - jobs?: JobsConfig - /** Generic background-job controls; set false to keep the job service without model-facing job tools. */ - toolJobs?: toolJobs.Config | false - /** Global enablement and package-name filters for invariant companions. */ - invariants?: InvariantConfig - /** Opt-in persisted same-session goal stack; set false or omit to leave it unmounted. */ - goals?: GoalConfig | false -} - -/** Skill bundle config forwarded to the registry, local provider, and model-facing consumer. */ -export interface SkillConfig { - /** Mount the bundled local skill provider and model-facing skill tool (default true). */ - enabled?: boolean - /** Registry-level discovery cache settings. */ - registry?: SkillRegistryConfig - /** Local filesystem skill provider settings. */ - filesystem?: SkillFileSystem.Config - /** Model-facing skill catalog and tool settings. */ - tool?: toolSkill.Config -} - -/** Persisted goal domain, model-tool policy, and same-session driver config. */ -export interface GoalConfig { - /** Goal-domain creation defaults. */ - domain?: GoalDomainConfig - /** Model-facing goal-tool authority policy. */ - tool?: toolGoal.Config -} -``` - -Depends on: [`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`JobsConfig`](#deepseek-aidsh-jobs-local) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillFileSystem`](../packages/skill/skill-filesystem/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`toolBash`](../packages/shell/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`toolJobs`](../packages/jobs/tool-jobs/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`workspaceContext`](../packages/context/agent-instructions/src/index.ts) - -Source: [`packages/examples/agent-spine-demo/src/index.ts:94`](../packages/examples/agent-spine-demo/src/index.ts) - ## `@deepseek-ai/dsh-agent-tool-presentation` diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index bdcf6fca49..902a41105a 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -161,99 +161,6 @@ export type PresetTrust = 'system' | 'user' 来源:[`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts) - - -## `@deepseek-ai/dsh-agent-spine-demo` - -```ts config-catalog -/** - * Bundle config: each field forwarded verbatim to the child that owns it — - * `agents` to the agent loop (an app that pre-creates no agents, like the ACP - * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`, - * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener, - * dynamic-context policy, deployment persona, and explicit model-facing tool - * order), the `tools` object to the tool - * registry (its presentation `mode`), - * `dshHome` to bash environment and local skill discovery, `sessionTitle` to - * the fallback title service, `skills` to the - * skill registry/local provider/tool consumer, `workspaceContext` to the - * agent-instructions loader, `jobs` to the process-local job provider, and - * `toolBash`/`toolJobs` to the model-facing tool plugins this bundle owns. - * Provider adapters own their `retryPolicy`; this bundle always mounts its - * executor. - * `goals` opts into and configures the persisted goal domain plus its model tool - * and same-session driver; `invariants` configures global and package-filtered - * relational checks. Owner schemas supply defaults for optional input; - * workspace context instead requires an explicit byte budget or `false` because - * it changes model-visible input. Producer opt-in stays producer-local: - * `toolBash` configures bash only; independently composed producers keep their - * own config. Set `toolBash: false` when another plugin owns the model-facing - * `bash` name. - */ -export interface Config { - /** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */ - agents?: AgentLoopConfig['agents'] - /** Agent-loop concurrency cap; `1` is serial. */ - maxParallelToolCalls?: AgentLoopConfig['maxParallelToolCalls'] - /** Whether the system prompt includes the fixed Harness identity (default true). */ - includeHarnessIdentity?: SystemPromptConfig['includeHarnessIdentity'] - /** Whether model history includes dynamic runtime-context snapshots (default true). */ - includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext'] - /** The deployment persona (see dsh-system-prompt's `Config`). */ - persona?: SystemPromptConfig['persona'] - /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */ - toolOrder?: SystemPromptConfig['toolOrder'] - /** The tool registry's config — its presentation `mode` (see dsh-tools' `Config`). */ - tools?: ToolsConfig - /** DeepSeek Harness home directory shared by shell context and local skill discovery. */ - dshHome?: string - /** Deterministic fallback and accepted-title limits; omission uses the bundle's example policy. */ - sessionTitle?: SessionTitleConfig - /** Workspace-context loader controls with an explicit byte budget; set `false` for hermetic prompts. */ - workspaceContext: workspaceContext.Config | false - /** - * Skill registry, local provider, and model-facing consumer config. - * Skills use `enabled` because one nested config controls a provider stack; - * single model-tool plugins use `Config | false` to disable that one consumer. - */ - skills?: SkillConfig - /** Model-facing bash tool config, or false when another plugin owns `bash`. */ - toolBash?: toolBash.Config | false - /** Process-local background-job admission config. */ - jobs?: JobsConfig - /** Generic background-job controls; set false to keep the job service without model-facing job tools. */ - toolJobs?: toolJobs.Config | false - /** Global enablement and package-name filters for invariant companions. */ - invariants?: InvariantConfig - /** Opt-in persisted same-session goal stack; set false or omit to leave it unmounted. */ - goals?: GoalConfig | false -} - -/** Skill bundle config forwarded to the registry, local provider, and model-facing consumer. */ -export interface SkillConfig { - /** Mount the bundled local skill provider and model-facing skill tool (default true). */ - enabled?: boolean - /** Registry-level discovery cache settings. */ - registry?: SkillRegistryConfig - /** Local filesystem skill provider settings. */ - filesystem?: SkillFileSystem.Config - /** Model-facing skill catalog and tool settings. */ - tool?: toolSkill.Config -} - -/** Persisted goal domain, model-tool policy, and same-session driver config. */ -export interface GoalConfig { - /** Goal-domain creation defaults. */ - domain?: GoalDomainConfig - /** Model-facing goal-tool authority policy. */ - tool?: toolGoal.Config -} -``` - -依赖:[`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`JobsConfig`](#deepseek-aidsh-jobs-local) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillFileSystem`](../packages/skill/skill-filesystem/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`toolBash`](../packages/shell/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`toolJobs`](../packages/jobs/tool-jobs/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`workspaceContext`](../packages/context/agent-instructions/src/index.ts) - -来源:[`packages/examples/agent-spine-demo/src/index.ts:94`](../packages/examples/agent-spine-demo/src/index.ts) - ## `@deepseek-ai/dsh-agent-tool-presentation` diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index a2eca48433..ad8b4d6232 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -10,9 +10,9 @@ 本文介绍 DeepSeek Harness 整体架构,它是 **DeepSeek Code** 的底层基座。微内核设计讨论中确立了核心设计准则:**一切皆插件**。内核刻意做得极精简,仅包含少量抽象服务,外加一个实体循环插件 `dsh-agent-loop`。所有产品功能均基于本文定义的扩展接口开发为独立插件,无需改动主循环逻辑。 -> Dependency rule: extension plugins depend on interfaces, never on `dsh-agent-loop` (the loop is swappable); the sanctioned exception is the composition bundle `dsh-agent-spine-demo`, whose job is assembling the concrete spine. +> Dependency rule: extension plugins depend on interfaces, never on `dsh-agent-loop` (the loop is swappable); composition bundles such as `dsh-base` and `dsh-sdk-minimal` may assemble the concrete loop. -依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-spine-demo`,它的职责是组装整套实体主干。 +依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);`dsh-base` 与 `dsh-sdk-minimal` 等组合包可以组装具体循环。 > This document covers **behavior**; type definitions live in [subsystems/](../subsystems/core.md), the per-event/service reference lives in the generated regions of [subsystems/](../subsystems/core.md), and package contracts in the package READMEs state each package's required configuration and behavior ([map](../../packages/README.md)). diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index 8727d17738..bd7d42b362 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/module-graph.md -module-graph.md: 6b69abd956658b5bf17f0b49158ec8f366888d94 -module-graph.zh.md: 4af048cc46db9bcf990a702d29970a67ff222949 +module-graph.md: 7177faafa35c11cd7e922e60a3ff50bf24e57969 +module-graph.zh.md: a50aa3bec30b7d1f274f6c6d106cd3f8746d8980 diff --git a/docs/module-graph.md b/docs/module-graph.md index 6b69abd956..7177faafa3 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -205,9 +205,6 @@ flowchart TD pkg_fs_e2b["fs-e2b"] pkg_subprocess_e2b["subprocess-e2b"] end - subgraph group_examples["packages/examples"] - pkg_agent_spine_demo["agent-spine-demo"] - end subgraph group_experimental["packages/experimental"] pkg_experimental_agent_team["experimental-agent-team"] pkg_experimental_agent_team_profile["experimental-agent-team-profile"] @@ -1131,28 +1128,6 @@ flowchart TD pkg_session_reference --> pkg_session_query pkg_session_reference --> pkg_session_title pkg_session_reference --> pkg_typert_protocol - pkg_agent_spine_demo --> pkg_agent - pkg_agent_spine_demo --> pkg_agent_instructions - pkg_agent_spine_demo --> pkg_agent_loop - pkg_agent_spine_demo --> pkg_goal - pkg_agent_spine_demo --> pkg_goal_round_driver - pkg_agent_spine_demo --> pkg_home_paths - pkg_agent_spine_demo --> pkg_invariants - pkg_agent_spine_demo --> pkg_jobs_local - pkg_agent_spine_demo --> pkg_llm - pkg_agent_spine_demo --> pkg_llm_retry - pkg_agent_spine_demo --> pkg_scope - pkg_agent_spine_demo --> pkg_session - pkg_agent_spine_demo --> pkg_session_title - pkg_agent_spine_demo --> pkg_shell_env - pkg_agent_spine_demo --> pkg_skill - pkg_agent_spine_demo --> pkg_skill_filesystem - pkg_agent_spine_demo --> pkg_system_prompt - pkg_agent_spine_demo --> pkg_tool_bash - pkg_agent_spine_demo --> pkg_tool_goal - pkg_agent_spine_demo --> pkg_tool_jobs - pkg_agent_spine_demo --> pkg_tool_skill - pkg_agent_spine_demo --> pkg_tools pkg_webhook_github --> pkg_credentials pkg_webhook_github --> pkg_host_webserver pkg_webhook_github --> pkg_invariants @@ -1558,7 +1533,6 @@ flowchart TD | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`typert-protocol`](../packages/typert/protocol) | -| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs-local`](../packages/jobs/jobs-local), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`shell-env`](../packages/shell/shell-env), [`skill`](../packages/skill/skill), [`skill-filesystem`](../packages/skill/skill-filesystem), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/shell/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) | | [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 4af048cc46..a50aa3bec3 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -207,9 +207,6 @@ flowchart TD pkg_fs_e2b["fs-e2b"] pkg_subprocess_e2b["subprocess-e2b"] end - subgraph group_examples["packages/examples"] - pkg_agent_spine_demo["agent-spine-demo"] - end subgraph group_experimental["packages/experimental"] pkg_experimental_agent_team["experimental-agent-team"] pkg_experimental_agent_team_profile["experimental-agent-team-profile"] @@ -1133,28 +1130,6 @@ flowchart TD pkg_session_reference --> pkg_session_query pkg_session_reference --> pkg_session_title pkg_session_reference --> pkg_typert_protocol - pkg_agent_spine_demo --> pkg_agent - pkg_agent_spine_demo --> pkg_agent_instructions - pkg_agent_spine_demo --> pkg_agent_loop - pkg_agent_spine_demo --> pkg_goal - pkg_agent_spine_demo --> pkg_goal_round_driver - pkg_agent_spine_demo --> pkg_home_paths - pkg_agent_spine_demo --> pkg_invariants - pkg_agent_spine_demo --> pkg_jobs_local - pkg_agent_spine_demo --> pkg_llm - pkg_agent_spine_demo --> pkg_llm_retry - pkg_agent_spine_demo --> pkg_scope - pkg_agent_spine_demo --> pkg_session - pkg_agent_spine_demo --> pkg_session_title - pkg_agent_spine_demo --> pkg_shell_env - pkg_agent_spine_demo --> pkg_skill - pkg_agent_spine_demo --> pkg_skill_filesystem - pkg_agent_spine_demo --> pkg_system_prompt - pkg_agent_spine_demo --> pkg_tool_bash - pkg_agent_spine_demo --> pkg_tool_goal - pkg_agent_spine_demo --> pkg_tool_jobs - pkg_agent_spine_demo --> pkg_tool_skill - pkg_agent_spine_demo --> pkg_tools pkg_webhook_github --> pkg_credentials pkg_webhook_github --> pkg_host_webserver pkg_webhook_github --> pkg_invariants @@ -1560,7 +1535,6 @@ flowchart TD | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`typert-protocol`](../packages/typert/protocol) | -| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs-local`](../packages/jobs/jobs-local), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`shell-env`](../packages/shell/shell-env), [`skill`](../packages/skill/skill), [`skill-filesystem`](../packages/skill/skill-filesystem), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/shell/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) | | [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index 1d5d3f0400..34ee576787 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md -core.md: 4cdd9b02adeefa7dee33c5432c4824413000f04e -core.zh.md: 592c5fb519e2331a5d7b10c380091be9ea2b4b25 +core.md: 08c4f5f32ea291d70a3918e92a1f7c4c23a7b3dd +core.zh.md: 48ecdad1e1d45b660215e88e908f3dea288afe64 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 4cdd9b02ad..08c4f5f32e 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -17,7 +17,7 @@ A turn flows through the six packages in one loop: the driver in [`agent-loop`]( | `agent-loop/` | The concrete driver implementing the public `Agent` contract (`ctx.agentLoop`) | this page | | `scope/` | The scoped-registration primitive the registries and loop build per-agent scoping on | [scope.md](scope.md) | -`scope/` is the one non-service package: a dependency-free library (`createScope`/`scopeOf`/`scopeTarget`) that sits below `session/` and `system-prompt/` in the module graph precisely so they can consume it without a cycle. `agent-loop` is the one concrete implementation of the public `Agent` contract and lives here because it is the harness's default product loop; it runs each driver inside `ctx.agents.withInitiator()`. Extension plugins depend on `agent` — including when they need the initiating Agent — and never on `agent-loop` directly, so the loop stays swappable. The default composition that wires this spine into a runnable agent is [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md). +`scope/` is the one non-service package: a dependency-free library (`createScope`/`scopeOf`/`scopeTarget`) that sits below `session/` and `system-prompt/` in the module graph precisely so they can consume it without a cycle. `agent-loop` is the one concrete implementation of the public `Agent` contract and lives here because it is the harness's default product loop; it runs each driver inside `ctx.agents.withInitiator()`. Extension plugins depend on `agent` — including when they need the initiating Agent — and never on `agent-loop` directly, so the loop stays swappable. [`dsh-base`](../../packages/bundle/base/README.md) is the default product composition, while [`dsh-sdk-minimal`](../../packages/bundle/sdk-minimal/README.md) declares a smaller standalone tree. ## Creation and ownership diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 592c5fb519..48ecdad1e1 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -17,7 +17,7 @@ | `agent-loop/` | 实现公开 `Agent` 约定的具体 driver(`ctx.agentLoop`) | 本页 | | `scope/` | 注册表与循环用于构建按 agent 作用域的注册原语 | [scope.md](scope.zh.md) | -`scope/` 是这里唯一的非服务包:一个零依赖库(`createScope`/`scopeOf`/`scopeTarget`),在模块图中位于 `session/` 与 `system-prompt/` 之下,正是为了让它们消费它而不形成环。`agent-loop` 是公开 `Agent` 约定的唯一具体实现,放在这里因为它是 harness 的默认产品循环;它在 `ctx.agents.withInitiator()` 内运行每个 driver。扩展插件依赖 `agent`——包括需要发起 Agent 时——而绝不直接依赖 `agent-loop`,因此循环保持可替换。把这条主干接成可运行 agent 的默认组合是 [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.zh.md)。 +`scope/` 是这里唯一的非服务包:一个零依赖库(`createScope`/`scopeOf`/`scopeTarget`),在模块图中位于 `session/` 与 `system-prompt/` 之下,正是为了让它们消费它而不形成环。`agent-loop` 是公开 `Agent` 约定的唯一具体实现,放在这里因为它是 harness 的默认产品循环;它在 `ctx.agents.withInitiator()` 内运行每个 driver。扩展插件依赖 `agent`——包括需要发起 Agent 时——而绝不直接依赖 `agent-loop`,因此循环保持可替换。[`dsh-base`](../../packages/bundle/base/README.zh.md) 是默认产品组合,[`dsh-sdk-minimal`](../../packages/bundle/sdk-minimal/README.zh.md) 则声明一棵更小的独立配置树。 diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index 158a9ead5c..e974e49ade 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/README.md -README.md: e1a729a6c80d8f8125e0d4c4b038dc631edd7a26 -README.zh.md: 754ad0fc44677afb243c3a32a0281f58ec3b3af2 +README.md: 9c298d3f2426bab92a6d602495d836ef8ee9fb44 +README.zh.md: 309a0e678968927b79d527991447847d0fa45f83 diff --git a/packages/README.md b/packages/README.md index e1a729a6c8..9c298d3f24 100644 --- a/packages/README.md +++ b/packages/README.md @@ -74,7 +74,6 @@ Every package lives in exactly one group; new packages join existing groups, and | [`boot/`](boot/README.md) | Shared app-bin boot glue | | [`host/`](host/README.md) | Web-GUI host half: API gateway + HTTP route server | | [`client/`](client/README.md) | Web-GUI browser half: shell, wire, object services, slots, `ui-*` plugins | -| [`examples/`](examples/README.md) | Reusable composition bundles for tests and custom deployments | | [`test-support/`](test-support/README.md) | Support infrastructure (testkits, invariants, replay, Loader smokes) | | [`runtime-diagnostics/`](runtime-diagnostics/README.md) | Runtime diagnostics: package-owned invariant checks and reports | | [`util/`](util/README.md) | Low-level zero-dependency utilities shared across groups (`Branded`, home/path helpers, timeout, retention) | @@ -84,7 +83,7 @@ Every package lives in exactly one group; new packages join existing groups, and ## Release expectations -Most groups are product — stable API. The exceptions: `e2b/` is a POC, `experimental/` is unreleased, and `examples/`, `test-support/`, `runtime-diagnostics/`, and `util/` are support with lower compatibility expectations. +Most groups are product — stable API. The exceptions: `e2b/` is a POC, `experimental/` is unreleased, and `test-support/`, `runtime-diagnostics/`, and `util/` are support with lower compatibility expectations. ----- @@ -93,7 +92,7 @@ Most groups are product — stable API. The exceptions: `e2b/` is a POC, `experi The dependency graph is generated: [docs/module-graph.md](../docs/module-graph.md) (`pnpm run gen-module-graph`, freshness-gated in CI). -**Extension plugins depend on Service Definitions, never concrete providers.** `dsh-agent-loop` is swappable; UI, hook, and tool plugins use `dsh-agent`. Composition bundles, including `dsh-agent-spine-demo`, may depend on spine plugins. Capabilities separate Service Definition / Service Provider / Consumer roles when they evolve independently; see [capability seams](../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md). +**Extension plugins depend on Service Definitions, never concrete providers.** `dsh-agent-loop` is swappable; UI, hook, and tool plugins use `dsh-agent`. Composition bundles may depend on spine plugins. Capabilities separate Service Definition / Service Provider / Consumer roles when they evolve independently; see [capability seams](../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md). ----- diff --git a/packages/README.zh.md b/packages/README.zh.md index 754ad0fc44..309a0e6789 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -74,7 +74,6 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`boot/`](boot/README.zh.md) | 共享的 app bin 启动粘合层 | | [`host/`](host/README.zh.md) | web GUI 宿主半侧:API 网关 + HTTP 路由服务器 | | [`client/`](client/README.zh.md) | web GUI 浏览器半侧:shell、协议层、对象服务、slot、`ui-*` 插件 | -| [`examples/`](examples/README.zh.md) | 供测试与自定义部署复用的组合包 | | [`test-support/`](test-support/README.zh.md) | 支持基础设施(testkit、不变式、回放、Loader 冒烟测试) | | [`runtime-diagnostics/`](runtime-diagnostics/README.zh.md) | 运行时诊断:按包归属的运行时不变式检查与报告 | | [`util/`](util/README.zh.md) | 组间共享的低层零依赖工具(`Branded`、home/路径辅助函数、超时、留存) | @@ -84,7 +83,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 ## 发布预期 -大多数组是产品——稳定 API。例外:`e2b/` 是 POC,`experimental/` 不发布,`examples/`、`test-support/`、`runtime-diagnostics/` 与 `util/` 是兼容性预期较低的支持组。 +大多数组是产品——稳定 API。例外:`e2b/` 是 POC,`experimental/` 不发布,`test-support/`、`runtime-diagnostics/` 与 `util/` 是兼容性预期较低的支持组。 ----- @@ -93,7 +92,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 依赖图由工具生成:[docs/module-graph.md](../docs/module-graph.zh.md)(`pnpm run gen-module-graph`,CI 中有新鲜度门禁)。 -**扩展插件依赖 Service Definition,绝不依赖具体提供方。** `dsh-agent-loop` 可替换;UI、钩子和工具插件使用 `dsh-agent`。包括 `dsh-agent-spine-demo` 在内的组合包可以依赖主干插件。能力在需要独立演进时分离 Service Definition / Service Provider / Consumer 角色;详见[能力 seam](../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)。 +**扩展插件依赖 Service Definition,绝不依赖具体提供方。** `dsh-agent-loop` 可替换;UI、钩子和工具插件使用 `dsh-agent`。组合包可以依赖主干插件。能力在需要独立演进时分离 Service Definition / Service Provider / Consumer 角色;详见[能力 seam](../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)。 ----- diff --git a/packages/bundle/sdk-minimal/README.i18n.yaml b/packages/bundle/sdk-minimal/README.i18n.yaml index 8230113dff..b47c43bf62 100644 --- a/packages/bundle/sdk-minimal/README.i18n.yaml +++ b/packages/bundle/sdk-minimal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bundle/sdk-minimal/README.md -README.md: cf9333cd4304f24344b79d54b4149dadf246c325 -README.zh.md: eeda31bff9e34fd20dae0e43c5678c0e398e0649 +README.md: f1db30ccf0e3bc0e09801eff225d72c20c3184bc +README.zh.md: 0163a6ffd71bff9811ec4a556bd2240d5c389312 diff --git a/packages/bundle/sdk-minimal/README.md b/packages/bundle/sdk-minimal/README.md index cf9333cd43..f1db30ccf0 100644 --- a/packages/bundle/sdk-minimal/README.md +++ b/packages/bundle/sdk-minimal/README.md @@ -46,7 +46,7 @@ The profile mounts exactly one persistent shell stack: Bash on Linux and macOS,
Implementation internals — click to expand -The bundle's single insert is the complete application tree: SDK stdio startup and JSON-RPC serving, one environment-configured DeepSeek adapter, the executor-less agent spine, local subprocess and unrestricted filesystem providers, a platform-selected persistent shell PTY, the string-replace editor, and uncompressed JSONL persistence under `$DSH_HOME/sessions`. It does not inherit another bundle, so every extra row is an explicit profile change. +The bundle's single insert is the complete application tree: SDK stdio startup and JSON-RPC serving, one environment-configured DeepSeek adapter, the explicit agent core, local subprocess and unrestricted filesystem providers, a platform-selected persistent shell PTY, the string-replace editor, and uncompressed JSONL persistence under `$DSH_HOME/sessions`. It does not inherit another bundle, so every extra row is an explicit profile change. ### Source map diff --git a/packages/bundle/sdk-minimal/README.zh.md b/packages/bundle/sdk-minimal/README.zh.md index eeda31bff9..0163a6ffd7 100644 --- a/packages/bundle/sdk-minimal/README.zh.md +++ b/packages/bundle/sdk-minimal/README.zh.md @@ -46,7 +46,7 @@ dsh --profile sdk-minimal
实现细节——点击展开 -该 bundle 的单个 insert 就是完整应用配置树:SDK stdio 启动与 JSON-RPC 服务、一个由环境配置的 DeepSeek 适配器、无执行器 agent 主干、本地子进程与不受限文件系统提供方、按平台选择的持久 shell PTY、字符串替换编辑器,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 持久化。它不继承其他 bundle,因此每个额外配置项都是显式 profile 变更。 +该 bundle 的单个 insert 就是完整应用配置树:SDK stdio 启动与 JSON-RPC 服务、一个由环境配置的 DeepSeek 适配器、显式 agent 核心、本地子进程与不受限文件系统提供方、按平台选择的持久 shell PTY、字符串替换编辑器,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 持久化。它不继承其他 bundle,因此每个额外配置项都是显式 profile 变更。 ### 源码地图 diff --git a/packages/bundle/sdk-minimal/cordis.patch.yml b/packages/bundle/sdk-minimal/cordis.patch.yml index 0d0c1ff974..4375e2ab67 100644 --- a/packages/bundle/sdk-minimal/cordis.patch.yml +++ b/packages/bundle/sdk-minimal/cordis.patch.yml @@ -70,17 +70,62 @@ config: cwd: !!js process.cwd() - - id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' + # Keep the minimal agent kernel explicit: unlike dsh-base, this profile + # owns each service row and omits every optional producer it does not use. + - id: timer + name: '@deepseek-ai/cordis-plugin-timer' + + - id: llm + name: '@deepseek-ai/dsh-llm' + + - id: session + name: '@deepseek-ai/dsh-session' + + - id: session-title + name: '@deepseek-ai/dsh-session-title' + config: + fallbackMaxWords: 5 + fallbackMaxBytes: 40 + maxTitleBytes: 80 + + - id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' config: includeHarnessIdentity: false includeRuntimeContext: false persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.' - workspaceContext: false - skills: - enabled: false - toolBash: false - toolJobs: false + + - id: tools + name: '@deepseek-ai/dsh-tools' + + - id: agent + name: '@deepseek-ai/dsh-agent' + + - id: llm-retry + name: '@deepseek-ai/dsh-llm-retry' + + - id: jobs + name: '@deepseek-ai/dsh-jobs-local' + + - id: invariants + name: '@deepseek-ai/dsh-invariants' + + - id: session-invariant + name: '@deepseek-ai/dsh-session/invariant' + + - id: agent-invariant + name: '@deepseek-ai/dsh-agent/invariant' + + - id: scope-invariant + name: '@deepseek-ai/dsh-scope/invariant' + + - id: agent-loop-invariant + name: '@deepseek-ai/dsh-agent-loop/invariant' + + - id: agent-loop + name: '@deepseek-ai/dsh-agent-loop' + config: + agents: [] - id: persistent-bash name: '@deepseek-ai/dsh-tool-bash-persistent' diff --git a/packages/bundle/sdk-minimal/package.json b/packages/bundle/sdk-minimal/package.json index 0d11c33943..8589093590 100644 --- a/packages/bundle/sdk-minimal/package.json +++ b/packages/bundle/sdk-minimal/package.json @@ -39,24 +39,35 @@ } }, "dependencies": { - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", + "@deepseek-ai/cordis-plugin-timer": "workspace:^", + "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-jobs-local": "workspace:^", + "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm-deepseek": "workspace:^", + "@deepseek-ai/dsh-llm-retry": "workspace:^", "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:^", "@deepseek-ai/dsh-sandbox-local": "workspace:^", "@deepseek-ai/dsh-sandbox-policy": "workspace:^", + "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-sdk-app": "workspace:^", "@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:^", + "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-log-deepseek": "workspace:^", "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", "@deepseek-ai/dsh-session-projection": "workspace:^", + "@deepseek-ai/dsh-session-title": "workspace:^", "@deepseek-ai/dsh-subprocess-local": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-terminal": "workspace:^", "@deepseek-ai/dsh-terminal-bash": "workspace:^", "@deepseek-ai/dsh-tool-bash-persistent": "workspace:^", "@deepseek-ai/dsh-tool-pwsh-persistent": "workspace:^", - "@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^" + "@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^" }, "peerDependencies": { "@deepseek-ai/dsh-invariants": "workspace:^", diff --git a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts index f202ecfaa6..062e0f7ba4 100644 --- a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts +++ b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts @@ -7,6 +7,10 @@ import * as yaml from 'js-yaml' import { describe, expect, it } from 'vitest' import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' +function packageName(specifier: string): string { + return specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0]! +} + describe('dsh-sdk-minimal bundle', () => { it('declares one standalone allowlisted tree with every row dependency', () => { const root = fileURLToPath(new URL('..', import.meta.url)) @@ -36,7 +40,21 @@ describe('dsh-sdk-minimal bundle', () => { ['terminal-bash', '@deepseek-ai/dsh-terminal-bash'], ['terminal-pwsh', '@deepseek-ai/dsh-terminal-bash'], ['fs-local', '@deepseek-ai/dsh-fs-local'], - ['agent-spine', '@deepseek-ai/dsh-agent-spine-demo'], + ['timer', '@deepseek-ai/cordis-plugin-timer'], + ['llm', '@deepseek-ai/dsh-llm'], + ['session', '@deepseek-ai/dsh-session'], + ['session-title', '@deepseek-ai/dsh-session-title'], + ['system-prompt', '@deepseek-ai/dsh-system-prompt'], + ['tools', '@deepseek-ai/dsh-tools'], + ['agent', '@deepseek-ai/dsh-agent'], + ['llm-retry', '@deepseek-ai/dsh-llm-retry'], + ['jobs', '@deepseek-ai/dsh-jobs-local'], + ['invariants', '@deepseek-ai/dsh-invariants'], + ['session-invariant', '@deepseek-ai/dsh-session/invariant'], + ['agent-invariant', '@deepseek-ai/dsh-agent/invariant'], + ['scope-invariant', '@deepseek-ai/dsh-scope/invariant'], + ['agent-loop-invariant', '@deepseek-ai/dsh-agent-loop/invariant'], + ['agent-loop', '@deepseek-ai/dsh-agent-loop'], ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], ['persistent-pwsh', '@deepseek-ai/dsh-tool-pwsh-persistent'], ['str-replace-editor', '@deepseek-ai/dsh-tool-str-replace-editor'], @@ -52,14 +70,12 @@ describe('dsh-sdk-minimal bundle', () => { defaultContextWindow: { __jsExpr: 'Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)' }, streamIdleTimeoutMs: 172800000, }) - expect(rows.find(row => row.id === 'agent-spine')?.config).toMatchObject({ + expect(rows.find(row => row.id === 'system-prompt')?.config).toEqual({ includeHarnessIdentity: false, includeRuntimeContext: false, - workspaceContext: false, - skills: { enabled: false }, - toolBash: false, - toolJobs: false, + persona: { __jsExpr: "process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'" }, }) + expect(rows.find(row => row.id === 'agent-loop')?.config).toEqual({ agents: [] }) expect(rows.find(row => row.id === 'terminal-bash')).toMatchObject({ disabled: { __jsExpr: "process.platform === 'win32'" }, }) @@ -68,7 +84,7 @@ describe('dsh-sdk-minimal bundle', () => { config: { shellDialect: 'pwsh', timeoutMs: 300000 }, }) expect(Object.keys(manifest.dependencies ?? {}).sort()).toEqual( - [...new Set(rows.map(row => row.name).filter((name): name is string => name !== undefined))].sort(), + [...new Set(rows.map(row => row.name).filter((name): name is string => name !== undefined).map(packageName))].sort(), ) }) }) diff --git a/packages/context/README.i18n.yaml b/packages/context/README.i18n.yaml index 24012ae844..586b1f103e 100644 --- a/packages/context/README.i18n.yaml +++ b/packages/context/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/README.md -README.md: 3da0b89e5c974f03b966dee70a38a55a8adc5208 -README.zh.md: c623d48d58a2c771fd6be9a2502a3ad5a9ee10e6 +README.md: 12ca57d2bbcaca78c1e2d699c6ea3eae73155d67 +README.zh.md: 32b350c37eacebc67dc4b045a527dea92201f8e7 diff --git a/packages/context/README.md b/packages/context/README.md index 3da0b89e5c..12ca57d2bb 100644 --- a/packages/context/README.md +++ b/packages/context/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -The context group provides plugins that add model-visible context to each request without defining any tool: workspace instruction files become guidance, `@file` mentions offer path completion, other sessions can be referenced as bounded snapshots, and the model can see the current time and the agent's tmux location. All of them are opt-in except `agent-instructions`, which ships enabled in the default `dsh-agent-spine-demo` bundle and can be disabled through bundle config. Context is durable: injected instructions and references enter session history as user-role messages, so they persist, replay, and compact like other conversation content. This page maps the group; each package README owns the per-package contract. +The context group provides plugins that add model-visible context to each request without defining any tool: workspace instruction files become guidance, `@file` mentions offer path completion, other sessions can be referenced as bounded snapshots, and the model can see the current time and the agent's tmux location. All of them are opt-in except `agent-instructions`, which `dsh-base` includes by default and a profile patch can disable. Context is durable: injected instructions and references enter session history as user-role messages, so they persist, replay, and compact like other conversation content. This page maps the group; each package README owns the per-package contract. ## Table of Contents diff --git a/packages/context/README.zh.md b/packages/context/README.zh.md index c623d48d58..32b350c37e 100644 --- a/packages/context/README.zh.md +++ b/packages/context/README.zh.md @@ -9,7 +9,7 @@ kind: "package-group" ## 概述 -context 组提供不定义任何工具、为每次请求添加模型可见上下文的插件:工作区指令文件成为指引,`@file` mention 提供路径补全,其他会话可以作为有界快照被引用,模型还能看到当前时间与 agent 的 tmux 位置。除 `agent-instructions`(随默认 `dsh-agent-spine-demo` 组合包发布并默认启用,可通过组合包配置禁用)外,其余全部需主动启用。上下文是持久的:注入的指令与引用以 user 角色消息进入会话历史,因此与其他对话内容一样持久、可回放、可压缩。本页是组的映射;包级约定由各包 README 负责。 +context 组提供不定义任何工具、为每次请求添加模型可见上下文的插件:工作区指令文件成为指引,`@file` mention 提供路径补全,其他会话可以作为有界快照被引用,模型还能看到当前时间与 agent 的 tmux 位置。除 `agent-instructions`(`dsh-base` 默认包含它,profile patch 可以禁用)外,其余全部需主动启用。上下文是持久的:注入的指令与引用以 user 角色消息进入会话历史,因此与其他对话内容一样持久、可回放、可压缩。本页是组的映射;包级约定由各包 README 负责。 ## 目录 diff --git a/packages/context/agent-instructions/README.i18n.yaml b/packages/context/agent-instructions/README.i18n.yaml index 19102ec246..54936af968 100644 --- a/packages/context/agent-instructions/README.i18n.yaml +++ b/packages/context/agent-instructions/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/agent-instructions/README.md -README.md: 798134a9e11a4e2cf6337f1f7a2d9c79410860eb -README.zh.md: d187494ec0f6a907df1907d6420f589792beff8b +README.md: 164f7071d3decd6c94d5464d4be1930f62465865 +README.zh.md: 13d515fea3d649f4663b29efe95820a8f01a4980 diff --git a/packages/context/agent-instructions/README.md b/packages/context/agent-instructions/README.md index 798134a9e1..164f7071d3 100644 --- a/packages/context/agent-instructions/README.md +++ b/packages/context/agent-instructions/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-agent-instructions` loads `AGENTS.md`-compatible workspace instruction files into model context: the user-global file and the project chain reach the first request as one durable baseline, and successful `read`, `write`, or `edit` calls bring newly relevant nested files, changes, and removals into later requests. It ships enabled in the default `dsh-agent-spine-demo` bundle and can be disabled through bundle config. Everything is bounded by a byte budget: broader files are omitted before the most specific file is truncated, and an empty chain contributes nothing. There is no file watcher — external edits become visible on the next successful filesystem touch or when a resumed session reconciles its baseline. +`dsh-agent-instructions` loads `AGENTS.md`-compatible workspace instruction files into model context: the user-global file and the project chain reach the first request as one durable baseline, and successful `read`, `write`, or `edit` calls bring newly relevant nested files, changes, and removals into later requests. `dsh-base` includes it by default, and a profile patch can disable it. Everything is bounded by a byte budget: broader files are omitted before the most specific file is truncated, and an empty chain contributes nothing. There is no file watcher — external edits become visible on the next successful filesystem touch or when a resumed session reconciles its baseline. ## Table of Contents @@ -25,7 +25,7 @@ English | [中文](README.zh.md) ## Use this package -Mount this plugin when agents should work from the workspace's own instruction files. The default spine bundle already includes it with a 65,536-byte budget, so most compositions only adjust `maxBytes`; providerless trees load nothing until a filesystem provider is present. +Mount this plugin when agents should work from the workspace's own instruction files. `dsh-base` already includes it with a 65,536-byte budget, so base-backed profiles only need to replace the row when they want another `maxBytes`; providerless trees load nothing until a filesystem provider is present. ### What the agent gets diff --git a/packages/context/agent-instructions/README.zh.md b/packages/context/agent-instructions/README.zh.md index d187494ec0..13d515fea3 100644 --- a/packages/context/agent-instructions/README.zh.md +++ b/packages/context/agent-instructions/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -`dsh-agent-instructions` 将兼容 `AGENTS.md` 的工作区指令文件加载到模型上下文:用户全局文件与项目指令链作为一条持久基线进入第一次请求,成功的 `read`、`write` 或 `edit` 调用会把新出现的嵌套文件、变更与移除带入后续请求。它随默认 `dsh-agent-spine-demo` 组合包发布并默认启用,可通过组合包配置禁用。一切内容都受字节预算约束:较宽泛的文件先被省略,最具体的文件最后被截断,空指令链不产生任何内容。没有文件 watcher——外部编辑会在下一次成功的文件系统 touch 时,或恢复后的会话对账其基线时变得可见。 +`dsh-agent-instructions` 将兼容 `AGENTS.md` 的工作区指令文件加载到模型上下文:用户全局文件与项目指令链作为一条持久基线进入第一次请求,成功的 `read`、`write` 或 `edit` 调用会把新出现的嵌套文件、变更与移除带入后续请求。`dsh-base` 默认包含它,profile patch 可以禁用。一切内容都受字节预算约束:较宽泛的文件先被省略,最具体的文件最后被截断,空指令链不产生任何内容。没有文件 watcher——外部编辑会在下一次成功的文件系统 touch 时,或恢复后的会话对账其基线时变得可见。 ## 目录 @@ -25,7 +25,7 @@ kind: "package-reference" ## 使用本包 -当 agent(智能体)需要依据工作区自身的指令文件工作时,挂载此插件。默认 spine 组合包已包含它并给予 65,536 字节预算,因此大多数组合只需调整 `maxBytes`;没有文件系统提供方的树加载不到任何内容,直到提供方出现。 +当 agent(智能体)需要依据工作区自身的指令文件工作时,挂载此插件。`dsh-base` 已包含它并给予 65,536 字节预算,因此基于 base 的 profile 仅在需要其他 `maxBytes` 时替换该配置行;没有文件系统提供方的树加载不到任何内容,直到提供方出现。 ### agent 获得的内容 diff --git a/packages/context/time-context/package.json b/packages/context/time-context/package.json index 4cc80f48ea..ca543d05bb 100644 --- a/packages/context/time-context/package.json +++ b/packages/context/time-context/package.json @@ -49,7 +49,6 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", diff --git a/packages/context/time-context/tests/fixtures/cordis.yml b/packages/context/time-context/tests/fixtures/cordis.yml deleted file mode 100644 index c334acc55d..0000000000 --- a/packages/context/time-context/tests/fixtures/cordis.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Test-only composition: keep time-context opt-in while exercising its real Loader/app path. -- id: time-context-mock-llm - name: './mock-llm.ts' - -# Managed child-process groups for the bash executor (spawn/kill/output plumbing). -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: bash - name: '@deepseek-ai/dsh-bash-local' - -- id: time-context - name: '@deepseek-ai/dsh-time-context' - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: time-context-mock - model: time-context-mock - cwd: !!js process.cwd() - persona: 'Test the time-context plugin.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: 'none' - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/context/time-context/tests/fixtures/driver.ts b/packages/context/time-context/tests/fixtures/driver.ts index 00479b4221..93bbfb686a 100644 --- a/packages/context/time-context/tests/fixtures/driver.ts +++ b/packages/context/time-context/tests/fixtures/driver.ts @@ -1,13 +1,18 @@ #!/usr/bin/env node /** Test driver that sends two turns through one Headless Loader composition. */ -import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke' +import { bootProductionProfile } from '../../../../test-support/loader-smoke/tests/fixtures/production-profile.ts' const configPath = process.argv[2] if (configPath === undefined) throw new Error('time-context driver requires a config path') -const ctx = await boot('time-context-e2e', resolveConfigPath(configPath, undefined)) +const ctx = await bootProductionProfile({ + binName: 'time-context-e2e', + profile: 'headless', + overlayPaths: [resolveConfigPath(configPath, undefined)], +}) try { await runFixtureTurn(ctx, { task: 'first' }) await runFixtureTurn(ctx, { task: 'second' }) diff --git a/packages/context/time-context/tests/fixtures/time-context.patch.yml b/packages/context/time-context/tests/fixtures/time-context.patch.yml new file mode 100644 index 0000000000..399d5d78ac --- /dev/null +++ b/packages/context/time-context/tests/fixtures/time-context.patch.yml @@ -0,0 +1,27 @@ +# Test-only patch over the shipped headless profile. The production bundle +# layers own the agent services, tools, persistence plugin, and defaults. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: time-context-mock + model: time-context-mock + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: time-context-mock-llm + name: './mock-llm.ts' + + - id: time-context + name: '@deepseek-ai/dsh-time-context' diff --git a/packages/context/time-context/tests/time-context.e2e.ts b/packages/context/time-context/tests/time-context.e2e.ts index e986708df6..fbc999af05 100644 --- a/packages/context/time-context/tests/time-context.e2e.ts +++ b/packages/context/time-context/tests/time-context.e2e.ts @@ -12,7 +12,7 @@ const driver = fileURLToPath(new URL( import.meta.url, )) const configPath = fileURLToPath(new URL( - './fixtures/cordis.yml', + './fixtures/time-context.patch.yml', import.meta.url, )) const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) @@ -27,7 +27,7 @@ async function jsonlFiles(dir: string): Promise { return paths.flat() } -describe('time-context through a real headless cordis.yml', () => { +describe('time-context through the production headless profile', () => { it('uses the process zone and persists one ordered context event per request', async () => { let events: SessionEvent[] = [] const { stderr } = await runLoaderSmoke({ @@ -49,7 +49,9 @@ describe('time-context through a real headless cordis.yml', () => { expect(events.filter(event => event.type === 'turn/end')).toHaveLength(2) const contexts = events.filter( - (event): event is SessionEvent<'user/message'> => event.type === 'user/message' && event.data.source.kind === 'plugin') + (event): event is SessionEvent<'user/message'> => event.type === 'user/message' + && event.data.source.kind === 'plugin' + && event.data.source.plugin === 'time-context') const starts = events.filter(event => event.type === 'step/start') expect(contexts).toHaveLength(2) expect(starts).toHaveLength(2) diff --git a/packages/core/README.i18n.yaml b/packages/core/README.i18n.yaml index 77a982c6c8..0bf7fdc6d7 100644 --- a/packages/core/README.i18n.yaml +++ b/packages/core/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/README.md -README.md: 8e68cfb93eabbcb867e4bf3262c4cdaed98406ab -README.zh.md: 0b81a48a88f1da32fc8f74a74bc8ae567a716591 +README.md: fbcf4bcf35486f847089ff868199e8a56171e215 +README.zh.md: 778715c68c480595df41187c95a1f717717a7bfb diff --git a/packages/core/README.md b/packages/core/README.md index 8e68cfb93e..fbcf4bcf35 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -The core group provides the product API spine of the DeepSeek Harness: an append-only session log, system-prompt assembly, a tool registry, the `Agent` handle, and the concrete loop that drives them. Every composition boots these packages, and plugins and consumers build against their stable contracts. A turn flows through all of them — the loop claims a prompt, opens a turn on the session log, assembles the request through system-prompt, streams the model response, dispatches tool calls through the registry, and appends every model-visible fact back to the log. Choose this group when you build an agent or extend one; the runnable default composition is [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md). +The core group provides the product API spine of the DeepSeek Harness: an append-only session log, system-prompt assembly, a tool registry, the `Agent` handle, and the concrete loop that drives them. Every composition boots these packages, and plugins and consumers build against their stable contracts. A turn flows through all of them — the loop claims a prompt, opens a turn on the session log, assembles the request through system-prompt, streams the model response, dispatches tool calls through the registry, and appends every model-visible fact back to the log. Choose this group when you build an agent or extend one; the default product composition is [`dsh-base`](../bundle/base/README.md). ## Table of Contents @@ -33,7 +33,7 @@ The core group provides the product API spine of the DeepSeek Harness: an append | [`agent-default-model/`](agent-default-model/README.md) | The deployment default model selection entry points apply to fresh agents | `ctx.agentDefaultModel` | | [`agent-loop/`](agent-loop/README.md) | The default agent driver: creates agents and runs the turn and step lifecycle | `ctx.agentLoop` | -`scope` supplies the shared scoping primitive; `agent` owns the public `Agent` contract, while `agent-loop` is its default implementation, so extension plugins depend on `agent` and the driver stays swappable. `agent-default-model` owns the deployment selection an entry point applies when a session has none of its own. Runnable compositions live in [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md); this group owns only the swappable spine pieces. +`scope` supplies the shared scoping primitive; `agent` owns the public `Agent` contract, while `agent-loop` is its default implementation, so extension plugins depend on `agent` and the driver stays swappable. `agent-default-model` owns the deployment selection an entry point applies when a session has none of its own. Runnable compositions live under [`packages/bundle`](../bundle/README.md); this group owns only the swappable spine pieces. ----- @@ -46,7 +46,8 @@ The core group provides the product API spine of the DeepSeek Harness: an append - [Tools subsystem](../../docs/subsystems/tools.md) — the tool execution pipeline and presentation vocabulary. - [Scoped registration subsystem](../../docs/subsystems/scope.md) — the scoped-layer primitive these registries build on. - [Architecture](../../docs/architecture.md) — the turn flow and where new behavior goes. -- [agent-spine-demo](../examples/agent-spine-demo/README.md) — the default runnable composition. +- [Base bundle](../bundle/base/README.md) — the default product composition. +- [SDK minimal bundle](../bundle/sdk-minimal/README.md) — a complete standalone composition with a deliberately smaller feature set. ----- diff --git a/packages/core/README.zh.md b/packages/core/README.zh.md index 0b81a48a88..778715c68c 100644 --- a/packages/core/README.zh.md +++ b/packages/core/README.zh.md @@ -9,7 +9,7 @@ kind: "package-group" ## 概述 -core 分组提供 DeepSeek Harness 的产品 API 主干:仅追加的会话日志、系统提示词组装、工具注册表、`Agent` 句柄,以及驱动它们的具体循环。每个组合都会启动这些包,插件与消费方构建所依赖的正是它们稳定的约定。一个轮次会流经其中全部环节——循环领取提示词,在会话日志上打开轮次,通过 system-prompt 组装请求,流式接收模型响应,通过注册表分发工具调用,并把每个模型可见的事实追加回日志。构建或扩展 agent 时请选择本分组;默认可运行组合是 [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.zh.md)。 +core 分组提供 DeepSeek Harness 的产品 API 主干:仅追加的会话日志、系统提示词组装、工具注册表、`Agent` 句柄,以及驱动它们的具体循环。每个组合都会启动这些包,插件与消费方构建所依赖的正是它们稳定的约定。一个轮次会流经其中全部环节——循环领取提示词,在会话日志上打开轮次,通过 system-prompt 组装请求,流式接收模型响应,通过注册表分发工具调用,并把每个模型可见的事实追加回日志。构建或扩展 agent 时请选择本分组;默认产品组合是 [`dsh-base`](../bundle/base/README.zh.md)。 ## 目录 @@ -33,7 +33,7 @@ core 分组提供 DeepSeek Harness 的产品 API 主干:仅追加的会话日 | [`agent-default-model/`](agent-default-model/README.zh.md) | 入口对全新 agent 应用的部署默认模型选择 | `ctx.agentDefaultModel` | | [`agent-loop/`](agent-loop/README.zh.md) | 默认 agent 驱动器:创建 agent 并运行轮次与步骤生命周期 | `ctx.agentLoop` | -`scope` 提供共享作用域原语;`agent` 拥有公开的 `Agent` 约定,而 `agent-loop` 是其默认实现,因此扩展插件依赖 `agent`,驱动器保持可替换。`agent-default-model` 拥有会话自身没有选择时由入口应用的部署选择。可运行组合位于 [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.zh.md);本分组只拥有可替换的主干组件。 +`scope` 提供共享作用域原语;`agent` 拥有公开的 `Agent` 约定,而 `agent-loop` 是其默认实现,因此扩展插件依赖 `agent`,驱动器保持可替换。`agent-default-model` 拥有会话自身没有选择时由入口应用的部署选择。可运行组合位于 [`packages/bundle`](../bundle/README.zh.md);本分组只拥有可替换的主干组件。 ----- @@ -46,7 +46,8 @@ core 分组提供 DeepSeek Harness 的产品 API 主干:仅追加的会话日 - [工具子系统](../../docs/subsystems/tools.zh.md)——工具执行流水线与呈现词汇。 - [作用域注册子系统](../../docs/subsystems/scope.zh.md)——这些注册表所依赖的作用域层原语。 - [架构](../../docs/architecture.zh.md)——轮次流与新行为归属。 -- [agent-spine-demo](../examples/agent-spine-demo/README.zh.md)——默认可运行组合。 +- [基础组合包](../bundle/base/README.zh.md)——默认产品组合。 +- [SDK 最小组合包](../bundle/sdk-minimal/README.zh.md)——完整、独立且功能集更小的组合。 ----- diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 0e12ae0be0..be8d264f6f 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/agent-loop/README.md -README.md: f1830c3293b4ca37fe102aa6868bbb612dc773a1 -README.zh.md: 671a117d64baeb77f083a6d429e5bb205e667fe6 +README.md: a37d75545b4c1eccb4363c7ff2f09ae102f480db +README.zh.md: a209e6a99d96cb8790a19b0ebb043332d619026f diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index f1830c3293..a37d75545b 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -25,7 +25,7 @@ English | [中文](README.zh.md) ## Use this package -Mount `dsh-agent-loop` in any composition that should run agents. It supplies the driver behind `ctx.agents` and starts any agents you declare in its config; the standard demo composition is [`examples/agent-spine-demo`](../../../packages/examples/agent-spine-demo/README.md). +Mount `dsh-agent-loop` in any composition that should run agents. It supplies the driver behind `ctx.agents` and starts any agents you declare in its config; both [`dsh-base`](../../bundle/base/README.md) and [`dsh-sdk-minimal`](../../bundle/sdk-minimal/README.md) mount it as an explicit row. ### Configure declarative agents diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index 671a117d64..a209e6a99d 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -25,7 +25,7 @@ kind: "package-reference" ## 使用本包 -在任何应运行 agent 的组合中挂载 `dsh-agent-loop`。它提供 `ctx.agents` 背后的驱动器,并启动你在配置中声明的 agent;标准演示组合是 [`examples/agent-spine-demo`](../../../packages/examples/agent-spine-demo/README.zh.md)。 +在任何应运行 agent 的组合中挂载 `dsh-agent-loop`。它提供 `ctx.agents` 背后的驱动器,并启动你在配置中声明的 agent;[`dsh-base`](../../bundle/base/README.zh.md) 与 [`dsh-sdk-minimal`](../../bundle/sdk-minimal/README.zh.md) 都将它作为显式配置行挂载。 ### 配置声明式 agent diff --git a/packages/examples/README.i18n.yaml b/packages/examples/README.i18n.yaml deleted file mode 100644 index d962a63423..0000000000 --- a/packages/examples/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/examples/README.md -README.md: 42a5bf5548a897fa245d192252044c1fbea5d94e -README.zh.md: 67e458e21293f6dfeaf0adb0475143f915423eef diff --git a/packages/examples/README.md b/packages/examples/README.md deleted file mode 100644 index 42a5bf5548..0000000000 --- a/packages/examples/README.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -description: "The examples package group: reusable agent-spine composition bundles for tests and custom deployments." -kind: "package-group" ---- - -# examples/ — reusable composition bundles - -English | [中文](README.zh.md) - -## Summary - -The examples group provides the reusable agent spine for tests and custom deployments that need a concrete composition without assembling it by hand. Its `-demo` npm suffix marks it as support infrastructure rather than a product interface. ACP, SDK, and one-shot applications launch through the `acp`, `sdk` or `sdk-minimal`, and `headless` profiles. This group contains no application entrypoint. - -## Table of Contents - -- [Packages](#packages) -- [Related documentation](#related-documentation) -- [Dev Note](#dev-note) - ------ - - -## Packages - -| Package | npm name | Role | -|---|---|---| -| [`agent-spine-demo/`](agent-spine-demo/README.md) | `@deepseek-ai/dsh-agent-spine-demo` | Working agent core you mount and configure with your own LLM and executor | - -`agent-spine-demo` is the shared agent core. Product application assemblies live under [`bundle/`](../bundle/README.md); this support package remains available to focused tests and custom compositions. - ------ - - -## Related documentation - -- [ACP application bundle](../bundle/acp-app/README.md) — the `dsh --profile acp` application for programmatic clients. -- [SDK application bundle](../bundle/sdk-app/README.md) — the `dsh --profile sdk` application for JSON-RPC clients. -- [Minimal SDK bundle](../bundle/sdk-minimal/README.md) — the standalone two-tool SDK profile used by the Python example. - - -## Dev Note - -None. diff --git a/packages/examples/README.zh.md b/packages/examples/README.zh.md deleted file mode 100644 index 67e458e212..0000000000 --- a/packages/examples/README.zh.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -description: "examples 包组:供测试与自定义部署使用的可复用 agent-spine 组合 bundle。" -kind: "package-group" ---- - -# examples/:可复用组合 bundle - -[English](README.md) | 中文 - -## 概述 - -examples 组提供可复用 agent 主干,供需要具体组合但不想手工组装的测试与自定义部署使用。其 npm 名称中的 `-demo` 后缀表明它是支持基础设施,而非产品接口。ACP、SDK 与单次执行应用分别通过 `acp`、`sdk` 或 `sdk-minimal`、`headless` profile 启动。本组不包含应用入口。 - -## 目录 - -- [包](#packages) -- [相关文档](#related-documentation) -- [开发备注](#dev-note) - ------ - - -## 包 - -| 包 | npm 名称 | 角色 | -|---|---|---| -| [`agent-spine-demo/`](agent-spine-demo/README.zh.md) | `@deepseek-ai/dsh-agent-spine-demo` | 可挂载、可用自己的 LLM 与执行器配置的工作 agent 核心 | - -`agent-spine-demo` 是共享 agent 核心。产品应用装配位于 [`bundle/`](../bundle/README.zh.md);这个支持包继续供聚焦测试与自定义组合使用。 - ------ - - -## 相关文档 - -- [ACP 应用组合包](../bundle/acp-app/README.zh.md)——面向程序化客户端的 `dsh --profile acp` 应用。 -- [SDK 应用组合包](../bundle/sdk-app/README.zh.md)——面向 JSON-RPC 客户端的 `dsh --profile sdk` 应用。 -- [极简 SDK 组合包](../bundle/sdk-minimal/README.zh.md)——Python 示例使用的独立双工具 SDK profile。 - - -## 开发备注 - -无。 diff --git a/packages/examples/agent-spine-demo/README.i18n.yaml b/packages/examples/agent-spine-demo/README.i18n.yaml deleted file mode 100644 index d6dd65615e..0000000000 --- a/packages/examples/agent-spine-demo/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/examples/agent-spine-demo/README.md -README.md: 727effb9f6c9c4b1b5af6c7f3da8aea50b0641fa -README.zh.md: df84c48e8271cdddcbf68e7659e8014e0c223ae1 diff --git a/packages/examples/agent-spine-demo/README.md b/packages/examples/agent-spine-demo/README.md deleted file mode 100644 index 727effb9f6..0000000000 --- a/packages/examples/agent-spine-demo/README.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -description: "The default executor-less, UI-less agent spine as one Cordis bundle plugin: the fixed service set, concrete loop, and model-facing consumers an app package composes by adding an entry point and backends." -kind: "package-reference" ---- - -# @deepseek-ai/dsh-agent-spine-demo - -English | [中文](README.zh.md) - -## Summary - -`dsh-agent-spine-demo` gives you a working agent in one plugin: mount it, add an LLM adapter and an executor, and you can run a full agent conversation — in-memory sessions with automatic titles, a system prompt with your persona and workspace instructions, tools for bash, skills, and background jobs, and a loop that runs turns with retries. You configure it in user terms: persona, tool order, workspace-context budget, which agents to pre-create, optional persisted goals, and background-job limits. It ships no UI, executor, or persistence backend, and it adds no prompts or tool schemas of its own — the model sees only what your configuration produces. Use it when you are building a headless, ACP, or JSON-RPC agent and want the common agent machinery without building it yourself. Read this package for what you get out of the box and what you must supply. - -## Table of Contents - -- [Use this package](#use-this-package) -- [Understand the implementation](#understand-the-implementation) -- [Further Exploration](#further-exploration) -- [Model Experience](#model-experience) -- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) -- [Dev Note](#dev-note) - ------ - - -## Use this package - -Use this bundle when you are building an agent without a UI and want the common machinery handled for you: sessions, prompts, tools, and the turn loop. The common path is explicit — mount the bundle with a workspace-context budget, add an LLM adapter and a bash executor as sibling plugins, and provide `agents` when your entry point pre-creates them. When everything is configured, your agent accepts a prompt and returns a completed answer while the process keeps its session in memory. - -### What the bundle includes - -Out of the box you get: in-memory sessions with automatic fallback titles; a system prompt assembled from your persona, the Harness identity, and optional workspace instructions; model-facing tools for bash, local skills, and background jobs; optional persisted goals the agent can create and track; and a loop that runs turns with provider-routed retries. Add a session-persistence provider when sessions must survive process exit. The full plugin list lives in the implementation section below. - -### What stays outside the bundle - -These pieces are yours to supply; the bundle leaves them out so each entry point can pick its own: - -- **The LLM adapter** — you add the provider that calls a model; pick `llm-deepseek`, `llm-pi-ai`, or `llm-replay`. -- **Model-backed session-title providers** — you get deterministic fallback titles out of the box; add at most one model-based titling provider if you want smarter titles. -- **The bash executor** — the bundle provides the bash tool; you add the executor that runs commands (`bash-local` or a sandboxed implementation). -- **Non-local skill providers** — local skills load out of the box; add providers for embedded or remote catalogs if you need them. -- **The entry point and per-app infrastructure** — headless, ACP, and JSON-RPC apps own transport and output choices; you pick the one that fits your deployment. - -### Minimal configuration - -The smallest working setup mounts the bundle with a workspace-context budget, plus an LLM adapter and an executor: - -```yaml -- name: '@deepseek-ai/dsh-agent-spine-demo' - config: - workspaceContext: - maxBytes: 4096 -- name: '@deepseek-ai/dsh-llm-deepseek' # concrete adapter for ctx.llm -- name: '@deepseek-ai/dsh-bash-local' # executor for ctx.shell -``` - -`workspaceContext` is the one required field: give it a byte budget so workspace files load into the agent's context, or set `false` for hermetic prompts. `agents` defaults to none, so pass the agents you want running — or omit it when your entry point creates them on demand (the ACP app does this). You know the setup works when the agent answers a first prompt and its session is saved. - -| Field | Default | What it configures | -|---|---|---| -| `agents` | `[]` | which agents your entry point pre-creates; omit to create them on demand | -| `maxParallelToolCalls` | agent-loop default | how many tool calls may run at once; `1` is serial | -| `includeHarnessIdentity` | `true` | whether the system prompt names the DeepSeek Harness identity | -| `includeRuntimeContext` | `true` | whether the agent's history includes dynamic runtime-context snapshots | -| `persona` | `''` | the deployment persona text in the system prompt | -| `toolOrder` | lexicographic | the order the model sees tools in | -| `tools` | `{ mode: 'native' }` | how tools reach the model: native schemas, PTC mode, or both | -| `dshHome` | `$DSH_HOME` or `~/.dsh` | the harness home used for the bash environment and local skill folders | -| `sessionTitle` | example limits | fallback title limits: 5 words, 40 fallback bytes, 80 accepted bytes | -| `workspaceContext` | required | byte budget for loading workspace files into context, or `false` | -| `skills` | enabled | whether local skills load and the skill tool is available | -| `toolBash` | mounted | the bash tool; set `false` when another plugin owns `bash` | -| `jobs` | owner default | how many background jobs each owner may run at once | -| `toolJobs` | mounted | the background-job control tools; `false` keeps jobs without the tools | -| `invariants` | owner default | developer setting: which package checks run and which packages are filtered | -| `goals` | unmounted | optional persisted goals the agent can create and track across turns | - -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-agent-spine-demo) is the exhaustive source for every accepted field and its source declaration. - -### Request retry and billing - -Provider requests are retried automatically: if a call fails, the agent tries again in a new numbered step. Retries can cost you — each attempt may be billed, and `always` mode retries without limit — so usage accounting stays at your entry point. The retry itself does not clutter the conversation; you see the successful result. - ------ - - -## Understand the implementation - -
-Implementation internals — click to expand - -This section explains how the bundle composes the spine and points at the code that realizes it; the observable behavior is fully covered in [Use this package](#use-this-package). - -### Composition model - -`apply(ctx, config)` mounts each child plugin under the bundle fiber and forwards every config field to the child that owns it. Cordis pends each fiber on its `inject` declarations until the services it needs exist, so load order is irrelevant to correctness; the listing below mirrors dependency layering for readability. `pickSpineConfig()` copies only the bundle-owned fields from an app config, so entry-point settings never leak into the spine; conflicting `dshHome` values fail during composition because local skills and the managed bash environment must share one harness home. The retry policy keeps retry status, provider errors, and failed partial chunks outside model history, and it reconstructs a retried request with its prior prefix intact so provider caches stay reusable. - -### The tree it loads - -```text -@deepseek-ai/cordis-plugin-timer timer service (writes nothing to stdout) -@deepseek-ai/dsh-llm abstract LLM service + content-block vocabulary -@deepseek-ai/dsh-session event-sourced session log + store -@deepseek-ai/dsh-session-title log-backed title service + deterministic fallback -@deepseek-ai/dsh-system-prompt prompt-section + tool-schema assembly -@deepseek-ai/dsh-tools registry + guarded pre/around/post/final-result pipeline -@deepseek-ai/dsh-skill skill provider registry -@deepseek-ai/dsh-skill-filesystem local filesystem skill provider -@deepseek-ai/dsh-agent agent registry + initiator scope + agent/* events -@deepseek-ai/dsh-goal optional persisted same-session goal domain -@deepseek-ai/dsh-tool-goal optional model-facing goal controls -@deepseek-ai/dsh-goal-round-driver optional same-session goal-round driver -@deepseek-ai/dsh-llm-retry provider-routed request retry policy -@deepseek-ai/dsh-jobs-local generic background-job registry -@deepseek-ai/dsh-invariants configurable invariant registry service -@deepseek-ai/dsh-session/invariant -@deepseek-ai/dsh-agent/invariant -@deepseek-ai/dsh-scope/invariant -@deepseek-ai/dsh-agent-loop/invariant package-owned relational checks -@deepseek-ai/dsh-shell-env managed DSH_* shell environment for model shell calls (unless toolBash=false) -@deepseek-ai/dsh-tool-bash the model-facing bash schema (unless toolBash=false) -@deepseek-ai/dsh-agent-instructions AGENTS.md/CLAUDE.md workspace context loader -@deepseek-ai/dsh-tool-skill session-prefix skill catalog + model-facing loader schema -@deepseek-ai/dsh-tool-jobs job_output/job_list/job_kill schemas + completion notices -@deepseek-ai/dsh-agent-loop THE concrete loop (gets the forwarded `agents`) -``` - -### Why a code bundle, not a shared YAML include - -A YAML include can deduplicate config but cannot own a bin or provide entry-point defaults; the ACP app package makes protocol-pure stdout wiring the default, though a leaf can still add an unsafe logger. Bundle children register services in the root isolate-keyed store, so injected leaf siblings see them without load-order coupling. - -### Invariant companions - -The bundle mounts the invariant registry and its four package companions (`session`, `agent`, `scope`, `agent-loop`). `invariants.enabled: false` or package filters suppress the checks but do not remove the service or companion registrations; Session's always-on validation and freezing are separate. The package's own companion ([`src/invariant.ts`](src/invariant.ts)) installs no runtime invariant because this composition package owns no independent event stream or mutable data. - -### Source map - -| File | Role | -|---|---| -| [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `pickSpineConfig()`, `apply()` mounting every child | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; composition wiring is covered by tests) | - -
- ------ - - -## Further Exploration - -Read these pages when the package-level contract is not enough. They move from the sibling demo apps to the subsystems this spine mounts and the exhaustive configuration. - -- [Examples group map](../README.md) — sibling demo bundles and how the runnable leaves consume them. -- [ACP application bundle](../../bundle/acp-app/README.md) — the `dsh --profile acp` application that composes this spine without pre-created agents. -- [SDK application bundle](../../bundle/sdk-app/README.md) — the `dsh --profile sdk` application that composes this spine for JSON-RPC clients. -- [Core subsystem](../../../docs/subsystems/core.md) — the services this spine mounts and the agent-loop contract. -- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-agent-spine-demo) — every accepted config field and its source declaration. -- [Service Definition / Service Provider / Consumer separation](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) — why the bundle owns the shared spine while leaves own the backends. - ------ - - -## Model Experience - -Indirectly, through the model-facing child plugins the bundle mounts — `dsh-system-prompt`, `dsh-tools`, `dsh-tool-skill`, `dsh-tool-bash`, `dsh-tool-jobs`, and `dsh-llm-retry`, plus `dsh-tool-goal` and the goal-round driver's prompts when `goals` is enabled; the bundle adds no model-bound wrapper content of its own. - -#### KV Cache effect - -The bundle adds no request-prefix content of its own; provider cache reuse depends on the mounted consumers' contributions, and `dsh-llm-retry` reconstructs a retried request with its prior prefix intact. - -## Known Limitations and Deferred Work - - - - -These limits define when the bundle is a poor fit or needs special operational care. They are current package constraints, not a comparison with other composition approaches or a task backlog. - -- **Most of the spine set is fixed in code** — `apply()` always mounts the core services; config can omit bundled goals, skills, bash, and task-control tools, but swapping the loop or dropping another spine member means composing a different bundle. -- **The invariant service and companions remain fixed members** — `invariants.enabled: false` or package filters suppress checks but do not remove the service or companion registrations; Session's always-on validation and freezing are separate. -- **Retry can duplicate provider billing** — each provider attempt may incur billing, and `always` mode has no attempt limit; usage accounting stays with the entry point. - - -### Dev Note - -
-Working context for maintainers — click to expand - -This Dev Note is working context for maintainers; it is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above and the package code. - -The fallback title limits (5 words, 40 fallback bytes, 80 accepted-title bytes) are an overridable example policy owned by this bundle rather than by `dsh-session-title`; an entry point that needs different bounds passes its own `sessionTitle` config. The `workspaceContext` field is required (not defaulted) because it changes model-visible input; keep that requirement if the field is ever re-shaped. - -
diff --git a/packages/examples/agent-spine-demo/README.zh.md b/packages/examples/agent-spine-demo/README.zh.md deleted file mode 100644 index df84c48e82..0000000000 --- a/packages/examples/agent-spine-demo/README.zh.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -description: "默认的无执行器、无 UI agent(智能体)主干,以一个 Cordis 组合包插件交付:应用包只需添加入口与后端,即可组合出固定服务集、具体循环与面向模型的消费方。" -kind: "package-reference" ---- - -# @deepseek-ai/dsh-agent-spine-demo - -[English](README.md) | 中文 - -## 概述 - -`dsh-agent-spine-demo` 用一个插件给你一个可工作的 agent(智能体):挂载它、添加 LLM(大语言模型)适配器与执行器,即可运行完整的 agent 对话——带自动标题的内存会话、包含你的 persona 与工作区指令的系统提示词、bash、skill 与后台任务工具,以及带自动重试的运行轮次循环。你可以用用户术语配置它:persona、工具顺序、工作区上下文预算、预创建哪些 agent、可选持久目标与后台任务上限。它不附带 UI、执行器或持久化后端,且不添加任何自己的提示词或工具 schema——模型只看到你的配置产生的内容。当你构建无头、ACP(Agent Client Protocol)或 JSON-RPC agent、希望复用通用 agent 机制而不必自己搭建时使用它。阅读本包可了解开箱即得的内容,以及需要你自行提供的内容。 - -## 目录 - -- [使用本包](#use-this-package) -- [理解实现](#understand-the-implementation) -- [进一步探索](#further-exploration) -- [模型体验](#model-experience) -- [已知限制与延期工作](#known-limitations-and-deferred-work) -- [开发备注](#dev-note) - ------ - - -## 使用本包 - -当你在构建不带 UI 的 agent、希望会话、提示词、工具与轮次循环这些通用机制由本包代劳时使用此组合包。常用路径是显式的——用工作区上下文预算挂载组合包,把 LLM 适配器与 bash 执行器作为同级插件添加,并在入口预创建 agent 时提供 `agents`。配置完成后,你的 agent 就能接受提示词并返回完成的答案,当前进程会把该会话保留在内存中。 - -### 组合包包含的内容 - -开箱即得:带自动后备标题的内存会话;由你的 persona、Harness 身份与可选工作区指令组装的系统提示词;bash、本地 skill 与后台任务等面向模型的工具;agent 可以创建与跟踪的可选持久目标;以及带提供方路由重试、运行轮次的循环。会话需要在进程退出后继续存在时,请添加会话持久化提供方。完整插件清单见下方实现章节。 - -### 留在组合包外的内容 - -以下部分需要你自行提供;组合包把它们留在外部,让每个入口都能自行选择: - -- **LLM 适配器**——你需要添加调用模型的提供方;可选择 `llm-deepseek`、`llm-pi-ai` 或 `llm-replay`。 -- **基于模型的会话标题提供方**——开箱即用确定性后备标题;若想要更智能的标题,可以恰好添加一个基于模型的标题提供方。 -- **bash 执行器**——组合包提供 bash 工具;你需要添加实际运行命令的执行器(`bash-local` 或沙箱化实现)。 -- **非本地 skill 提供方**——本地 skill 开箱即用;需要嵌入式或远程目录时,可以添加相应提供方。 -- **入口与各应用基础设施**——无头、ACP 与 JSON-RPC 应用负责传输与输出选择;请选择适合你部署的那一个。 - -### 最小配置 - -最小可用配置是:用工作区上下文预算挂载组合包,再加上 LLM 适配器与执行器: - -```yaml -- name: '@deepseek-ai/dsh-agent-spine-demo' - config: - workspaceContext: - maxBytes: 4096 -- name: '@deepseek-ai/dsh-llm-deepseek' # concrete adapter for ctx.llm -- name: '@deepseek-ai/dsh-bash-local' # executor for ctx.shell -``` - -`workspaceContext` 是唯一必填字段:给它一个字节预算,工作区文件就会加载进 agent 上下文;设 `false` 则得到隔离提示词。`agents` 默认为空,因此请传入你想运行的 agent——或者当你的入口按需创建 agent 时省略它(ACP 应用就是这样)。当 agent 回答第一个提示词且会话被保存时,说明配置成功。 - -| 字段 | 默认值 | 配置内容 | -|---|---|---| -| `agents` | `[]` | 你的入口预创建的 agent;按需创建时省略 | -| `maxParallelToolCalls` | agent loop 默认值 | 同时运行的工具调用数;`1` 表示串行 | -| `includeHarnessIdentity` | `true` | 系统提示词是否点名 DeepSeek Harness 身份 | -| `includeRuntimeContext` | `true` | agent 历史是否包含动态运行时上下文快照 | -| `persona` | `''` | 系统提示词中的部署 persona 文本 | -| `toolOrder` | 字典序 | 模型看到工具的顺序 | -| `tools` | `{ mode: 'native' }` | 工具如何到达模型:native schema、PTC mode 或两者 | -| `dshHome` | `$DSH_HOME` 或 `~/.dsh` | bash 环境与本地 skill 目录使用的 harness 主目录 | -| `sessionTitle` | 示例限制 | 后备标题限制:5 个词、40 个后备字节、80 个可接受字节 | -| `workspaceContext` | 必填 | 加载工作区文件进上下文的字节预算,或 `false` | -| `skills` | 启用 | 是否加载本地 skill 并提供 skill 工具 | -| `toolBash` | 挂载 | bash 工具;当其他插件拥有 `bash` 时设 `false` | -| `jobs` | 拥有者默认值 | 每个 owner 可同时运行的后台任务数 | -| `toolJobs` | 挂载 | 后台任务控制工具;`false` 保留任务但不提供工具 | -| `invariants` | 拥有者默认值 | 开发者设置:运行哪些包检查、过滤哪些包 | -| `goals` | 不挂载 | agent 可创建并跨轮次跟踪的可选持久目标 | - -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-agent-spine-demo)是每个受支持字段及其源声明的穷尽式真源。 - -### 请求重试与计费 - -提供方请求会自动重试:调用失败时,agent 会在新的编号步骤中重试。重试可能产生费用——每次尝试都可能计费,`always` 模式没有尝试次数上限——因此用量核算留在你的入口。重试本身不会弄乱对话;你看到的是成功的结果。 - ------ - - -## 理解实现 - -
-实现细节——点击展开 - -本节解释组合包如何组装主干,并指出实现它的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。 - -### 组合模型 - -`apply(ctx, config)` 把每个子插件挂载到组合包 fiber 下,并把每个配置字段转发给拥有它的子插件。Cordis 会按 `inject` 声明挂起每个 fiber,直到其所需服务存在为止,因此加载顺序不影响正确性;下方的清单只是按依赖分层排列,便于阅读。`pickSpineConfig()` 只从应用配置中复制组合包拥有的字段,因此入口设置绝不会泄漏进主干;`dshHome` 值冲突会在组合时失败,因为本地 skill 与受管 bash 环境必须共享同一个 harness 主目录。重试策略让重试状态、提供方错误与失败的部分分片不进入模型历史,并重建保留先前前缀的重试请求,使提供方缓存保持可复用。 - -### 它加载的插件树 - -```text -@deepseek-ai/cordis-plugin-timer timer service (writes nothing to stdout) -@deepseek-ai/dsh-llm abstract LLM service + content-block vocabulary -@deepseek-ai/dsh-session event-sourced session log + store -@deepseek-ai/dsh-session-title log-backed title service + deterministic fallback -@deepseek-ai/dsh-system-prompt prompt-section + tool-schema assembly -@deepseek-ai/dsh-tools registry + guarded pre/around/post/final-result pipeline -@deepseek-ai/dsh-skill skill provider registry -@deepseek-ai/dsh-skill-filesystem local filesystem skill provider -@deepseek-ai/dsh-agent agent registry + initiator scope + agent/* events -@deepseek-ai/dsh-goal optional persisted same-session goal domain -@deepseek-ai/dsh-tool-goal optional model-facing goal controls -@deepseek-ai/dsh-goal-round-driver optional same-session goal-round driver -@deepseek-ai/dsh-llm-retry provider-routed request retry policy -@deepseek-ai/dsh-jobs-local generic background-job registry -@deepseek-ai/dsh-invariants configurable invariant registry service -@deepseek-ai/dsh-session/invariant -@deepseek-ai/dsh-agent/invariant -@deepseek-ai/dsh-scope/invariant -@deepseek-ai/dsh-agent-loop/invariant package-owned relational checks -@deepseek-ai/dsh-shell-env managed DSH_* shell environment for model shell calls (unless toolBash=false) -@deepseek-ai/dsh-tool-bash the model-facing bash schema (unless toolBash=false) -@deepseek-ai/dsh-agent-instructions AGENTS.md/CLAUDE.md workspace context loader -@deepseek-ai/dsh-tool-skill session-prefix skill catalog + model-facing loader schema -@deepseek-ai/dsh-tool-jobs job_output/job_list/job_kill schemas + completion notices -@deepseek-ai/dsh-agent-loop THE concrete loop (gets the forwarded `agents`) -``` - -### 为何用代码组合包,而非共享 YAML include - -YAML include 可以去重配置,却无法拥有 bin 或提供入口默认值;ACP 应用包默认接出协议纯净的 stdout 接线,但叶节点仍可添加不安全的 logger。组合包子节点把服务注册到根 isolate-keyed store,因此叶节点的同级插件无需依赖加载顺序即可通过注入看到它们。 - -### 不变式配套插件 - -组合包挂载不变式注册表及其四个包配套插件(`session`、`agent`、`scope`、`agent-loop`)。`invariants.enabled: false` 或包筛选器会抑制检查,但不会移除服务或配套插件注册;Session 始终启用的校验与冻结是另一套机制。本包自己的配套插件([`src/invariant.ts`](src/invariant.ts))不安装任何运行时不变式,因为这个组合包不拥有独立事件流或可变数据。 - -### 源码地图 - -| 文件 | 职责 | -|---|---| -| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`pickSpineConfig()`、挂载所有子插件的 `apply()` | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套插件(无运行时不变式;组合接线由测试覆盖) | - -
- ------ - - -## 进一步探索 - -当包级约定不够用时阅读以下页面。它们从同级演示应用逐步进入本主干挂载的子系统与穷尽式配置。 - -- [examples 组映射](../README.zh.md)——同级演示组合包,以及可运行叶节点如何消费它们。 -- [ACP 应用组合包](../../bundle/acp-app/README.zh.md)——不预创建 agent、组合本主干的 `dsh --profile acp` 应用。 -- [SDK 应用组合包](../../bundle/sdk-app/README.zh.md)——为 JSON-RPC 客户端组合本主干的 `dsh --profile sdk` 应用。 -- [核心子系统](../../../docs/subsystems/core.zh.md)——本主干挂载的服务与 agent loop 约定。 -- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-agent-spine-demo)——每个受支持配置字段及其源声明。 -- [Service Definition/Service Provider/Consumer 职责分离](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)——为何组合包拥有共享主干,而叶节点拥有后端。 - ------ - - -## 模型体验 - -模型体验由组合包挂载的面向模型子插件间接提供——`dsh-system-prompt`、`dsh-tools`、`dsh-tool-skill`、`dsh-tool-bash`、`dsh-tool-jobs` 与 `dsh-llm-retry`,启用 `goals` 时还包括 `dsh-tool-goal` 与目标轮次驱动器的提示词;组合包自身不添加任何面向模型的包装内容。 - -#### KV Cache 影响 - -组合包自身不添加请求前缀内容;提供方缓存复用取决于所挂载消费方的贡献,`dsh-llm-retry` 重建重试请求时会保留其先前前缀。 - -## 已知限制与延期工作 - - - - -这些限制说明组合包何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是与其他组合方式的对比,也不是任务积压。 - -- **大部分主干集合固定在代码中**:`apply()` 始终挂载核心服务;配置可以省略组合包内的目标、skill、bash 与任务控制工具,但要替换循环或删除其他主干成员,就必须组合另一个组合包。 -- **不变式服务与配套插件仍是固定成员**:`invariants.enabled: false` 或包筛选器会抑制检查,但不会移除服务或配套插件注册;Session 始终启用的校验与冻结是另一套机制。 -- **重试可能重复产生提供方计费**:每次提供方尝试都可能产生计费,`always` 模式没有尝试次数上限;用量核算由入口负责。 - - -### 开发备注 - -
-维护者的工作上下文——点击展开 - -本开发备注是维护者的工作上下文,明确不具权威性——已交付的行为、限制与既定理由以上文和包代码为准。 - -后备标题限制(5 个词、40 个后备字节、80 个可接受标题字节)是本组合包拥有、可覆盖的示例策略,而非 `dsh-session-title` 所有;需要不同上限的入口应传入自己的 `sessionTitle` 配置。`workspaceContext` 字段为必填(不设默认值),因为它改变模型可见输入;若该字段日后改型,请保留这一要求。 - -
diff --git a/packages/examples/agent-spine-demo/package.json b/packages/examples/agent-spine-demo/package.json deleted file mode 100644 index 5738da4b1e..0000000000 --- a/packages/examples/agent-spine-demo/package.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "name": "@deepseek-ai/dsh-agent-spine-demo", - "description": "The default executor-less/UI-less agent spine with fallback session titles, provider-routed retry, and optional persisted goals", - "version": "0.1.2-alpha.2", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/examples/agent-spine-demo" - }, - "type": "module", - "main": "lib/index.js", - "types": "lib/types/index.d.ts", - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "default": "./lib/index.js" - }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, - "./src/*": "./src/*", - "./package.json": "./package.json" - }, - "files": [ - "lib/index.js", - "lib/invariant.js", - "lib/types/**/*.d.ts" - ], - "license": "MIT", - "peerDependencies": { - "@deepseek-ai/cordis-plugin-timer": "workspace:^", - "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-loop": "workspace:^", - "@deepseek-ai/dsh-goal": "workspace:^", - "@deepseek-ai/dsh-goal-round-driver": "workspace:^", - "@deepseek-ai/dsh-invariants": "workspace:^", - "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-home-paths": "workspace:^", - "@deepseek-ai/dsh-llm-retry": "workspace:^", - "@deepseek-ai/dsh-scope": "workspace:^", - "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/dsh-session-title": "workspace:^", - "@deepseek-ai/dsh-skill": "workspace:^", - "@deepseek-ai/dsh-skill-filesystem": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-jobs-local": "workspace:^", - "@deepseek-ai/dsh-shell-env": "workspace:^", - "@deepseek-ai/dsh-tool-bash": "workspace:^", - "@deepseek-ai/dsh-tool-goal": "workspace:^", - "@deepseek-ai/dsh-tool-skill": "workspace:^", - "@deepseek-ai/dsh-tool-jobs": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-agent-instructions": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" - }, - "devDependencies": { - "@deepseek-ai/cordis-plugin-timer": "workspace:^", - "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-loop": "workspace:^", - "@deepseek-ai/dsh-shell-env": "workspace:^", - "@deepseek-ai/dsh-bash-local": "workspace:^", - "@deepseek-ai/dsh-bash-sandbox": "workspace:^", - "@deepseek-ai/dsh-fs-local": "workspace:^", - "@deepseek-ai/dsh-fs-observation-policy": "workspace:^", - "@deepseek-ai/dsh-fs-sandbox": "workspace:^", - "@deepseek-ai/dsh-goal": "workspace:^", - "@deepseek-ai/dsh-goal-round-driver": "workspace:^", - "@deepseek-ai/dsh-invariants": "workspace:^", - "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-home-paths": "workspace:^", - "@deepseek-ai/dsh-llm-retry": "workspace:^", - "@deepseek-ai/dsh-subprocess-local": "workspace:^", - "@deepseek-ai/dsh-sandbox-local": "workspace:^", - "@deepseek-ai/dsh-sandbox-policy": "workspace:^", - "@deepseek-ai/dsh-scope": "workspace:^", - "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/dsh-session-title": "workspace:^", - "@deepseek-ai/dsh-skill": "workspace:^", - "@deepseek-ai/dsh-skill-filesystem": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-jobs": "workspace:^", - "@deepseek-ai/dsh-jobs-local": "workspace:^", - "@deepseek-ai/dsh-tool-bash": "workspace:^", - "@deepseek-ai/dsh-tool-fs": "workspace:^", - "@deepseek-ai/dsh-tool-goal": "workspace:^", - "@deepseek-ai/dsh-tool-skill": "workspace:^", - "@deepseek-ai/dsh-tool-jobs": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-agent-instructions": "workspace:^", - "@deepseek-ai/node-addon-landlock-run": "workspace:^", - "@deepseek-ai/cordis": "workspace:^", - "@deepseek-ai/dsh-session-projection": "workspace:^" - }, - "dependencies": { - "@deepseek-ai/schemastery": "workspace:^" - } -} diff --git a/packages/examples/agent-spine-demo/src/index.ts b/packages/examples/agent-spine-demo/src/index.ts deleted file mode 100644 index f74bc9407c..0000000000 --- a/packages/examples/agent-spine-demo/src/index.ts +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Default executor-less, UI-less agent spine. It bundles the common services, - * background-job registry and controls, optional persisted goals, concrete loop, local skill and - * agent-instructions providers, and model-facing shell/skill consumers; - * deployments still choose the LLM adapter, bash executor, and presentation. - * The plugin intentionally exposes named exports only because Loader default - * unwrapping would discard its `Config` schema (see docs/postmortem/0001). - * @module @deepseek-ai/dsh-agent-spine-demo - */ - -import type { Context } from '@deepseek-ai/cordis' -import Timer from '@deepseek-ai/cordis-plugin-timer' -import z from '@deepseek-ai/schemastery' -import LlmRuntime from '@deepseek-ai/dsh-llm' -import SessionStore from '@deepseek-ai/dsh-session' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import SessionTitleService, { type Config as SessionTitleConfig } from '@deepseek-ai/dsh-session-title' -import SystemPrompt, { type Config as SystemPromptConfig } from '@deepseek-ai/dsh-system-prompt' -import ToolRuntime, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools' -import SkillRegistry, { type Config as SkillRegistryConfig } from '@deepseek-ai/dsh-skill' -import * as SkillFileSystem from '@deepseek-ai/dsh-skill-filesystem' -import AgentRegistry from '@deepseek-ai/dsh-agent' -import GoalService, { type Config as GoalDomainConfig } from '@deepseek-ai/dsh-goal' -import * as goalSession from '@deepseek-ai/dsh-goal-round-driver' -import * as toolGoal from '@deepseek-ai/dsh-tool-goal' -import LocalJobRegistry, { type Config as JobsConfig } from '@deepseek-ai/dsh-jobs-local' -import InvariantRegistry, { type Config as InvariantConfig } from '@deepseek-ai/dsh-invariants' -import * as sessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as agentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as scopeInvariant from '@deepseek-ai/dsh-scope/invariant' -import * as agentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' -import * as toolBash from '@deepseek-ai/dsh-tool-bash' -import * as bashEnv from '@deepseek-ai/dsh-shell-env' -import * as workspaceContext from '@deepseek-ai/dsh-agent-instructions' -import * as toolSkill from '@deepseek-ai/dsh-tool-skill' -import * as toolJobs from '@deepseek-ai/dsh-tool-jobs' -import AgentLoop, { type Config as AgentLoopConfig } from '@deepseek-ai/dsh-agent-loop' -import * as llmRetry from '@deepseek-ai/dsh-llm-retry' -import { resolveDshHome } from '@deepseek-ai/dsh-home-paths' - -export const name = 'agent-spine-demo' - -/** Overridable example policy used when a bundle consumer omits `sessionTitle`. */ -const EXAMPLE_SESSION_TITLE_CONFIG: SessionTitleConfig = { - fallbackMaxWords: 5, - fallbackMaxBytes: 40, - maxTitleBytes: 80, -} - -/** Skill bundle config forwarded to the registry, local provider, and model-facing consumer. */ -export interface SkillConfig { - /** Mount the bundled local skill provider and model-facing skill tool (default true). */ - enabled?: boolean - /** Registry-level discovery cache settings. */ - registry?: SkillRegistryConfig - /** Local filesystem skill provider settings. */ - filesystem?: SkillFileSystem.Config - /** Model-facing skill catalog and tool settings. */ - tool?: toolSkill.Config -} - -/** Persisted goal domain, model-tool policy, and same-session driver config. */ -export interface GoalConfig { - /** Goal-domain creation defaults. */ - domain?: GoalDomainConfig - /** Model-facing goal-tool authority policy. */ - tool?: toolGoal.Config -} - -/** - * Bundle config: each field forwarded verbatim to the child that owns it — - * `agents` to the agent loop (an app that pre-creates no agents, like the ACP - * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`, - * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener, - * dynamic-context policy, deployment persona, and explicit model-facing tool - * order), the `tools` object to the tool - * registry (its presentation `mode`), - * `dshHome` to bash environment and local skill discovery, `sessionTitle` to - * the fallback title service, `skills` to the - * skill registry/local provider/tool consumer, `workspaceContext` to the - * agent-instructions loader, `jobs` to the process-local job provider, and - * `toolBash`/`toolJobs` to the model-facing tool plugins this bundle owns. - * Provider adapters own their `retryPolicy`; this bundle always mounts its - * executor. - * `goals` opts into and configures the persisted goal domain plus its model tool - * and same-session driver; `invariants` configures global and package-filtered - * relational checks. Owner schemas supply defaults for optional input; - * workspace context instead requires an explicit byte budget or `false` because - * it changes model-visible input. Producer opt-in stays producer-local: - * `toolBash` configures bash only; independently composed producers keep their - * own config. Set `toolBash: false` when another plugin owns the model-facing - * `bash` name. - */ -export interface Config { - /** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */ - agents?: AgentLoopConfig['agents'] - /** Agent-loop concurrency cap; `1` is serial. */ - maxParallelToolCalls?: AgentLoopConfig['maxParallelToolCalls'] - /** Whether the system prompt includes the fixed Harness identity (default true). */ - includeHarnessIdentity?: SystemPromptConfig['includeHarnessIdentity'] - /** Whether model history includes dynamic runtime-context snapshots (default true). */ - includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext'] - /** The deployment persona (see dsh-system-prompt's `Config`). */ - persona?: SystemPromptConfig['persona'] - /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */ - toolOrder?: SystemPromptConfig['toolOrder'] - /** The tool registry's config — its presentation `mode` (see dsh-tools' `Config`). */ - tools?: ToolsConfig - /** DeepSeek Harness home directory shared by shell context and local skill discovery. */ - dshHome?: string - /** Deterministic fallback and accepted-title limits; omission uses the bundle's example policy. */ - sessionTitle?: SessionTitleConfig - /** Workspace-context loader controls with an explicit byte budget; set `false` for hermetic prompts. */ - workspaceContext: workspaceContext.Config | false - /** - * Skill registry, local provider, and model-facing consumer config. - * Skills use `enabled` because one nested config controls a provider stack; - * single model-tool plugins use `Config | false` to disable that one consumer. - */ - skills?: SkillConfig - /** Model-facing bash tool config, or false when another plugin owns `bash`. */ - toolBash?: toolBash.Config | false - /** Process-local background-job admission config. */ - jobs?: JobsConfig - /** Generic background-job controls; set false to keep the job service without model-facing job tools. */ - toolJobs?: toolJobs.Config | false - /** Global enablement and package-name filters for invariant companions. */ - invariants?: InvariantConfig - /** Opt-in persisted same-session goal stack; set false or omit to leave it unmounted. */ - goals?: GoalConfig | false -} - -/** The skill config schema exported for app packages that forward `skills`. */ -export const SkillConfigSchema: z = z.object({ - enabled: z.boolean().default(true), - registry: SkillRegistry.Config, - filesystem: SkillFileSystem.Config, - tool: toolSkill.Config, -}) - -/** The session-title config schema with the shared bundle's overridable example limits. */ -export const SessionTitleConfigSchema: z = SessionTitleService.Config - .default(EXAMPLE_SESSION_TITLE_CONFIG) - -/** The bash-tool config schema exported for app packages that forward `toolBash`. */ -export const ToolBashConfigSchema: z = - z.union([z.const(false), toolBash.Config]) - -/** The process-local job registry schema exported for app packages that forward `jobs`. */ -export const JobsConfigSchema: z = LocalJobRegistry.Config - -/** The job-control-tool config schema exported for app packages that forward `toolJobs`. */ -export const ToolJobsConfigSchema: z = toolJobs.Config - -/** The persisted-goal config schema exported for app packages that opt in. */ -export const GoalConfigSchema: z = z.object({ - domain: GoalService.Config, - tool: toolGoal.Config, -}) - -/** Intersect the owners' schemas so validation + defaulting stay identical. */ -export const Config = z.intersect([ - AgentLoop.Config, - SystemPrompt.Config, - z.object({ - tools: ToolRuntime.Config, - dshHome: z.string(), - sessionTitle: SessionTitleConfigSchema, - skills: SkillConfigSchema, - workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(), - toolBash: ToolBashConfigSchema, - jobs: JobsConfigSchema, - toolJobs: z.union([z.const(false), ToolJobsConfigSchema]), - invariants: InvariantRegistry.Config, - goals: z.union([z.const(false), GoalConfigSchema]), - }) as unknown as z>, -]) as unknown as z - -/** - * Copy the bundle-owned fields from an app config without leaking entry-point settings. - * @param config - App config containing the shared spine fields. - * @returns The fields accepted by this bundle, preserving optional absence. - */ -export function pickSpineConfig(config: Omit): Omit { - return { - ...config.maxParallelToolCalls !== undefined ? { maxParallelToolCalls: config.maxParallelToolCalls } : {}, - ...config.includeHarnessIdentity !== undefined ? { includeHarnessIdentity: config.includeHarnessIdentity } : {}, - ...config.includeRuntimeContext !== undefined ? { includeRuntimeContext: config.includeRuntimeContext } : {}, - ...config.persona !== undefined ? { persona: config.persona } : {}, - ...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {}, - ...config.tools !== undefined ? { tools: config.tools } : {}, - ...config.dshHome !== undefined ? { dshHome: config.dshHome } : {}, - ...config.sessionTitle !== undefined ? { sessionTitle: config.sessionTitle } : {}, - workspaceContext: config.workspaceContext, - ...config.skills !== undefined ? { skills: config.skills } : {}, - ...config.toolBash !== undefined ? { toolBash: config.toolBash } : {}, - ...config.jobs !== undefined ? { jobs: config.jobs } : {}, - ...config.toolJobs !== undefined ? { toolJobs: config.toolJobs } : {}, - ...config.invariants !== undefined ? { invariants: config.invariants } : {}, - ...config.goals !== undefined ? { goals: config.goals } : {}, - } -} - -/** - * Load the spine. Each `ctx.plugin(...)` mounts one child of the bundle fiber; - * `agent-loop` receives the forwarded `agents` list and `system-prompt` the - * forwarded persona fields and `toolOrder`. Workspace-context receives its own - * explicitly forwarded config. Load order is irrelevant (cordis - * pends each fiber on its `inject` until the services it needs exist), but the - * listing mirrors the dependency layering for readability: the LLM vocabulary - * and core registries first, then extension plugins that wrap request/tool - * seams, then the loop that drives them. - */ -export function apply(ctx: Context, config: Config): void { - const nestedDshHome = config.skills?.filesystem?.dshHome - if (config.dshHome !== undefined && nestedDshHome !== undefined - && resolveDshHome(config.dshHome) !== resolveDshHome(nestedDshHome)) { - throw new Error('agent-spine-demo: dshHome and skills.filesystem.dshHome must resolve to the same directory') - } - const dshHome = resolveDshHome(config.dshHome ?? nestedDshHome) - - ctx.plugin(Timer) - ctx.plugin(LlmRuntime) - ctx.plugin(SessionStore) - ctx.plugin(SessionProjectionRegistry) - ctx.plugin(SessionTitleService, config.sessionTitle ?? EXAMPLE_SESSION_TITLE_CONFIG) - // Owner schemas resolve defaults; forward toolOrder only when explicitly set. - ctx.plugin(SystemPrompt, { - includeHarnessIdentity: config.includeHarnessIdentity ?? true, - includeRuntimeContext: config.includeRuntimeContext ?? true, - persona: config.persona ?? '', - ...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {}, - }) - ctx.plugin(ToolRuntime, config.tools ?? {}) - const skillsEnabled = config.skills?.enabled ?? true - if (skillsEnabled) { - ctx.plugin(SkillRegistry, config.skills?.registry ?? {}) - ctx.plugin(SkillFileSystem, Object.assign({}, config.skills?.filesystem, { dshHome })) - } - ctx.plugin(AgentRegistry) - ctx.plugin(llmRetry) - if (config.goals !== undefined && config.goals !== false) { - ctx.plugin(GoalService, config.goals.domain ?? {}) - ctx.plugin(toolGoal, config.goals.tool ?? {}) - ctx.plugin(goalSession) - } - ctx.plugin(LocalJobRegistry, config.jobs ?? {}) - ctx.plugin(InvariantRegistry, config.invariants ?? {}) - ctx.plugin(sessionInvariant) - ctx.plugin(agentInvariant) - ctx.plugin(scopeInvariant) - ctx.plugin(agentLoopInvariant) - if (config.toolBash !== false) { - ctx.plugin(bashEnv, { dshHome }) - ctx.plugin(toolBash, config.toolBash ?? {}) - } - if (config.workspaceContext !== false) { - ctx.plugin(workspaceContext, config.workspaceContext) - } - // Both plugins prepend session-prefix messages. Registration order is the - // rendered order, so workspace instructions must precede the skill catalog. - if (skillsEnabled) ctx.plugin(toolSkill, config.skills?.tool ?? {}) - if (config.toolJobs !== false) ctx.plugin(toolJobs, config.toolJobs ?? {}) - ctx.plugin(AgentLoop, { - agents: config.agents ?? [], - ...config.maxParallelToolCalls !== undefined ? { maxParallelToolCalls: config.maxParallelToolCalls } : {}, - }) -} diff --git a/packages/examples/agent-spine-demo/src/invariant.ts b/packages/examples/agent-spine-demo/src/invariant.ts deleted file mode 100644 index 9735d8e74d..0000000000 --- a/packages/examples/agent-spine-demo/src/invariant.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-agent-spine-demo`. - * @module @deepseek-ai/dsh-agent-spine-demo/invariant - */ - -/* jscpd:ignore-start */ -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-agent-spine-demo' - -/** Cordis companion plugin name. */ -export const name = 'agent-spine-demo-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * No runtime invariant: this composition package owns no independent event stream or mutable data; - * Loader and built-entry tests cover its wiring. - */ -const install: InvariantInstaller = () => {} - -/** - * Register this package's invariant companion. - * @param ctx - Cordis context carrying the invariant service. - * @returns the installed registration's disposer after setup succeeds. - */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) -/* jscpd:ignore-end */ diff --git a/packages/examples/agent-spine-demo/tests/agent-core.spec.ts b/packages/examples/agent-spine-demo/tests/agent-core.spec.ts deleted file mode 100644 index 94d16b50fe..0000000000 --- a/packages/examples/agent-spine-demo/tests/agent-core.spec.ts +++ /dev/null @@ -1,849 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' -import { join, sep } from 'node:path' -import { tmpdir } from 'node:os' -import { Context } from '@deepseek-ai/cordis' -import Loader from '@deepseek-ai/cordis-plugin-loader' -import { renderPrompt, TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt' -import * as agentCore from '../src/index.ts' -import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' -import { SessionId } from '@deepseek-ai/dsh-session' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import LocalBashExecutor from '@deepseek-ai/dsh-bash-local' -import LocalFileSystem from '@deepseek-ai/dsh-fs-local' -import * as ToolFs from '@deepseek-ai/dsh-tool-fs' -import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' -import { - createUserMessage, - ToolCallId, - LlmAdapter, - LlmError, - resolveRetryPolicy, - type GenerateOptions, - type Message, - type ResolvedRetryPolicy, - type StreamChunk, -} from '@deepseek-ai/dsh-llm' -import type { ToolExecution } from '@deepseek-ai/dsh-tools' -import * as sessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as agentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as scopeInvariant from '@deepseek-ai/dsh-scope/invariant' -import * as agentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' - -const testToolSignal = new AbortController().signal - -declare module '@deepseek-ai/dsh-jobs' { - interface JobKindMap { - probe: 'probe' - } -} - -async function composePrefix(ctx: Context, cwd: string): Promise { - const agent = ctx.agentLoop.create(SessionId('agent-spine-prefix'), {}, { cwd }) - const signal = new AbortController().signal - const decision = await agentEvents(ctx, agent).waterfall( - 'agent/pre-step', { messages: [], turn: 1, step: 1, signal }, - () => Promise.resolve({ kind: 'enter', messages: [] }), - ) - if (decision.kind === 'enter') { - for (const message of decision.messages) { - agent.session.append('user/message', message, { surfaceOp: 'append' }) - } - } - return agent.session.deriveMessages() -} - -/** - * Unit coverage for the @deepseek-ai/dsh-agent-spine-demo bundle: mounting it brings - * up the whole default spine in one `ctx.plugin`, and the forwarded - * `agents` config reaches the loop (default `[]`, or a pre-created agent). - * - * The bundle is exercised through `ctx.plugin(agentCore, …)` — the NAMESPACE - * import, the same shape the Loader builds from `unwrapExports`. The real - * Loader-path guard (export shape, `unwrapExports`) is the app packages' keyless - * bin smokes; here we assert the composition + config forwarding. - */ -async function mount(config: agentCore.Config, withBash = false): Promise { - const oldDshHome = process.env.DSH_HOME - const oldAgentsHome = process.env.DSH_AGENTS_HOME - process.env.DSH_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-home-')) - process.env.DSH_AGENTS_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-agents-')) - const ctx = new Context() - if (withBash) { - ctx.provide('shell', { - sandboxMode: undefined, - resolve() { throw new Error('composition test does not execute bash') }, - run() { throw new Error('composition test does not execute bash') }, - start() { throw new Error('composition test does not execute bash') }, - }) - } - try { - // AgentLoop declares the registry as a required injection; the bundle does - // not mount it (leaves do), so the harness provides it before the spine. - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(agentCore, config) - // The bundle mounts its children inside apply() (not awaited there); let their - // fibers settle so the spine services and any pre-created agent are ready. - await new Promise(resolve => setTimeout(resolve, 50)) - return ctx - } finally { - if (oldDshHome === undefined) { - delete process.env.DSH_HOME - } else { - process.env.DSH_HOME = oldDshHome - } - if (oldAgentsHome === undefined) { - delete process.env.DSH_AGENTS_HOME - } else { - process.env.DSH_AGENTS_HOME = oldAgentsHome - } - } -} - -async function withIsolatedSkillHomes(run: () => Promise): Promise { - const oldDshHome = process.env.DSH_HOME - const oldAgentsHome = process.env.DSH_AGENTS_HOME - process.env.DSH_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-home-')) - process.env.DSH_AGENTS_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-agents-')) - try { - return await run() - } finally { - if (oldDshHome === undefined) { - delete process.env.DSH_HOME - } else { - process.env.DSH_HOME = oldDshHome - } - if (oldAgentsHome === undefined) { - delete process.env.DSH_AGENTS_HOME - } else { - process.env.DSH_AGENTS_HOME = oldAgentsHome - } - } -} - -function waitForIdle(_ctx: Context, target: Agent): Promise { - return target.whenIdle() -} - -function messageText(message: Message | undefined): string { - return message?.content.map(block => block.type === 'text' ? block.text : '').join('\n') ?? '' -} - -class TransientOnceAdapter extends LlmAdapter { - requests = 0 - private readonly retryPolicy = resolveRetryPolicy({ - mode: 'normal', - maxRetries: 1, - backoff: { initialDelayMs: 1, maxDelayMs: 1, jitterRatio: 0 }, - }, 'agent-spine test provider retryPolicy') - - override providerRetryPolicy(_provider: string): ResolvedRetryPolicy { - return this.retryPolicy - } - - async * stream(_options: GenerateOptions): AsyncIterable { - this.requests += 1 - if (this.requests === 1) throw new LlmError('temporary outage', 'SERVER') - yield* textResponse('recovered by bundled policy') - } -} - -describe('dsh-agent-spine-demo bundle', () => { - it('brings up the full default spine', async () => { - const ctx = await mount({ workspaceContext: false }) - // One service from each layer of the spine proves the children loaded. - expect(ctx.get('timer')).toBeDefined() - expect(ctx.get('llm')).toBeDefined() - expect(ctx.get('sessions')).toBeDefined() - expect(ctx.get('sessionTitle')).toBeDefined() - expect(ctx.get('systemPrompt')).toBeDefined() - expect(ctx.get('tools')).toBeDefined() - expect(ctx.get('skills')).toBeDefined() - expect(ctx.get('agents')).toBeDefined() - expect(ctx.get('jobs')).toBeDefined() - expect(ctx.get('invariants')).toBeDefined() - expect(ctx.get('agentLoop')).toBeDefined() - expect(ctx.get('goals')).toBeUndefined() - await ctx.fiber.dispose() - }) - - it('forwards configurable fallback title limits to the bundled service', async () => { - const ctx = await mount({ - workspaceContext: false, - sessionTitle: { - fallbackMaxWords: 1, - fallbackMaxBytes: 40, - maxTitleBytes: 80, - }, - }) - const session = ctx.sessions.create(SessionId('configured-title-limits')) - session.append('turn/start', { - turn: 1, - }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'One two three four' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - await new Promise(resolve => setTimeout(resolve, 0)) - - expect(ctx.sessionTitle.get(session)?.title).toBe('One') - await ctx.fiber.dispose() - }) - - it('opts into the configured persisted-goal domain, tools, and same-session driver', async () => { - const ctx = await mount({ - workspaceContext: false, - agents: [{ id: SessionId('configured-goal'), provider: 'mock', model: 'mock' }], - goals: { - domain: { defaultMaxGoalRounds: 17 }, - tool: { blockedAfterConsecutiveRounds: 5 }, - }, - }) - const agent = ctx.agents.list()[0] - if (agent === undefined) throw new Error('configured goal test has no live agent') - expect(ctx.goals.create(agent, { objective: 'configured' })).toMatchObject({ - objective: 'configured', maxGoalRounds: 17, - }) - expect(['create_goal', 'get_goal', 'update_goal'].map(name => ctx.tools.get(name)?.name)) - .toEqual(['create_goal', 'get_goal', 'update_goal']) - expect((await ctx.systemPrompt.assemble()).sections.find(section => section.name === 'tool:goal')?.text) - .toContain('at least 5 consecutive goal rounds') - await ctx.fiber.dispose() - }) - - it('accepts an explicit false goal composition without mounting it', async () => { - const ctx = await mount({ workspaceContext: false, goals: false }) - expect(ctx.get('goals')).toBeUndefined() - expect(ctx.tools.get('get_goal')).toBeUndefined() - await ctx.fiber.dispose() - }) - - it('mounts package companions and forwards invariant selection config', async () => { - const nestedTurn = (ctx: Context): void => { - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('turn/start', { turn: 2 }) - } - - const enabled = await mount({ workspaceContext: false }) - expect(() => { nestedTurn(enabled) }).toThrow(/turn 1 is still open/) - await enabled.fiber.dispose() - - for (const invariants of [ - { enabled: false }, - { package_allowlist: ['^@deepseek-ai/dsh-agent$'] }, - { package_blocklist: ['^@deepseek-ai/dsh-session$'] }, - ]) { - const filtered = await mount({ workspaceContext: false, invariants }) - expect(() => { nestedTurn(filtered) }).not.toThrow() - await filtered.fiber.dispose() - } - }) - - it('loads and configures bounded request recovery for every bundled entry point', async () => { - const adapter = new TransientOnceAdapter() - const ctx = await mount({ workspaceContext: false }) - ctx.llm.registerAdapter(['mock'], adapter) - const handle = await ctx.agents.create({ - sessionId: SessionId('bundled-retry-session'), - meta: { cwd: process.cwd() }, - agentOptions: { provider: 'mock', model: 'mock' }, - }) - - handle.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'recover' }], source: { kind: 'user' } })) - await waitForIdle(ctx, handle.agent) - - expect(adapter.requests).toBe(2) - const retryEvents = handle.agent.session.events.filter(event => event.type === 'llm/retry') - expect(retryEvents).toHaveLength(1) - expect(retryEvents[0]?.data.retry).toBe(1) - expect(retryEvents[0]?.data).toMatchObject({ provider: 'mock', mode: 'normal', maxRetries: 1 }) - expect(handle.agent.session.events.find(event => event.type === 'session/title')?.data.title).toBe('recover') - expect(messageText(handle.agent.session.deriveMessages().at(-1))).toBe('recovered by bundled policy') - await handle.dispose() - await ctx.fiber.dispose() - }) - - it('includes the skill registry, local provider, and skill tool without builtin skills', async () => { - const ctx = await mount({ workspaceContext: false }) - - expect(ctx.skills).toBeDefined() - expect(ctx.tools.schemas().map(tool => tool.name)).toContain('skill') - expect(await ctx.skills.list()).toEqual([]) - - await ctx.fiber.dispose() - }) - - it('defaults the agents list to empty (no pre-created agents)', async () => { - const ctx = await mount({ workspaceContext: false }) - expect(ctx.get('agents')?.get(SessionId('main'))).toBeUndefined() - await ctx.fiber.dispose() - }) - - it('forwards a pre-created agent to the loop and the persona to system-prompt', async () => { - const ctx = await mount({ - agents: [{ id: SessionId('main'), provider: 'mock', model: 'mock' }], - persona: 'You are main.', - workspaceContext: false, - }) - const agent = ctx.get('agents')?.list()[0] - expect(agent?.id).toBe(agent?.session.id) - expect(agent?.id).toMatch(/^main-session-/) - const assembly = await ctx.get('systemPrompt')!.assemble() - expect(assembly.sections.find(s => s.name === 'deployment:persona')?.text).toBe('You are main.') - await ctx.fiber.dispose() - }) - - it('forwards the global maxParallelToolCalls config to agent-loop', async () => { - const ctx = await mount({ - agents: [{ id: SessionId('main'), provider: 'mock', model: 'mock' }], - maxParallelToolCalls: 3, - workspaceContext: false, - }) - expect(ctx.get('agentLoop')?.config.maxParallelToolCalls).toBe(3) - await ctx.fiber.dispose() - }) - - it('forwards task admission config to the process-local provider', async () => { - const ctx = await mount({ - jobs: { maxConcurrentJobsPerOwner: 1 }, - workspaceContext: false, - }) - let settle!: (outcome: { status: 'killed' }) => void - ctx.jobs.start({ - kind: 'probe', - label: 'hold configured slot', - run: () => ({ - cancel: () => { settle({ status: 'killed' }) }, - done: new Promise((resolve) => { settle = resolve }), - }), - }) - expect(() => ctx.jobs.start({ - kind: 'probe', - label: 'blocked configured task', - run: () => ({ cancel: () => {}, done: Promise.resolve({ status: 'completed' }) }), - })).toThrow('(limit: 1)') - await ctx.fiber.dispose() - }) - - it('tolerates a schema-bypassing direct apply (the ?? fallbacks fire)', async () => { - // ctx.plugin validates + defaults the bundle config first; a direct apply - // skips the schema, so the forwarding `?? []` / `?? ''` are what fire. - const ctx = new Context() - await ctx.plugin(SessionProjectionRegistry) - agentCore.apply(ctx, { workspaceContext: false }) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(ctx.get('agentLoop')).toBeDefined() - expect(ctx.get('agents')?.list()).toHaveLength(0) - const assembly = await ctx.get('systemPrompt')!.assemble() - expect(assembly.sections.find(s => s.name === 'deployment:persona')?.text).toBe('') - await ctx.fiber.dispose() - }) - - it('uses owner defaults for a schema-bypassing empty goal opt-in', async () => { - const ctx = new Context() - await ctx.plugin(SessionProjectionRegistry) - agentCore.apply(ctx, { - workspaceContext: false, - agents: [{ id: SessionId('defaulted-goal'), provider: 'mock', model: 'mock' }], - goals: {}, - }) - await new Promise(resolve => setTimeout(resolve, 50)) - const agent = ctx.agents.list()[0] - if (agent === undefined) throw new Error('default goal test has no live agent') - expect(ctx.goals.create(agent, { objective: 'defaulted' })).toMatchObject({ - objective: 'defaulted', maxGoalRounds: 256, - }) - expect(ctx.tools.get('get_goal')).toBeDefined() - await ctx.fiber.dispose() - }) - - it('loads workspace instructions into requests through the bundled spine', async () => { - const root = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-workspace-context-')) - try { - await mkdir(join(root, '.git'), { recursive: true }) - await writeFile(join(root, 'AGENTS.md'), 'bundled project rule') - const adapter = new MockAdapter([textResponse('first')]) - const ctx = await mount({ workspaceContext: { maxBytes: 65536 } }) - await ctx.plugin(LocalFileSystem, { cwd: '/' }) - ctx.llm.registerAdapter(['mock'], adapter) - const handle = await ctx.agents.create({ - sessionId: SessionId('main-session'), - meta: { cwd: root }, - agentOptions: { provider: 'mock', model: 'mock' }, - }) - const agent = handle.agent - - agent.followup(createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } })) - await waitForIdle(ctx, agent) - - expect(adapter.requests).toHaveLength(1) - const firstRequestText = adapter.requests[0]?.messages.map(messageText).join('\n') - expect(firstRequestText).toContain('hi') - expect(firstRequestText).toContain('bundled project rule') - expect(adapter.requests[0]?.system).toContain('You are an AI agent powered by DeepSeek Harness.') - expect(adapter.requests[0]?.system).not.toContain('bundled project rule') - await handle.dispose() - await ctx.fiber.dispose() - } finally { - await rm(root, { recursive: true, force: true }) - } - }) - - it('forwards agent-instructions config to the bundled loader', async () => { - const root = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-workspace-context-disabled-')) - try { - await mkdir(join(root, '.git'), { recursive: true }) - await writeFile(join(root, 'AGENTS.md'), 'must not be injected') - const adapter = new MockAdapter([textResponse('ok')]) - const ctx = await mount({ workspaceContext: { maxBytes: 0 } }) - ctx.llm.registerAdapter(['mock'], adapter) - const handle = await ctx.agents.create({ - sessionId: SessionId('main-disabled-session'), - meta: { cwd: root }, - agentOptions: { provider: 'mock', model: 'mock' }, - }) - - handle.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } })) - await waitForIdle(ctx, handle.agent) - - expect(adapter.requests[0]?.messages).toEqual([{ - id: expect.any(String) as unknown, - role: 'user', - content: [{ type: 'text', text: 'hi' }], - source: { kind: 'user' }, - }]) - await handle.dispose() - await ctx.fiber.dispose() - } finally { - await rm(root, { recursive: true, force: true }) - } - }) - - it('forwards skill config to the registry, local provider, and model-facing consumer', async () => { - const home = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-skill-home-')) - const agentsHome = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-skill-agents-')) - const custom = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-skill-custom-')) - await mkdir(custom, { recursive: true }) - await writeFile(join(custom, 'custom-skill.md'), '---\nname: custom-skill\ndescription: Custom skill\n---\n\nCustom body.\n') - const ctx = await mount({ - agents: [], - workspaceContext: false, - skills: { - registry: { collectCacheMaxEntries: 4 }, - filesystem: { - dshHome: join(home, '.dsh'), - agentsHome: join(agentsHome, '.agents'), - customSkillDirs: [custom], - }, - tool: { catalogDescriptionMaxLength: 6 }, - }, - }) - expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(['custom-skill']) - expect(JSON.stringify(await composePrefix(ctx, '/tmp'))).toContain('- `custom-skill`: Cus...') - await ctx.fiber.dispose() - }) - - it('snapshots a created project skill through catalog refresh and progressive loading', { timeout: 15_000 }, async () => { - const root = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-skill-refresh-')) - const home = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-skill-refresh-home-')) - try { - await mkdir(join(root, '.git'), { recursive: true }) - const skillPath = '.agents/skills/hot-skill/SKILL.md' - const skillSource = '---\nname: hot-skill\ndescription: Hot-added skill\n---\n\nUse the freshly loaded body.\n' - const adapter = new MockAdapter([ - toolCallResponse('mkdir-skill', 'bash', { - command: 'mkdir -p .agents/skills/hot-skill', - description: 'Create the project skill directory', - }), - toolCallResponse('write-skill', 'write', { - file_path: skillPath, - content: skillSource, - }), - toolCallResponse('load-skill', 'skill', { name: 'hot-skill' }), - textResponse('SKILL_REFRESH_OK'), - ]) - const ctx = await mount({ - workspaceContext: false, - skills: { - filesystem: { - dshHome: join(home, '.dsh'), - agentsHome: join(home, '.agents'), - watchStabilityThresholdMs: 20, - watchPollIntervalMs: 10, - }, - }, - }) - await ctx.plugin(LocalBashExecutor, {}) - await ctx.plugin(LocalFileSystem, { cwd: root }) - await ctx.plugin(ToolFs) - ctx.on('tools/post-execute', async (exec, _result, next) => { - const decision = await next() - if (exec.callId === 'write-skill') { - await vi.waitFor(async () => { - expect((await ctx.skills.list({ cwd: root })).map(skill => skill.name)).toContain('hot-skill') - }, { timeout: 5_000 }) - } - return decision - }) - ctx.llm.registerAdapter(['mock'], adapter) - const handle = await ctx.agents.create({ - sessionId: SessionId('skill-refresh-session'), - meta: { cwd: root }, - agentOptions: { provider: 'mock', model: 'mock' }, - }) - - handle.agent.followup(createUserMessage({ - content: [{ type: 'text', text: 'Create and load the project skill.' }], - source: { kind: 'user' }, - })) - await waitForIdle(ctx, handle.agent) - - expect(adapter.requests).toHaveLength(4) - expect(adapter.requests.slice(0, 2).map(request => request.messages.map(messageText).join('\n'))) - .toEqual([ - expect.not.stringContaining('hot-skill'), - expect.not.stringContaining('hot-skill'), - ]) - const catalogRequest = adapter.requests[2]?.messages.map(messageText).join('\n') - expect(catalogRequest).toContain('The following skills are available in this session:') - expect(catalogRequest).toContain('- `hot-skill`: Hot-added skill') - const loadedRequest = JSON.stringify(adapter.requests[3]?.messages) - expect(loadedRequest).toContain('') - expect(loadedRequest).toContain('Use the freshly loaded body.') - - const transcript = handle.agent.session.events.flatMap>((event) => { - if (event.type === 'user/message' && event.data.source.kind === 'skill-catalog') { - return [{ - type: event.type, - source: event.data.source, - text: event.data.content.map(block => block.type === 'text' ? block.text : '').join('\n'), - }] - } - if (event.type === 'tool/result' - && ['write-skill', 'load-skill'].includes(event.data.message.source.callId)) { - const result = event.data.message.content[0] - return [{ - type: event.type, - callId: event.data.message.source.callId, - isError: result.isError, - text: result.content.map(block => block.type === 'text' ? block.text : '').join('\n') - .replaceAll(root, '{{cwd}}') - .replaceAll(sep, '/'), - }] - } - return [] - }) - expect(transcript).toMatchInlineSnapshot(` - [ - { - "callId": "write-skill", - "isError": false, - "text": "{{cwd}}/.agents/skills/hot-skill/SKILL.md - file - - Created file - ", - "type": "tool/result", - }, - { - "source": { - "entries": [ - { - "description": "Hot-added skill", - "name": "hot-skill", - }, - ], - "form": "catalog", - "kind": "skill-catalog", - }, - "text": " - A skill is a reusable set of task-specific instructions. The following skills are available in this session: - - - - \`hot-skill\`: Hot-added skill - - - If the user names a skill, or the task clearly matches a skill's description, call the \`skill\` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded. - A user may also invoke a skill directly; its block then appears in this conversation. Follow it, and do not call the \`skill\` tool again for that skill. - ", - "type": "user/message", - }, - { - "callId": "load-skill", - "isError": false, - "text": " - - Base directory for this skill: {{cwd}}/.agents/skills/hot-skill - Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed. - - - - Use the freshly loaded body. - - ", - "type": "tool/result", - }, - ] - `) - - await handle.dispose() - await ctx.fiber.dispose() - } finally { - await rm(root, { recursive: true, force: true }) - await rm(home, { recursive: true, force: true }) - } - }) - - it('shares top-level dshHome between local skills and the managed bash environment', async () => { - const home = await mkdtemp(join(tmpdir(), 'dsh-agent-core-shared-home-')) - const agentsHome = await mkdtemp(join(tmpdir(), 'dsh-agent-core-shared-agents-')) - await mkdir(join(home, 'skills'), { recursive: true }) - await writeFile(join(home, 'skills', 'shared-skill.md'), '---\nname: shared-skill\ndescription: Shared home skill\n---\n\nShared body.\n') - - const ctx = await mount({ - dshHome: home, - workspaceContext: false, - skills: { filesystem: { agentsHome } }, - }, true) - - expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(['shared-skill']) - const execution: ToolExecution = { - signal: testToolSignal, - token: Symbol('agent-core-dsh-home-test') as ToolExecution['token'], - callId: ToolCallId('agent-core-dsh-home'), - rootCallId: ToolCallId('agent-core-dsh-home'), - name: 'bash', - arguments: { command: 'true' }, - } - expect(ctx.shellEnv.collect(execution)).toMatchObject({ DSH_HOME: home, DSH_SHELL: '1' }) - await ctx.fiber.dispose() - }) - - it('rejects conflicting global and nested DSH home directories', () => { - expect(() => { - agentCore.apply(new Context(), { - dshHome: '/global-dsh-home', - workspaceContext: false, - skills: { filesystem: { dshHome: '/nested-dsh-home' } }, - }) - }).toThrow('agent-spine-demo: dshHome and skills.filesystem.dshHome must resolve to the same directory') - }) - - it('delivers workspace instructions ahead of the first-step skill catalog', async () => { - const root = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-prefix-order-')) - try { - await mkdir(join(root, '.git'), { recursive: true }) - await writeFile(join(root, 'AGENTS.md'), 'workspace rule before skills') - const adapter = new MockAdapter([textResponse('first')]) - const ctx = await mount({ workspaceContext: { maxBytes: 65536 } }) - await ctx.plugin(LocalFileSystem, { cwd: '/' }) - ctx.llm.registerAdapter(['mock'], adapter) - ctx.skills.register({ - name: 'prefix-order-skill', - description: 'Skill catalog after workspace rules', - source: 'runtime', - content: 'body', - }) - const handle = await ctx.agents.create({ - sessionId: SessionId('prefix-order-session'), - meta: { cwd: root }, - agentOptions: { provider: 'mock', model: 'mock' }, - }) - - handle.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } })) - await waitForIdle(ctx, handle.agent) - - expect(adapter.requests).toHaveLength(1) - const workspaceIndex = adapter.requests[0]!.messages.findIndex( - message => messageText(message).includes('workspace rule before skills'), - ) - const catalogIndex = adapter.requests[0]!.messages.findIndex( - message => messageText(message).includes('prefix-order-skill'), - ) - expect(workspaceIndex).toBeGreaterThanOrEqual(0) - expect(catalogIndex).toBeGreaterThanOrEqual(0) - expect(workspaceIndex).toBeLessThan(catalogIndex) - await handle.dispose() - await ctx.fiber.dispose() - } finally { - await rm(root, { recursive: true, force: true }) - } - }) - - it('forwards its bundled tool configs to tool-bash and tool-jobs', async () => { - const ctx = await mount({ - workspaceContext: false, - toolBash: { enableRunInBackground: false }, - toolJobs: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 }, - }, true) - - const bash = ctx.tools.schemas().find(tool => tool.name === 'bash') - expect(bash).toBeDefined() - expect(Object.keys((bash!.parameters as { properties: Record }).properties)) - .not.toContain('run_in_background') - - const id = ctx.jobs.start({ - kind: 'probe', - label: 'config forwarding probe', - run: () => ({ cancel: () => {}, done: Promise.resolve({ status: 'completed' }) }), - }) - const wait = vi.spyOn(ctx.jobs, 'wait') - await ctx.tools.execute({ - signal: testToolSignal, - callId: ToolCallId('task-config-forwarding'), - name: 'job_output', - arguments: { job_id: id, wait: true }, - }) - expect(wait).toHaveBeenCalledWith(id, 7, undefined, testToolSignal) - - await ctx.fiber.dispose() - }) - - it('can omit skills and model-facing task controls for a foreground-only deployment', async () => { - const ctx = await mount({ - workspaceContext: false, - skills: { enabled: false }, - toolBash: { enableRunInBackground: false }, - toolJobs: false, - }, true) - - expect(ctx.tools.schemas().map(tool => tool.name)).toEqual(['bash']) - expect(ctx.get('skills')).toBeUndefined() - expect(ctx.get('jobs')).toBeDefined() - - await ctx.fiber.dispose() - }) - - it('can omit the bundled bash tool, Harness identity, and runtime context', async () => { - const ctx = await mount({ - includeHarnessIdentity: false, - includeRuntimeContext: false, - persona: 'You are a helpful software engineer assistant.', - workspaceContext: false, - skills: { enabled: false }, - toolBash: false, - toolJobs: false, - }, true) - - expect(ctx.tools.schemas()).toEqual([]) - ctx.systemPrompt.context({ name: 'policy', order: 0, text: 'hidden policy' }) - expect((await ctx.systemPrompt.assemble()).contexts).toEqual([]) - expect(renderPrompt(await ctx.systemPrompt.assemble())) - .toBe('You are a helpful software engineer assistant.') - - await ctx.fiber.dispose() - }) - - it('picks shared spine config without leaking entry-point fields', () => { - const appConfig = { - model: 'entrypoint-only', - maxParallelToolCalls: 3, - includeHarnessIdentity: false, - includeRuntimeContext: false, - persona: 'You are merged.', - toolOrder: ['zulu'], - tools: { mode: 'native' as const }, - dshHome: '/tmp/dsh-home', - sessionTitle: { fallbackMaxWords: 3, fallbackMaxBytes: 24, maxTitleBytes: 60 }, - workspaceContext: false as const, - skills: { enabled: false }, - toolBash: { enableRunInBackground: false }, - jobs: { maxConcurrentJobsPerOwner: 4 }, - toolJobs: false as const, - invariants: { enabled: false }, - goals: false as const, - } - - expect(agentCore.pickSpineConfig(appConfig)).toEqual({ - maxParallelToolCalls: appConfig.maxParallelToolCalls, - includeHarnessIdentity: appConfig.includeHarnessIdentity, - includeRuntimeContext: appConfig.includeRuntimeContext, - persona: appConfig.persona, - toolOrder: appConfig.toolOrder, - tools: appConfig.tools, - dshHome: appConfig.dshHome, - sessionTitle: appConfig.sessionTitle, - workspaceContext: false, - skills: appConfig.skills, - toolBash: appConfig.toolBash, - jobs: appConfig.jobs, - toolJobs: appConfig.toolJobs, - invariants: appConfig.invariants, - goals: appConfig.goals, - }) - expect(agentCore.pickSpineConfig({ workspaceContext: false })).toEqual({ workspaceContext: false }) - }) - - it('uses the default skill config when apply is called directly without skills', async () => { - await withIsolatedSkillHomes(async () => { - const ctx = new Context() - await ctx.plugin(SessionProjectionRegistry) - agentCore.apply(ctx, { agents: [], workspaceContext: false }) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(ctx.skills).toBeDefined() - expect(await ctx.skills.list()).toEqual([]) - await ctx.fiber.dispose() - }) - }) - - it('forwards toolOrder to the system-prompt assembly', async () => { - const ctx = await mount({ toolOrder: ['zulu', TOOL_ORDER_REST], workspaceContext: false }) - // The bundle's own bash tools pend on the absent `ctx.shell` executor in - // this providerless mount, so register two plain tools to order. - for (const name of ['alpha', 'zulu']) { - ctx.get('tools')!.register({ - name, - description: name, - parameters: {}, - output: { schema: { type: 'null' }, render: () => [] }, - execute: async () => null, - }) - } - const assembly = await ctx.get('systemPrompt')!.assemble() - expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'job_kill', 'job_list', 'job_output', 'skill']) - await ctx.fiber.dispose() - }) - - it('supports direct apply with workspace instructions disabled and no forwarded agents', async () => { - const ctx = new Context() - await ctx.plugin(SessionProjectionRegistry) - agentCore.apply(ctx, { workspaceContext: false }) - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(ctx.get('agents')?.list()).toEqual([]) - expect(ctx.get('systemPrompt')).toBeDefined() - await ctx.fiber.dispose() - }) - - it('re-exports the loop config schema as its own', () => { - expect(agentCore.Config).toBeDefined() - expect(agentCore.name).toBe('agent-spine-demo') - }) - - it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/Config/apply', () => { - // A default export would make `unwrapExports` collapse this inject-less namespace and silently - // drop `name`/`Config`. Apps import the bundle directly, so this is its Loader-shape guard. - expect('default' in agentCore).toBe(false) - expect(typeof agentCore.apply).toBe('function') - - const loader = Object.create(Loader.prototype) as Loader - const unwrapped = loader.unwrapExports(agentCore) as Record - expect(unwrapped).toBe(agentCore) - expect(unwrapped.name).toBe('agent-spine-demo') - expect(unwrapped.Config).toBeDefined() - expect(typeof unwrapped.apply).toBe('function') - }) - - it('keeps each standard-spine invariant companion loadable through the real Loader unwrap path', () => { - const loader = Object.create(Loader.prototype) as Loader - for (const companion of [sessionInvariant, agentInvariant, scopeInvariant, agentLoopInvariant]) { - expect('default' in companion).toBe(false) - const unwrapped = loader.unwrapExports(companion) as Record - expect(unwrapped).toBe(companion) - expect(typeof unwrapped.name).toBe('string') - expect(unwrapped.inject).toContain('invariants') - expect(typeof unwrapped.apply).toBe('function') - } - }) -}) diff --git a/packages/examples/agent-spine-demo/tests/gen-config-catalog.spec.ts b/packages/examples/agent-spine-demo/tests/gen-config-catalog.spec.ts deleted file mode 100644 index dfd3ff407f..0000000000 --- a/packages/examples/agent-spine-demo/tests/gen-config-catalog.spec.ts +++ /dev/null @@ -1,468 +0,0 @@ -/** - * Negative-path tests for the config catalog generator (`scripts/gen-config-catalog.ts`). - */ - -import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, describe, expect, it } from 'vitest' -import { collectConfigCatalog, render } from '../../../../scripts/gen-config-catalog.ts' - -/** Write one fixture package (package.json + src files) under a scan root. */ -function writePkg(root: string, dir: string, name: string, files: Record): void { - const pkgDir = join(root, 'packages', dir) - mkdirSync(join(pkgDir, 'src'), { recursive: true }) - writeFileSync(join(pkgDir, 'package.json'), JSON.stringify({ name })) - for (const [rel, text] of Object.entries(files)) writeFileSync(join(pkgDir, rel), text) -} - -const roots: string[] = [] -const makeRoot = (): string => { - const root = mkdtempSync(join(tmpdir(), 'config-catalog-')) - roots.push(root) - return root -} -/** One-package fixture: the common case. */ -const make = (files: Record, name = '@fix/one'): string => { - const root = makeRoot() - writePkg(root, 'group/one', name, files) - return root -} - -afterEach(() => { - while (roots.length) rmSync(roots.pop()!, { recursive: true, force: true }) -}) - -const DOCUMENTED_CONFIG = `/** Fixture config. */ -export interface Config { - /** A knob. */ - knob?: string -} -` - -describe('gen-config-catalog classification', () => { - it('classifies an apply plugin with a config parameter and extracts the paste', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -export const inject = ['tools'] -${DOCUMENTED_CONFIG} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - })) - expect(entries).toHaveLength(1) - expect(entries[0]).toMatchObject({ pkg: '@fix/one', kind: 'config', configTypeName: 'Config', inject: ['tools'] }) - expect(entries[0]?.pastes?.[0]?.text).toContain('/** A knob. */') - }) - - it('classifies a default service class, reading its constructor and static inject', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -${DOCUMENTED_CONFIG} -/** Fixture service. */ -export default class Fix { - static inject = ['llm'] - static Config = z.object({ knob: z.string() }) as unknown as z - constructor(ctx: Context, config: Config) {} -} -`, - })) - expect(entries[0]).toMatchObject({ kind: 'config', className: 'Fix', inject: ['llm'], schemaKeys: ['knob'] }) - }) - - it('classifies an abstract default class as a seam', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': 'export default abstract class FixSeam { abstract run(): void }\n', - })) - expect(entries[0]).toMatchObject({ kind: 'seam', className: 'FixSeam' }) - }) - - it('classifies a plugin whose apply takes no config as no-config', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': 'import type { Context } from \'cordis\'\n/** Load. */\nexport function apply(ctx: Context): void {}\n', - })) - expect(entries[0]?.kind).toBe('no-config') - }) - - it('classifies a module with neither default export nor apply as a library', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': 'export const helper = 1\n', - })) - expect(entries[0]?.kind).toBe('library') - }) - - it('hard-errors on a package with no entry file', () => { - const root = makeRoot() - mkdirSync(join(root, 'packages', 'group', 'one'), { recursive: true }) - writeFileSync(join(root, 'packages', 'group', 'one', 'package.json'), JSON.stringify({ name: '@fix/one' })) - expect(() => collectConfigCatalog(root)).toThrow(/entry .* is missing or unreadable/) - }) - - it('hard-errors on a package.json without a name', () => { - const root = makeRoot() - mkdirSync(join(root, 'packages', 'group', 'one', 'src'), { recursive: true }) - writeFileSync(join(root, 'packages', 'group', 'one', 'package.json'), '{}') - expect(() => collectConfigCatalog(root)).toThrow(/has no "name"/) - }) -}) - -describe('gen-config-catalog config extraction guards', () => { - it('hard-errors on a config field with no JSDoc prose', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -export interface Config { - knob?: string -} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).toThrow(/config field 'Config\.knob' .* has no JSDoc prose/) - }) - - it('hard-errors on an undocumented field nested in a type literal', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -/** Fixture config. */ -export interface Config { - /** Entries. */ - entries: { - id: string - }[] -} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).toThrow(/config field 'Config\.entries\.id' .* has no JSDoc prose/) - }) - - it('pastes a package-local type transitively and records external refs', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import type { Mode } from './types.ts' -import type { Remote } from '@fix/dep' -/** Fixture config. */ -export interface Config { - /** The mode. */ - mode?: Mode - /** The remote. */ - remote?: Remote -} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - 'src/types.ts': '/** Fixture mode. */\nexport type Mode = \'a\' | \'b\'\n', - })) - expect(entries[0]?.pastes?.map(p => p.source)).toEqual([ - 'packages/group/one/src/index.ts:5', - 'packages/group/one/src/types.ts:2', - ]) - expect(entries[0]?.refs).toEqual([{ alias: 'Remote', imported: 'Remote', specifier: '@fix/dep' }]) - }) - - it('pastes an enum referenced by the config type', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -/** Fixture mode. */ -export enum Mode { - A = 'a', - B = 'b', -} -/** Fixture config. */ -export interface Config { - /** The mode. */ - mode?: Mode -} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - })) - expect(entries[0]?.pastes?.map(p => p.text)).toEqual([ - '/** Fixture config. */\nexport interface Config {\n /** The mode. */\n mode?: Mode\n}', - "/** Fixture mode. */\nexport enum Mode {\n A = 'a',\n B = 'b',\n}", - ]) - }) - - it('hard-errors on a referenced type name that resolves nowhere', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -/** Fixture config. */ -export interface Config { - /** The ghost. */ - ghost?: Ghost -} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).toThrow(/references 'Ghost' .* neither declared in the package, imported, nor a known global/) - }) - - it('hard-errors on a config type imported from another package', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import type { Config } from '@fix/dep' -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).toThrow(/config type 'Config' is imported from '@fix\/dep'/) - }) - - it('hard-errors when one name resolves to two different declarations across the closure', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import type { A } from './a.ts' -import type { B } from './b.ts' -/** Fixture config. */ -export interface Config { - /** A. */ - a?: A - /** B. */ - b?: B -} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - 'src/a.ts': '/** First Option. */\nexport interface Option {\n /** X. */\n x?: string\n}\n/** A. */\nexport interface A {\n /** O. */\n o?: Option\n}\n', - 'src/b.ts': '/** Second Option. */\nexport interface Option {\n /** Y. */\n y?: string\n}\n/** B. */\nexport interface B {\n /** O. */\n o?: Option\n}\n', - }))).toThrow(/type name 'Option' resolves to two different declarations/) - }) -}) - -describe('gen-config-catalog schema cross-check', () => { - it('accepts a chained schema whose keys all appear on the config type', () => { - const entries = collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -${DOCUMENTED_CONFIG} -export const Config: z = z.object({ knob: z.string() }).default({}) -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - })) - expect(entries[0]?.schemaKeys).toEqual(['knob']) - }) - - it('hard-errors on a schema key the config type does not declare', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -${DOCUMENTED_CONFIG} -export const Config: z = z.object({ knob: z.string(), hidden: z.number() }) -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).toThrow(/schema validates key 'hidden' but config type 'Config' declares no such member/) - }) - - it('hard-errors on a NESTED schema key the config type does not declare', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -/** Fixture config. */ -export interface Config { - /** Entries. */ - entries: { - /** Id. */ - id: string - }[] -} -export const Config: z = z.object({ entries: z.array(z.object({ id: z.string(), ghost: z.string() })) }) -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).toThrow(/schema validates key 'entries\[\]\.ghost'/) - }) - - it('resolves nested keys through a workspace-imported intersection part (re-export chains included)', () => { - const root = makeRoot() - writePkg(root, 'group/dep', '@fix/dep', { - 'src/index.ts': 'export * from \'./types.ts\'\n', - 'src/types.ts': '/** Shared options. */\nexport interface Opts {\n /** Model. */\n model?: string\n}\n', - }) - writePkg(root, 'group/one', '@fix/one', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import type { Opts } from '@fix/dep' -/** Fixture config. */ -export interface Config { - /** Entries. */ - entries: (Opts & { - /** Id. */ - id: string - })[] -} -export const Config: z = z.object({ entries: z.array(z.object({ id: z.string(), model: z.string() })) }) -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }) - expect(() => collectConfigCatalog(root)).not.toThrow() - }) - - it('resolves nested keys through a Partial<> wrapper', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -/** Caps. */ -export interface Caps { - /** X. */ - x?: boolean -} -/** Fixture config. */ -export interface Config { - /** Capabilities. */ - capabilities?: Partial -} -export const Config: z = z.object({ capabilities: z.object({ x: z.boolean() }) }) -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).not.toThrow() - }) - - it('leaves a nested key under an external (unresolvable) type unreported', () => { - expect(() => collectConfigCatalog(make({ - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import type { External } from 'some-external-pkg' -/** Fixture config. */ -export interface Config { - /** Options. */ - options?: External -} -export const Config: z = z.object({ options: z.object({ whatever: z.string() }) }) -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }))).not.toThrow() - }) - - it('folds an intersected workspace schema into the subset check', () => { - const root = makeRoot() - writePkg(root, 'group/leaf', '@fix/leaf', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -/** Leaf config. */ -export interface Config { - /** Leaf knob. */ - leaf?: string -} -/** Leaf service. */ -export default class Leaf { - static Config = z.object({ leaf: z.string() }) as unknown as z - constructor(ctx: Context, config: Config) {} -} -`, - }) - writePkg(root, 'group/bundle', '@fix/bundle', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import Leaf from '@fix/leaf' -/** Bundle config. */ -export interface Config { - /** Forwarded leaf knob. */ - leaf?: string -} -export const Config = z.intersect([Leaf.Config]) as unknown as z -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }) - const entries = collectConfigCatalog(root) - expect(entries.find(e => e.pkg === '@fix/bundle')?.schemaComposes).toEqual(['@fix/leaf']) - }) - - it('resolves composed nested keys through an indexed-access forwarder', () => { - const root = makeRoot() - writePkg(root, 'group/leaf', '@fix/leaf', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -/** Leaf config. */ -export interface Config { - /** Agents. */ - agents: { - /** Id. */ - id: string - }[] -} -/** Leaf service. */ -export default class Leaf { - static Config = z.object({ agents: z.array(z.object({ id: z.string() })) }) as unknown as z - constructor(ctx: Context, config: Config) {} -} -`, - }) - writePkg(root, 'group/bundle', '@fix/bundle', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import Leaf, { type Config as LeafConfig } from '@fix/leaf' -/** Bundle config forwarding the leaf's agents list. */ -export interface Config { - /** Forwarded agents list. */ - agents?: LeafConfig['agents'] -} -export const Config = z.intersect([Leaf.Config]) as unknown as z -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }) - expect(() => collectConfigCatalog(root)).not.toThrow() - }) - - it('hard-errors when an intersected schema key is missing from the bundle config type', () => { - const root = makeRoot() - writePkg(root, 'group/leaf', '@fix/leaf', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -/** Leaf config. */ -export interface Config { - /** Leaf knob. */ - leaf?: string -} -/** Leaf service. */ -export default class Leaf { - static Config = z.object({ leaf: z.string() }) as unknown as z - constructor(ctx: Context, config: Config) {} -} -`, - }) - writePkg(root, 'group/bundle', '@fix/bundle', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import Leaf from '@fix/leaf' -/** Bundle config that forgot to declare the forwarded field. */ -export interface Config { - /** Unrelated. */ - other?: string -} -export const Config = z.intersect([Leaf.Config]) as unknown as z -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }) - expect(() => collectConfigCatalog(root)).toThrow(/schema validates key 'leaf' but config type 'Config' declares no such member/) - }) -}) - -describe('gen-config-catalog render', () => { - it('renders sections, fences, and the terse classification lists', () => { - const root = makeRoot() - writePkg(root, 'group/one', '@fix/one', { - 'src/index.ts': `import type { Context } from '@deepseek-ai/cordis' -${DOCUMENTED_CONFIG} -/** Load. */ -export function apply(ctx: Context, config: Config): void {} -`, - }) - writePkg(root, 'group/lib', '@fix/lib', { 'src/index.ts': 'export const helper = 1\n' }) - writePkg(root, 'group/seam', '@fix/seam', { - 'src/index.ts': 'export default abstract class Seam { abstract run(): void }\n', - }) - const page = render(collectConfigCatalog(root)) - expect(page).toContain('## `@fix/one`') - expect(page).toContain('```ts config-catalog') - expect(page).toContain('/** A knob. */') - expect(page).toContain('- `@fix/lib` ([`packages/group/lib/src/index.ts`](../packages/group/lib/src/index.ts))') - expect(page).toContain('- `@fix/seam` — abstract `Seam`') - }) -}) diff --git a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts deleted file mode 100644 index 613ebb2aec..0000000000 --- a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { spawnSync } from 'node:child_process' -import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises' -import { homedir } from 'node:os' -import { basename, join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' -import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local' -import * as FsPolicy from '@deepseek-ai/dsh-fs-observation-policy' -import SandboxedFileSystem from '@deepseek-ai/dsh-fs-sandbox' -import { ToolCallId } from '@deepseek-ai/dsh-llm' -import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' -import { bwrapProfileArgs, seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts' -import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import { SessionId } from '@deepseek-ai/dsh-session' -import * as ToolFs from '@deepseek-ai/dsh-tool-fs' -import type { ToolResult } from '@deepseek-ai/dsh-tools' -import { launcherPath } from '@deepseek-ai/node-addon-landlock-run' -import * as agentSpine from '../src/index.ts' - -const bwrapUsable = spawnSync('bwrap', [...bwrapProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' }).status === 0 -const landlockUsable = spawnSync(launcherPath(), ['--probe'], { timeout: 5_000, stdio: 'ignore' }).status === 0 -const seatbeltUsable = process.platform === 'darwin' - && spawnSync('sandbox-exec', [...seatbeltProfileArgs({ mode: 'workspace-write', workspaceRoot: homedir() }), '--', 'true'], { timeout: 5_000, stdio: 'ignore' }).status === 0 -const processSandboxUsable = bwrapUsable || landlockUsable || seatbeltUsable - -let ctx: Context | undefined -let projectA: string -let projectB: string -const tempDirs: string[] = [] - -async function projectDir(label: string): Promise { - const dir = await mkdtemp(join(homedir(), `dsh-${label}-`)) - tempDirs.push(dir) - return dir -} - -async function expectMissing(path: string): Promise { - await expect(readFile(path, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' }) -} - -function resultText(result: ToolResult): string { - return result.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\n') -} - -beforeEach(async () => { - projectA = await projectDir('project-a') - projectB = await projectDir('project-b') - const fallbackRoot = await projectDir('fallback') - - ctx = new Context() - await ctx.plugin(LocalSandboxProvider, {}) - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(SandboxPolicyService, { mode: 'workspace-write', workspaceRoot: fallbackRoot }) - await ctx.plugin(LocalSubprocessRuntime) - await ctx.plugin(SandboxBashExecutor, { cwd: fallbackRoot, timeoutMs: 30_000 }) - await ctx.plugin(SandboxedFileSystem, { cwd: fallbackRoot }) - await ctx.plugin(agentSpine, { - workspaceContext: false, - skills: { enabled: false }, - toolBash: { enableRunInBackground: false }, - toolJobs: false, - }) - await new Promise(resolve => setTimeout(resolve, 50)) - await ctx.plugin(FsPolicy) - await ctx.plugin(ToolFs) -}) - -afterEach(async () => { - await ctx?.fiber.dispose() - ctx = undefined - await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true }))) -}) - -async function agents() { - const active = ctx as Context - const [a, b] = await Promise.all([ - active.agents.create({ sessionId: SessionId('project-a-session'), meta: { cwd: projectA } }), - active.agents.create({ sessionId: SessionId('project-b-session'), meta: { cwd: projectB } }), - ]) - return { active, agentA: a.agent, agentB: b.agent } -} - -describe('one-context multi-project sandbox', () => { - it.skipIf(!processSandboxUsable)('confines concurrent bash calls to each calling session workspace', async () => { - const { active, agentA, agentB } = await agents() - const [aOwn, bOwn, aCross, bCross] = await Promise.all([ - active.tools.execute({ - callId: ToolCallId('bash-a-own'), name: 'bash', agent: agentA, - signal: new AbortController().signal, - arguments: { command: 'printf a > a-owned.txt', description: 'Write project A marker' }, - }), - active.tools.execute({ - callId: ToolCallId('bash-b-own'), name: 'bash', agent: agentB, - signal: new AbortController().signal, - arguments: { command: 'printf b > b-owned.txt', description: 'Write project B marker' }, - }), - active.tools.execute({ - callId: ToolCallId('bash-a-cross'), name: 'bash', agent: agentA, - signal: new AbortController().signal, - arguments: { command: `printf cross > ../${basename(projectB)}/from-a.txt`, description: 'Attempt project B write' }, - }), - active.tools.execute({ - callId: ToolCallId('bash-b-cross'), name: 'bash', agent: agentB, - signal: new AbortController().signal, - arguments: { command: `printf cross > ../${basename(projectA)}/from-b.txt`, description: 'Attempt project A write' }, - }), - ]) - - expect(aOwn.isError).toBe(false) - expect(bOwn.isError).toBe(false) - expect(aCross.isError).toBe(false) - expect(bCross.isError).toBe(false) - expect(resultText(aCross)).toContain('[sandbox: file access denied under workspace-write mode]') - expect(resultText(bCross)).toContain('[sandbox: file access denied under workspace-write mode]') - expect(await readFile(join(projectA, 'a-owned.txt'), 'utf8')).toBe('a') - expect(await readFile(join(projectB, 'b-owned.txt'), 'utf8')).toBe('b') - await expectMissing(join(projectB, 'from-a.txt')) - await expectMissing(join(projectA, 'from-b.txt')) - }) - - it('confines concurrent filesystem writes to each calling session workspace', async () => { - const { active, agentA, agentB } = await agents() - const [aOwn, bOwn, aCross, bCross] = await Promise.all([ - active.tools.execute({ - callId: ToolCallId('fs-a-own'), name: 'write', agent: agentA, - signal: new AbortController().signal, - arguments: { file_path: 'a-owned.txt', content: 'a' }, - }), - active.tools.execute({ - callId: ToolCallId('fs-b-own'), name: 'write', agent: agentB, - signal: new AbortController().signal, - arguments: { file_path: 'b-owned.txt', content: 'b' }, - }), - active.tools.execute({ - callId: ToolCallId('fs-a-cross'), name: 'write', agent: agentA, - signal: new AbortController().signal, - arguments: { file_path: join(projectB, 'from-a.txt'), content: 'cross' }, - }), - active.tools.execute({ - callId: ToolCallId('fs-b-cross'), name: 'write', agent: agentB, - signal: new AbortController().signal, - arguments: { file_path: join(projectA, 'from-b.txt'), content: 'cross' }, - }), - ]) - - expect(aOwn.isError).toBe(false) - expect(bOwn.isError).toBe(false) - expect(aCross.isError).toBe(true) - expect(bCross.isError).toBe(true) - expect(resultText(aCross)).toContain('[sandbox: file access denied under workspace-write mode]') - expect(resultText(bCross)).toContain('[sandbox: file access denied under workspace-write mode]') - expect(await readFile(join(projectA, 'a-owned.txt'), 'utf8')).toBe('a') - expect(await readFile(join(projectB, 'b-owned.txt'), 'utf8')).toBe('b') - await expectMissing(join(projectB, 'from-a.txt')) - await expectMissing(join(projectA, 'from-b.txt')) - }) - - it.skipIf(!processSandboxUsable)('keeps symlink-sensitive session cwd semantics aligned across bash, fs, and policy', async () => { - const active = ctx as Context - const lexicalRoot = await projectDir('lexical-workspace') - const physicalRoot = await projectDir('physical-workspace') - const physicalChild = join(physicalRoot, 'child') - await mkdir(physicalChild) - const link = join(lexicalRoot, 'link') - await symlink(physicalChild, link, process.platform === 'win32' ? 'junction' : 'dir') - const sessionCwd = `${link}/..` - const handle = await active.agents.create({ - sessionId: SessionId('symlink-parent-session'), - meta: { cwd: sessionCwd }, - }) - - const [bashOwn, bashLexical, fsOwn, fsLexical] = await Promise.all([ - active.tools.execute({ - callId: ToolCallId('bash-symlink-own'), name: 'bash', agent: handle.agent, - signal: new AbortController().signal, - arguments: { command: 'printf bash > bash-owned.txt', description: 'Write physical workspace marker' }, - }), - active.tools.execute({ - callId: ToolCallId('bash-symlink-lexical'), name: 'bash', agent: handle.agent, - signal: new AbortController().signal, - arguments: { command: `printf escaped > ${join(lexicalRoot, 'bash-escaped.txt')}`, description: 'Attempt lexical workspace write' }, - }), - active.tools.execute({ - callId: ToolCallId('fs-symlink-own'), name: 'write', agent: handle.agent, - signal: new AbortController().signal, - arguments: { file_path: 'fs-owned.txt', content: 'fs' }, - }), - active.tools.execute({ - callId: ToolCallId('fs-symlink-lexical'), name: 'write', agent: handle.agent, - signal: new AbortController().signal, - arguments: { file_path: join(lexicalRoot, 'fs-escaped.txt'), content: 'escaped' }, - }), - ]) - - expect(bashOwn.isError).toBe(false) - expect(resultText(bashOwn)).not.toContain('[sandbox:') - expect(bashLexical.isError).toBe(false) - expect(resultText(bashLexical)).toContain('[sandbox: file access denied under workspace-write mode]') - expect(fsOwn.isError).toBe(false) - expect(fsLexical.isError).toBe(true) - expect(resultText(fsLexical)).toContain('[sandbox: file access denied under workspace-write mode]') - expect(await readFile(join(physicalRoot, 'bash-owned.txt'), 'utf8')).toBe('bash') - expect(await readFile(join(physicalRoot, 'fs-owned.txt'), 'utf8')).toBe('fs') - await expectMissing(join(lexicalRoot, 'bash-escaped.txt')) - await expectMissing(join(lexicalRoot, 'fs-escaped.txt')) - }) - - it.skipIf(!processSandboxUsable)('resolves parent traversal from a symlinked session root consistently', async () => { - const active = ctx as Context - const lexicalRoot = await projectDir('lexical-parent') - const physicalRoot = await projectDir('physical-parent') - const physicalChild = join(physicalRoot, 'child') - await mkdir(physicalChild) - const link = join(lexicalRoot, 'link') - await symlink(physicalChild, link, process.platform === 'win32' ? 'junction' : 'dir') - await writeFile(join(lexicalRoot, 'shared.txt'), 'from-lexical-parent') - await writeFile(join(physicalRoot, 'shared.txt'), 'from-physical-parent') - const handle = await active.agents.create({ - sessionId: SessionId('symlink-root-parent-path-session'), - meta: { cwd: link }, - }) - - const [bashRead, fsRead] = await Promise.all([ - active.tools.execute({ - callId: ToolCallId('bash-symlink-parent-read'), name: 'bash', agent: handle.agent, - signal: new AbortController().signal, - arguments: { command: 'cat ../shared.txt', description: 'Read through the physical parent' }, - }), - active.tools.execute({ - callId: ToolCallId('fs-symlink-parent-read'), name: 'read', agent: handle.agent, - signal: new AbortController().signal, - arguments: { file_path: '../shared.txt' }, - }), - ]) - - expect(bashRead.isError).toBe(false) - expect(fsRead.isError).toBe(false) - expect(resultText(bashRead)).toContain('from-physical-parent') - expect(resultText(fsRead)).toContain('from-physical-parent') - expect(resultText(bashRead)).not.toContain('from-lexical-parent') - expect(resultText(fsRead)).not.toContain('from-lexical-parent') - }) -}) diff --git a/packages/examples/agent-spine-demo/tsconfig.json b/packages/examples/agent-spine-demo/tsconfig.json deleted file mode 100644 index e81c7b4591..0000000000 --- a/packages/examples/agent-spine-demo/tsconfig.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "../../../vendor/cordis" - }, - { - "path": "../../../vendor/timer" - }, - { - "path": "../../../vendor/schemastery" - }, - { - "path": "../../../native/landlock-run/packages/entry" - }, - { - "path": "../../llm/llm" - }, - { - "path": "../../core/session" - }, - { - "path": "../../session/session-title" - }, - { - "path": "../../core/system-prompt" - }, - { - "path": "../../core/tools" - }, - { - "path": "../../skill/skill" - }, - { - "path": "../../skill/skill-filesystem" - }, - { - "path": "../../skill/tool-skill" - }, - { - "path": "../../core/agent" - }, - { - "path": "../../goal/goal" - }, - { - "path": "../../goal/tool-goal" - }, - { - "path": "../../goal/goal-round-driver" - }, - { - "path": "../../context/agent-instructions" - }, - { - "path": "../../core/agent-loop" - }, - { - "path": "../../llm/llm-retry" - }, - { - "path": "../../runtime-diagnostics/invariants" - }, - { - "path": "../../util/home-paths" - }, - { - "path": "../../shell/shell-env" - }, - { - "path": "../../shell/tool-bash" - }, - { - "path": "../../jobs/jobs" - }, - { - "path": "../../jobs/jobs-local" - }, - { - "path": "../../jobs/tool-jobs" - } - ] -} diff --git a/packages/goal/command-goal/README.i18n.yaml b/packages/goal/command-goal/README.i18n.yaml index 80d925f56d..dbbe85b1b8 100644 --- a/packages/goal/command-goal/README.i18n.yaml +++ b/packages/goal/command-goal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/goal/command-goal/README.md -README.md: bb5aa406b0269e9fbedd1e85d557ac17640e3e73 -README.zh.md: 45864aa93abcd81584b27eff859bbcdd7b22a1f6 +README.md: d50b7b2a8c7d2d4b0e9ee1d518fda072d6f95087 +README.zh.md: 95b21bcd3af2b12199c3c5193b1da8e2e8964d37 diff --git a/packages/goal/command-goal/README.md b/packages/goal/command-goal/README.md index bb5aa406b0..d50b7b2a8c 100644 --- a/packages/goal/command-goal/README.md +++ b/packages/goal/command-goal/README.md @@ -61,7 +61,7 @@ The command injects the commands registry and the goal service. A custom app mou name: '@deepseek-ai/dsh-command-goal' ``` -The shipped `dsh` base enables the persisted-goal stack and this command. The Web bundle keeps the goal service and driver on the Host, disables the base command producer, and mounts the producer in the `standard`, `code`, and `cordis` agent presets; `minimal` omits it. The ACP automation app enables the domain and model tools without a command adapter. The UI-less `agent-spine-demo` requires an explicit `goals: {}` so headless one-shot callers do not silently change from one physical turn to a multi-round operation. +The shipped `dsh` base enables the persisted-goal stack and this command. The Web bundle keeps the goal service and driver on the Host, disables the base command producer, and mounts the producer in the `standard`, `code`, and `cordis` agent presets; `minimal` omits it. The ACP automation app enables the domain and model tools without a command adapter. The standalone `sdk-minimal` profile omits the complete goal stack so its result API still settles one correlated physical turn. ----- diff --git a/packages/goal/command-goal/README.zh.md b/packages/goal/command-goal/README.zh.md index 45864aa93a..95b21bcd3a 100644 --- a/packages/goal/command-goal/README.zh.md +++ b/packages/goal/command-goal/README.zh.md @@ -61,7 +61,7 @@ kind: "package-reference" name: '@deepseek-ai/dsh-command-goal' ``` -随附的 `dsh` 基础配置启用持久 goal 栈与此命令。Web bundle 把 goal 服务与 driver 保留在 Host,禁用基础命令 producer,并在 `standard`、`code` 和 `cordis` agent preset 中挂载 producer;`minimal` 会省略它。ACP(Agent Client Protocol)自动化应用启用领域与模型工具,但不挂载命令适配器。无 UI 的 `agent-spine-demo` 必须显式配置 `goals: {}`,避免无头单次调用方在不知情时从一个物理轮次变为包含多个 Round 的操作。 +随附的 `dsh` 基础配置启用持久 goal 栈与此命令。Web bundle 把 goal 服务与 driver 保留在 Host,禁用基础命令 producer,并在 `standard`、`code` 和 `cordis` agent preset 中挂载 producer;`minimal` 会省略它。ACP(Agent Client Protocol)自动化应用启用领域与模型工具,但不挂载命令适配器。独立的 `sdk-minimal` profile 省略完整 goal 栈,因此其结果 API 仍在一个关联的物理轮次后结束。 ----- diff --git a/packages/goal/goal/package.json b/packages/goal/goal/package.json index 43d6ea1b62..abc90ada9b 100644 --- a/packages/goal/goal/package.json +++ b/packages/goal/goal/package.json @@ -69,7 +69,6 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-session-projection": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", diff --git a/packages/goal/goal/tests/fixtures/domain/cordis.yml b/packages/goal/goal/tests/fixtures/domain/cordis.yml deleted file mode 100644 index b8a0e2dc8d..0000000000 --- a/packages/goal/goal/tests/fixtures/domain/cordis.yml +++ /dev/null @@ -1,38 +0,0 @@ -# Test-only composition: create one goal through a Loader-mounted step consumer. -- id: cli-mock-llm - name: '../../../../../test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts' - -# Managed child-process groups for the bash executor (spawn/kill/output plumbing). -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: bash - name: '@deepseek-ai/dsh-bash-local' - -- id: goal - name: '@deepseek-ai/dsh-goal' - config: - defaultMaxGoalRounds: 11 - -- id: seed-goal - name: './seed-goal.ts' - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: cli-mock - model: cli-mock - cwd: !!js process.cwd() - persona: 'Test the persisted goal domain.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: none - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/goal/goal/tests/fixtures/domain/goal.patch.yml b/packages/goal/goal/tests/fixtures/domain/goal.patch.yml new file mode 100644 index 0000000000..bc7af0b9e7 --- /dev/null +++ b/packages/goal/goal/tests/fixtures/domain/goal.patch.yml @@ -0,0 +1,31 @@ +# Test-only patch over the shipped headless profile. The real base bundle +# supplies the goal domain, model tools, and agent runtime. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: goal + config: + defaultMaxGoalRounds: 11 + +- id: agent-loop + config: + agents: + - id: main + provider: cli-mock + model: cli-mock + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: cli-mock-llm + name: '../../../../../test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts' + + - id: seed-goal + name: './seed-goal.ts' diff --git a/packages/goal/goal/tests/goal.e2e.ts b/packages/goal/goal/tests/goal.e2e.ts index ebaac243fb..b3f1f4e3c0 100644 --- a/packages/goal/goal/tests/goal.e2e.ts +++ b/packages/goal/goal/tests/goal.e2e.ts @@ -8,7 +8,7 @@ import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-l const binScript = fileURLToPath(new URL('../../../test-support/loader-smoke/tests/fixtures/headless-driver.ts', import.meta.url)) const configPath = fileURLToPath(new URL( - './fixtures/domain/cordis.yml', + './fixtures/domain/goal.patch.yml', import.meta.url, )) const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) @@ -23,7 +23,7 @@ async function jsonlFiles(dir: string): Promise { return paths.flat() } -describe('goal domain through a real cordis.yml and headless process', () => { +describe('goal domain through the production headless profile and process', () => { it('persists the Loader-mounted snapshot without starting a goal round', async () => { let events: SessionEvent[] = [] const { stdout, stderr } = await runLoaderSmoke({ diff --git a/packages/runtime-diagnostics/invariants/README.i18n.yaml b/packages/runtime-diagnostics/invariants/README.i18n.yaml index c4c6a4bf0a..c39d241481 100644 --- a/packages/runtime-diagnostics/invariants/README.i18n.yaml +++ b/packages/runtime-diagnostics/invariants/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/runtime-diagnostics/invariants/README.md -README.md: 72bb74cf5b9b367d0a29cb942370c4fa3d86e84c -README.zh.md: b7cffce5cf988bdfedce6b7b5de41d224182e086 +README.md: bb5057a771e7e6f70d6620c348bb0307f1c12688 +README.zh.md: 5cbe1ac96ee23858ddd954abdff20d60127403e3 diff --git a/packages/runtime-diagnostics/invariants/README.md b/packages/runtime-diagnostics/invariants/README.md index 72bb74cf5b..bb5057a771 100644 --- a/packages/runtime-diagnostics/invariants/README.md +++ b/packages/runtime-diagnostics/invariants/README.md @@ -29,7 +29,7 @@ Mount the registry when a composition should verify its own runtime contracts, t ### When to use it -Use the registry for compositions that want live diagnostics. The standard agent composition in [`agent-spine-demo`](../../../packages/examples/agent-spine-demo/README.md) already mounts it with the four core stateful companions — `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop`. Custom compositions mount the registry and add companions for any other loaded package whose contracts they want checked. Loading the registry alone installs no checks: it ships no product checks of its own, so a composition that never mounts a companion observes no diagnostic behavior. +Use the registry for compositions that want live diagnostics. [`dsh-sdk-minimal`](../../bundle/sdk-minimal/README.md) mounts it with the four core stateful companions — `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop`; `dsh-base` deliberately omits runtime diagnostics. Custom compositions mount the registry and add companions for any other loaded package whose contracts they want checked. Loading the registry alone installs no checks: it ships no product checks of its own, so a composition that never mounts a companion observes no diagnostic behavior. ### Enabling checks and selecting packages diff --git a/packages/runtime-diagnostics/invariants/README.zh.md b/packages/runtime-diagnostics/invariants/README.zh.md index b7cffce5cf..5cbe1ac96e 100644 --- a/packages/runtime-diagnostics/invariants/README.zh.md +++ b/packages/runtime-diagnostics/invariants/README.zh.md @@ -29,7 +29,7 @@ kind: "package-reference" ### 何时使用 -需要实时诊断的组合请使用注册表。[`agent-spine-demo`](../../../packages/examples/agent-spine-demo/README.zh.md) 中的标准 agent 组合已挂载它及四个核心有状态配套入口——`dsh-session`、`dsh-agent`、`dsh-scope` 与 `dsh-agent-loop`。自定义组合挂载注册表,并为任何其他已加载、且希望检查其约定的包添加配套入口。单独加载注册表不会安装任何检查:它自身不携带任何产品检查,因此从不挂载配套入口的组合不会观察到任何诊断行为。 +需要实时诊断的组合请使用注册表。[`dsh-sdk-minimal`](../../bundle/sdk-minimal/README.zh.md) 挂载它及四个核心有状态配套入口——`dsh-session`、`dsh-agent`、`dsh-scope` 与 `dsh-agent-loop`;`dsh-base` 刻意省略运行时诊断。自定义组合挂载注册表,并为任何其他已加载、且希望检查其约定的包添加配套入口。单独加载注册表不会安装任何检查:它自身不携带任何产品检查,因此从不挂载配套入口的组合不会观察到任何诊断行为。 ### 启用检查与选择包 diff --git a/packages/sdk/server/package.json b/packages/sdk/server/package.json index 629fbdc2c5..c449cb16d3 100644 --- a/packages/sdk/server/package.json +++ b/packages/sdk/server/package.json @@ -51,7 +51,8 @@ "@deepseek-ai/cordis": "workspace:^", "@deepseek-ai/cordis-plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", + "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-attachment": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", diff --git a/packages/sdk/server/tests/built-scope-carrier.e2e.ts b/packages/sdk/server/tests/built-scope-carrier.e2e.ts index ae4ab8ef05..e55bf76bb2 100644 --- a/packages/sdk/server/tests/built-scope-carrier.e2e.ts +++ b/packages/sdk/server/tests/built-scope-carrier.e2e.ts @@ -26,14 +26,16 @@ import { pathToFileURL } from "node:url"; const load = (path) => import(pathToFileURL(resolve(path)).href); const [ { Context }, - agentCore, + { default: AgentLoop }, + { mountAgentLoopTestDependencies }, { default: SubagentRuntime }, { default: JsonlSessionPersistence }, { HarnessSdkJsonRpcServer }, { SessionId }, ] = await Promise.all([ load("vendor/cordis/lib/index.js"), - load("packages/examples/agent-spine-demo/lib/index.js"), + load("packages/core/agent-loop/lib/index.js"), + load("packages/test-support/agent-loop-testkit/lib/index.js"), load("packages/subagent/subagent/lib/index.js"), load("packages/session/session-persistence-jsonl/lib/index.js"), load("packages/sdk/server/lib/index.js"), @@ -43,7 +45,8 @@ const [ const storageRoot = await mkdtemp(join(tmpdir(), "jsonrpc-built-scope-")); const ctx = new Context(); try { - await ctx.plugin(agentCore, { workspaceContext: false }); + await mountAgentLoopTestDependencies(ctx); + await ctx.plugin(AgentLoop, { agents: [] }); await ctx.plugin(SubagentRuntime); await ctx.plugin(JsonlSessionPersistence, { root: storageRoot }); await new Promise((ready) => setTimeout(ready, 50)); diff --git a/packages/sdk/server/tests/plugin-apply.spec.ts b/packages/sdk/server/tests/plugin-apply.spec.ts index c5954e8dac..e51324c019 100644 --- a/packages/sdk/server/tests/plugin-apply.spec.ts +++ b/packages/sdk/server/tests/plugin-apply.spec.ts @@ -7,7 +7,8 @@ import { PassThrough, Writable } from 'node:stream' import { afterEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' -import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LlmAdapter } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' @@ -74,7 +75,8 @@ async function mountPlugin( } = {}, ): Promise { const ctx = new Context() - await ctx.plugin(agentCore, { workspaceContext: false }) + await mountAgentLoopTestDependencies(ctx) + await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(JsonlSessionPersistence, { root: storageDir }) await new Promise(resolve => setTimeout(resolve, 50)) await options.beforeServer?.(ctx) diff --git a/packages/sdk/server/tests/server.spec.ts b/packages/sdk/server/tests/server.spec.ts index 60c01bcdca..ac3af4f47b 100644 --- a/packages/sdk/server/tests/server.spec.ts +++ b/packages/sdk/server/tests/server.spec.ts @@ -8,10 +8,11 @@ import { tmpdir } from 'node:os' import { afterEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentRegistry, { type Agent, type AgentHandle } from '@deepseek-ai/dsh-agent' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' import SubagentRuntime, { type SubagentResult, type SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent' @@ -63,8 +64,9 @@ async function mockCompletionServer(): Promise<{ url: string; requests: unknown[ async function makeHarness(storageDir: string) { const ctx = new Context() + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(agentCore, { workspaceContext: false }) + await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentRuntime) await ctx.plugin(JsonlSessionPersistence, { root: storageDir }) await new Promise(resolve => setTimeout(resolve, 50)) diff --git a/packages/session/session-telemetry-otel/package.json b/packages/session/session-telemetry-otel/package.json index 41264d65f0..8f985cfd0b 100644 --- a/packages/session/session-telemetry-otel/package.json +++ b/packages/session/session-telemetry-otel/package.json @@ -52,7 +52,6 @@ "devDependencies": { "@deepseek-ai/cordis-plugin-logger-console": "workspace:^", "@deepseek-ai/cordis-plugin-loader": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-command-feedback": "workspace:^", diff --git a/packages/session/session-telemetry-otel/tests/fixtures/cordis.yml b/packages/session/session-telemetry-otel/tests/fixtures/cordis.yml deleted file mode 100644 index 2f4d6175b0..0000000000 --- a/packages/session/session-telemetry-otel/tests/fixtures/cordis.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Test-only composition: session-telemetry-otel through the real Loader/app -# path, exporting to the mock OTLP collector the driver starts (url via env). -# The redact-rule entry models a deployment mounting its own scrub rule on the -# session-telemetry/record waterfall — the seam itself ships no rules. -- id: logger-console - name: '@deepseek-ai/cordis-plugin-logger-console' - config: - colors: false - levels: - default: 3 - showTime: '' - -- id: cli-mock-llm - name: '../../../../test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts' - -- id: telemetry-redact-rule - name: './telemetry-redact-rule.ts' - -# Managed child-process groups required by the bash executor. -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: bash - name: '@deepseek-ai/dsh-bash-local' - -- id: session-telemetry-otel - name: '@deepseek-ai/dsh-session-telemetry-otel' - config: - mode: !!js process.env.DSH_TELEMETRY_E2E_MODE || 'FULL' - exporter: - url: !!js process.env.DSH_TELEMETRY_E2E_URL - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: cli-mock - model: cli-mock - cwd: !!js process.cwd() - persona: 'Test the session-telemetry-otel plugin.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: 'none' - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/session/session-telemetry-otel/tests/fixtures/driver.ts b/packages/session/session-telemetry-otel/tests/fixtures/driver.ts index 04205f4224..1bc0a32a12 100644 --- a/packages/session/session-telemetry-otel/tests/fixtures/driver.ts +++ b/packages/session/session-telemetry-otel/tests/fixtures/driver.ts @@ -9,9 +9,11 @@ import { writeFile } from 'node:fs/promises' import { createServer } from 'node:http' import { once } from 'node:events' -import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { gunzipSync } from 'node:zlib' +import { resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import { recordFeedback } from '@deepseek-ai/dsh-command-feedback' import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke' +import { bootProductionProfile } from '../../../../test-support/loader-smoke/tests/fixtures/production-profile.ts' const configPath = process.argv[2] if (configPath === undefined) throw new Error('session-telemetry-otel driver requires a config path') @@ -21,7 +23,9 @@ const server = createServer((request, response) => { const chunks: Buffer[] = [] request.on('data', chunk => chunks.push(chunk as Buffer)) request.on('end', () => { - captures.push(JSON.parse(Buffer.concat(chunks).toString())) + const body = Buffer.concat(chunks) + const decoded = request.headers['content-encoding'] === 'gzip' ? gunzipSync(body) : body + captures.push(JSON.parse(decoded.toString())) response.writeHead(200, { 'content-type': 'application/json' }).end('{}') }) }) @@ -30,8 +34,14 @@ await once(server, 'listening') const address = server.address() if (address === null || typeof address === 'string') throw new Error('collector has no port') process.env.DSH_TELEMETRY_E2E_URL = `http://127.0.0.1:${address.port}/v1/logs` +process.env.DSH_TELEMETRY_OTLP_URL = process.env.DSH_TELEMETRY_E2E_URL +process.env.DSH_TELEMETRY_MODE = process.env.DSH_TELEMETRY_E2E_MODE ?? 'FULL' -const ctx = await boot('telemetry-otel-e2e', resolveConfigPath(configPath, undefined)) +const ctx = await bootProductionProfile({ + binName: 'telemetry-otel-e2e', + profile: 'headless', + overlayPaths: [resolveConfigPath(configPath, undefined)], +}) try { // The fixture credential rides the model-visible user message; the exported // copy must scrub it while the canonical log keeps the original bytes. diff --git a/packages/session/session-telemetry-otel/tests/fixtures/telemetry.patch.yml b/packages/session/session-telemetry-otel/tests/fixtures/telemetry.patch.yml new file mode 100644 index 0000000000..88cb0c6540 --- /dev/null +++ b/packages/session/session-telemetry-otel/tests/fixtures/telemetry.patch.yml @@ -0,0 +1,37 @@ +# Test-only patch over the shipped headless profile. The driver points the +# production telemetry row at its mock collector through the public env vars. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: cli-mock + model: cli-mock + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- insert: + - id: logger-console + name: '@deepseek-ai/cordis-plugin-logger-console' + config: + colors: false + levels: + default: 3 + showTime: '' + + - id: cli-mock-llm + name: '../../../../test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts' + + # A deployment may register scrub rules on the telemetry waterfall; the + # shipped telemetry plugin deliberately owns none. + - id: telemetry-redact-rule + name: './telemetry-redact-rule.ts' diff --git a/packages/session/session-telemetry-otel/tests/loader-composition.e2e.ts b/packages/session/session-telemetry-otel/tests/loader-composition.e2e.ts index 16ae059100..ef70d9f463 100644 --- a/packages/session/session-telemetry-otel/tests/loader-composition.e2e.ts +++ b/packages/session/session-telemetry-otel/tests/loader-composition.e2e.ts @@ -18,7 +18,7 @@ const driver = fileURLToPath(new URL( import.meta.url, )) const configPath = fileURLToPath(new URL( - './fixtures/cordis.yml', + './fixtures/telemetry.patch.yml', import.meta.url, )) const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) @@ -75,7 +75,7 @@ function eventTypes(captures: OtlpCapture[]): string[] { : []) ?? []) } -describe('session-telemetry-otel through a real headless cordis.yml', () => { +describe('session-telemetry-otel through the production headless profile', () => { it('exports redacted ledger records to the collector while the canonical log keeps the secret', async () => { let output!: FixtureOutput const { stderr } = await runLoaderSmoke({ diff --git a/packages/subagent/subagent-acp/package.json b/packages/subagent/subagent-acp/package.json index 73be7d5910..b85c698c24 100644 --- a/packages/subagent/subagent-acp/package.json +++ b/packages/subagent/subagent-acp/package.json @@ -50,7 +50,6 @@ "@deepseek-ai/cordis": "workspace:^", "@deepseek-ai/cordis-plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", diff --git a/packages/subagent/subagent-acp/tests/fixtures/loader/acp.patch.yml b/packages/subagent/subagent-acp/tests/fixtures/loader/acp.patch.yml new file mode 100644 index 0000000000..1ec2245d7e --- /dev/null +++ b/packages/subagent/subagent-acp/tests/fixtures/loader/acp.patch.yml @@ -0,0 +1,40 @@ +# Test-only ACP provider patch over the shipped headless profile. The parent +# omits `cwd` so both scenarios exercise parent-session inheritance. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: mock + model: mock-delegate + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- id: tool-subagent + config: + provider: acp + toolName: subagent + maxDepth: 'provider-managed' + +- insert: + - id: mock-llm + name: './mock-delegating-llm.ts' + + - id: subagent-acp + name: '@deepseek-ai/dsh-subagent-acp' + config: + providerName: acp + command: !!js process.execPath + args: + - !!js process.env.DSH_TEST_MOCK_ACP_SERVER + permission: reject + env: !!js "process.env.DSH_TEST_ACP_MODE === 'diagnostic' ? { MOCK_TEXT: 'partial loader answer', MOCK_STOP: 'max_turn_requests' } : { MOCK_ECHO_CWD: '1' }" diff --git a/packages/subagent/subagent-acp/tests/fixtures/loader/cordis.yml b/packages/subagent/subagent-acp/tests/fixtures/loader/cordis.yml deleted file mode 100644 index ecc275f0a3..0000000000 --- a/packages/subagent/subagent-acp/tests/fixtures/loader/cordis.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Test-only composition: the ACP subagent backend on the real Loader/app path. -# The scripted model delegates once. The driving e2e selects either the cwd -# echo or a remote-limit diagnostic through DSH_TEST_ACP_MODE. `cwd` is -# deliberately omitted so both paths exercise parent-session inheritance. The -# child command path is machine-absolute and arrives through -# DSH_TEST_MOCK_ACP_SERVER. -- id: mock-llm - name: './mock-delegating-llm.ts' - -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -# The out-of-process ACP backend spawns its child through the subprocess seam. -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: subagent-acp - name: '@deepseek-ai/dsh-subagent-acp' - config: - providerName: acp - command: !!js process.execPath - args: - - !!js process.env.DSH_TEST_MOCK_ACP_SERVER - permission: reject - env: !!js "process.env.DSH_TEST_ACP_MODE === 'diagnostic' ? { MOCK_TEXT: 'partial loader answer', MOCK_STOP: 'max_turn_requests' } : { MOCK_ECHO_CWD: '1' }" - -- id: tool-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: acp - toolName: subagent - # ACP advertises no depthLimit: the child harness owns its own recursion - # budget, so the local numeric default cannot apply here. - maxDepth: 'provider-managed' - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: mock - model: mock-delegate - cwd: !!js process.cwd() - persona: 'Test ACP subagent cwd inheritance.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: 'none' - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/subagent/subagent-acp/tests/fixtures/loader/driver.ts b/packages/subagent/subagent-acp/tests/fixtures/loader/driver.ts index 18f7691d55..2d81a207c1 100644 --- a/packages/subagent/subagent-acp/tests/fixtures/loader/driver.ts +++ b/packages/subagent/subagent-acp/tests/fixtures/loader/driver.ts @@ -1,13 +1,18 @@ #!/usr/bin/env node /** Test driver: one delegation turn through a headless Loader composition. */ -import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke' +import { bootProductionProfile } from '../../../../../test-support/loader-smoke/tests/fixtures/production-profile.ts' const configPath = process.argv[2] if (configPath === undefined) throw new Error('acp-subagent cwd driver requires a config path') -const ctx = await boot('acp-subagent-cwd-e2e', resolveConfigPath(configPath, undefined)) +const ctx = await bootProductionProfile({ + binName: 'acp-subagent-cwd-e2e', + profile: 'headless', + overlayPaths: [resolveConfigPath(configPath, undefined)], +}) try { await runFixtureTurn(ctx, { task: 'delegate' }) } finally { diff --git a/packages/subagent/subagent-acp/tests/loader-composition.e2e.ts b/packages/subagent/subagent-acp/tests/loader-composition.e2e.ts index d6635030e4..2d93909fab 100644 --- a/packages/subagent/subagent-acp/tests/loader-composition.e2e.ts +++ b/packages/subagent/subagent-acp/tests/loader-composition.e2e.ts @@ -8,7 +8,7 @@ import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-l /** * Keyless REAL-composition coverage for the ACP provider through a test-only - * cordis.yml: parent-session cwd inheritance and model-visible failure detail + * patch file: parent-session cwd inheritance and model-visible failure detail * both cross the Loader, subprocess, ACP, tool, and persisted-session paths. * The with-key tier lives in subagent-acp.e2e.ts. */ @@ -18,7 +18,7 @@ const driver = fileURLToPath(new URL( import.meta.url, )) const configPath = fileURLToPath(new URL( - './fixtures/loader/cordis.yml', + './fixtures/loader/acp.patch.yml', import.meta.url, )) const mockServer = fileURLToPath(new URL('./mock-acp-server.ts', import.meta.url)) @@ -43,7 +43,7 @@ function toolResultText(events: SessionEvent[]): string { .join('') } -describe('ACP subagent cwd inheritance through a real cordis.yml', () => { +describe('ACP subagent cwd inheritance through the production profile', () => { it('runs the child in the parent session workspace and announces it as the ACP session cwd', async () => { let events: SessionEvent[] = [] let workspace = '' diff --git a/packages/subagent/subagent-claude-code/package.json b/packages/subagent/subagent-claude-code/package.json index 7f1eaf3a5b..bb8a733a36 100644 --- a/packages/subagent/subagent-claude-code/package.json +++ b/packages/subagent/subagent-claude-code/package.json @@ -57,7 +57,6 @@ "devDependencies": { "@deepseek-ai/cordis": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", @@ -71,6 +70,7 @@ "@deepseek-ai/dsh-subprocess": "workspace:^", "@deepseek-ai/dsh-subprocess-local": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", + "@deepseek-ai/dsh-tool-jobs": "workspace:^", "@deepseek-ai/dsh-tool-subagent": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^" } diff --git a/packages/subagent/subagent-claude-code/tests/fixtures/loader/claude-code.patch.yml b/packages/subagent/subagent-claude-code/tests/fixtures/loader/claude-code.patch.yml new file mode 100644 index 0000000000..720419869c --- /dev/null +++ b/packages/subagent/subagent-claude-code/tests/fixtures/loader/claude-code.patch.yml @@ -0,0 +1,58 @@ +# Named Claude instance patch over the shipped headless profile and the package's +# public bundle patch. No model or product process starts in this test. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- insert: + - id: fixture + name: './fixture.ts' + + - id: subagent-codex + name: '@deepseek-ai/dsh-subagent-codex' + + - id: subagent-claude-primary + name: '@deepseek-ai/dsh-subagent-claude-code' + config: + providerName: claude-primary + model: claude-primary-model + + - id: subagent-claude-secondary + name: '@deepseek-ai/dsh-subagent-claude-code' + config: + providerName: claude-secondary + model: claude-secondary-model + + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex + toolName: subagent_codex + backgroundMode: one-shot + maxDepth: 'provider-managed' + + - id: tool-subagent-claude-code + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-code + toolName: subagent_claude_code + backgroundMode: one-shot + maxDepth: 'provider-managed' + + - id: tool-subagent-claude-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-primary + toolName: subagent_claude_primary + backgroundMode: one-shot + maxDepth: 'provider-managed' + + - id: tool-subagent-claude-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-secondary + toolName: subagent_claude_secondary + backgroundMode: one-shot + maxDepth: 'provider-managed' diff --git a/packages/subagent/subagent-claude-code/tests/fixtures/loader/cordis.yml b/packages/subagent/subagent-claude-code/tests/fixtures/loader/cordis.yml deleted file mode 100644 index 6a64d24855..0000000000 --- a/packages/subagent/subagent-claude-code/tests/fixtures/loader/cordis.yml +++ /dev/null @@ -1,76 +0,0 @@ -# Test-only composition of Codex, the Bundle-supplied default Claude provider, -# and two named Claude instances. It never invokes a model or product process. -- id: fixture - name: './fixture.ts' - -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: subagent-codex - name: '@deepseek-ai/dsh-subagent-codex' - -- id: subagent-claude-primary - name: '@deepseek-ai/dsh-subagent-claude-code' - config: - providerName: claude-primary - model: claude-primary-model - -- id: subagent-claude-secondary - name: '@deepseek-ai/dsh-subagent-claude-code' - config: - providerName: claude-secondary - model: claude-secondary-model - -- id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: tool-subagent-claude-code - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-code - toolName: subagent_claude_code - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: tool-subagent-claude-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-primary - toolName: subagent_claude_primary - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: tool-subagent-claude-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-secondary - toolName: subagent_claude_secondary - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: mock - model: mock-delegate - cwd: !!js process.cwd() - persona: 'This composition test must not start a model turn.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/subagent/subagent-claude-code/tests/fixtures/loader/driver.ts b/packages/subagent/subagent-claude-code/tests/fixtures/loader/driver.ts index b9d91773c3..2af70367e4 100644 --- a/packages/subagent/subagent-claude-code/tests/fixtures/loader/driver.ts +++ b/packages/subagent/subagent-claude-code/tests/fixtures/loader/driver.ts @@ -1,9 +1,10 @@ #!/usr/bin/env node /** Inspect the public Claude Code Bundle composition without invoking the product. */ -import { boot, loadOverlayPatches, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import type {} from '@deepseek-ai/dsh-subagent' import type {} from '@deepseek-ai/dsh-tools' +import { bootProductionProfile } from '../../../../../test-support/loader-smoke/tests/fixtures/production-profile.ts' const configPath = process.argv[2] const bundlePatchPath = process.argv[3] @@ -12,16 +13,19 @@ if (configPath === undefined || bundlePatchPath === undefined) { } let starts = 0 -const ctx = await boot( - 'subagent-claude-code-loader-composition', - resolveConfigPath(configPath, undefined), - loadOverlayPatches('subagent-claude-code-loader-composition', bundlePatchPath), - (hostCtx) => { +const ctx = await bootProductionProfile({ + binName: 'subagent-claude-code-loader-composition', + profile: 'headless', + overlayPaths: [ + resolveConfigPath(bundlePatchPath, undefined), + resolveConfigPath(configPath, undefined), + ], + prepare: (hostCtx) => { hostCtx.on('subagent/start', () => { starts += 1 }) }, -) +}) try { const providerNames = [ @@ -70,7 +74,8 @@ try { .sort() process.stdout.write(`${JSON.stringify({ - registeredProviders: ctx.subagents.list().sort(), + registeredProviders: ctx.subagents.list().filter(providerName => + providerNames.includes(providerName as typeof providerNames[number])).sort(), providers, tools, jobTools, diff --git a/packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts b/packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts index 5b55a09a11..758b5e8775 100644 --- a/packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts +++ b/packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts @@ -12,7 +12,7 @@ const fixtureDir = fileURLToPath(new URL( import.meta.url, )) const driver = join(fixtureDir, 'driver.ts') -const configPath = join(fixtureDir, 'cordis.yml') +const configPath = join(fixtureDir, 'claude-code.patch.yml') const packageDir = fileURLToPath(new URL('..', import.meta.url)) const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) as { dsh?: { bundle?: { patch?: string } } diff --git a/packages/subagent/subagent-codex/package.json b/packages/subagent/subagent-codex/package.json index f608ae934c..e46db7d9ec 100644 --- a/packages/subagent/subagent-codex/package.json +++ b/packages/subagent/subagent-codex/package.json @@ -56,7 +56,6 @@ "@deepseek-ai/cordis": "workspace:^", "@deepseek-ai/cordis-plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", @@ -69,6 +68,7 @@ "@deepseek-ai/dsh-subprocess": "workspace:^", "@deepseek-ai/dsh-subprocess-local": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", + "@deepseek-ai/dsh-tool-jobs": "workspace:^", "@deepseek-ai/dsh-tool-subagent": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^" } diff --git a/packages/subagent/subagent-codex/tests/fixtures/loader/codex.patch.yml b/packages/subagent/subagent-codex/tests/fixtures/loader/codex.patch.yml new file mode 100644 index 0000000000..e55f0e37cc --- /dev/null +++ b/packages/subagent/subagent-codex/tests/fixtures/loader/codex.patch.yml @@ -0,0 +1,47 @@ +# Named Codex instance patch over the shipped headless profile and the package's +# public bundle patch. No model or product process starts in this test. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- insert: + - id: fixture + name: './fixture.ts' + + - id: subagent-codex-primary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-primary + model: codex-primary-model + + - id: subagent-codex-secondary + name: '@deepseek-ai/dsh-subagent-codex' + config: + providerName: codex-secondary + model: codex-secondary-model + + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex + toolName: subagent_codex + backgroundMode: one-shot + maxDepth: 'provider-managed' + + - id: tool-subagent-codex-primary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-primary + toolName: subagent_codex_primary + backgroundMode: one-shot + maxDepth: 'provider-managed' + + - id: tool-subagent-codex-secondary + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex-secondary + toolName: subagent_codex_secondary + backgroundMode: one-shot + maxDepth: 'provider-managed' diff --git a/packages/subagent/subagent-codex/tests/fixtures/loader/cordis.yml b/packages/subagent/subagent-codex/tests/fixtures/loader/cordis.yml deleted file mode 100644 index a898426ba1..0000000000 --- a/packages/subagent/subagent-codex/tests/fixtures/loader/cordis.yml +++ /dev/null @@ -1,65 +0,0 @@ -# Test-only composition of the Bundle-supplied default and two named Codex instances. -# The owning e2e applies the package's real patch and never invokes the model or Codex. -- id: fixture - name: './fixture.ts' - -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -- id: subprocess - name: '@deepseek-ai/dsh-subprocess-local' - -- id: subagent-codex-primary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-primary - model: codex-primary-model - -- id: subagent-codex-secondary - name: '@deepseek-ai/dsh-subagent-codex' - config: - providerName: codex-secondary - model: codex-secondary-model - -- id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: tool-subagent-codex-primary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-primary - toolName: subagent_codex_primary - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: tool-subagent-codex-secondary - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex-secondary - toolName: subagent_codex_secondary - backgroundMode: one-shot - maxDepth: 'provider-managed' - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: mock - model: mock-delegate - cwd: !!js process.cwd() - persona: 'This composition test must not start a model turn.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/subagent/subagent-codex/tests/fixtures/loader/driver.ts b/packages/subagent/subagent-codex/tests/fixtures/loader/driver.ts index 920377f691..ca9f46a60c 100644 --- a/packages/subagent/subagent-codex/tests/fixtures/loader/driver.ts +++ b/packages/subagent/subagent-codex/tests/fixtures/loader/driver.ts @@ -1,9 +1,10 @@ #!/usr/bin/env node /** Inspect the public Codex provider composition without invoking the product. */ -import { boot, loadOverlayPatches, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import type {} from '@deepseek-ai/dsh-subagent' import type {} from '@deepseek-ai/dsh-tools' +import { bootProductionProfile } from '../../../../../test-support/loader-smoke/tests/fixtures/production-profile.ts' const configPath = process.argv[2] const bundlePatchPath = process.argv[3] @@ -12,16 +13,19 @@ if (configPath === undefined || bundlePatchPath === undefined) { } let starts = 0 -const ctx = await boot( - 'subagent-codex-loader-composition', - resolveConfigPath(configPath, undefined), - loadOverlayPatches('subagent-codex-loader-composition', bundlePatchPath), - (hostCtx) => { +const ctx = await bootProductionProfile({ + binName: 'subagent-codex-loader-composition', + profile: 'headless', + overlayPaths: [ + resolveConfigPath(bundlePatchPath, undefined), + resolveConfigPath(configPath, undefined), + ], + prepare: (hostCtx) => { hostCtx.on('subagent/start', () => { starts += 1 }) }, -) +}) try { const providerNames = ['codex', 'codex-primary', 'codex-secondary'] as const @@ -64,7 +68,7 @@ try { .sort() process.stdout.write(`${JSON.stringify({ - providers: ctx.subagents.list(), + providers: providerNames.filter(providerName => ctx.subagents.getProvider(providerName) !== undefined), providerDetails: providers, tools, jobTools, diff --git a/packages/subagent/subagent-codex/tests/loader-composition.e2e.ts b/packages/subagent/subagent-codex/tests/loader-composition.e2e.ts index 37b5f47aae..3c474ebf9f 100644 --- a/packages/subagent/subagent-codex/tests/loader-composition.e2e.ts +++ b/packages/subagent/subagent-codex/tests/loader-composition.e2e.ts @@ -12,7 +12,7 @@ const fixtureDir = fileURLToPath(new URL( import.meta.url, )) const driver = join(fixtureDir, 'driver.ts') -const configPath = join(fixtureDir, 'cordis.yml') +const configPath = join(fixtureDir, 'codex.patch.yml') const packageDir = fileURLToPath(new URL('..', import.meta.url)) const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) as { dsh?: { bundle?: { patch?: string } } @@ -40,7 +40,7 @@ describe('Codex provider public Loader composition', () => { expect(stderr).toBe('') expect(JSON.parse(stdout)).toEqual({ - providers: ['codex-primary', 'codex-secondary', 'codex'], + providers: ['codex', 'codex-primary', 'codex-secondary'], providerDetails: [ { name: 'codex', diff --git a/packages/subagent/subagent-dsh-sdk/package.json b/packages/subagent/subagent-dsh-sdk/package.json index 1766a074fc..7f6d4f672c 100644 --- a/packages/subagent/subagent-dsh-sdk/package.json +++ b/packages/subagent/subagent-dsh-sdk/package.json @@ -50,7 +50,6 @@ "@deepseek-ai/cordis-plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-instructions": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", diff --git a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/child.cordis.yml b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/child.patch.yml similarity index 100% rename from packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/child.cordis.yml rename to packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/child.patch.yml diff --git a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/cordis.yml b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/cordis.yml deleted file mode 100644 index f0234ffdf7..0000000000 --- a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/cordis.yml +++ /dev/null @@ -1,65 +0,0 @@ -# Test-only composition: the SDK subagent backend on the real Loader/app path. -# The scripted model selects a child route; the child — a COMPLETE second -# harness runtime speaking stdio JSON-RPC — echoes the effective route and cwd, -# so dynamic routing and parent-session cwd inheritance are asserted keylessly. -# `cwd` is deliberately omitted — the inheritance branch under test. The child -# profile patch and isolated Harness home are machine-absolute, supplied by -# the driving e2e. -- id: mock-llm - name: './mock-delegating-llm.ts' - -- id: subagent - name: '@deepseek-ai/dsh-subagent' - -# providerName is omitted: the composition exercises the shipped default -# (`dsh-sdk`) through the real Loader. -- id: subagent-dsh-sdk - name: '@deepseek-ai/dsh-subagent-dsh-sdk' - config: - profile: sdk - patches: !!js JSON.parse(process.env.DSH_TEST_CHILD_PATCHES ?? '[]') - dshHome: !!js process.env.DSH_TEST_CHILD_HOME - provider: mock - model: mock-routed - env: - DSH_TELEMETRY_DISABLED: '1' - DSH_TEST_CHILD_FAILURE: !!js String(process.env.DSH_TEST_CHILD_FAILURE ?? '') - -- id: subagent-model-selection-settings - name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings' - config: - enabled: true - allowedModels: - - provider: mock - model: mock-routed - -- id: tool-subagent - name: './scoped-tool-subagent.ts' - config: - provider: dsh-sdk - toolName: subagent - agentOptions: - maxTokens: 777 - # The SDK backend advertises no depthLimit: the child harness owns its own - # recursion budget, so the local numeric default cannot apply here. - maxDepth: 'provider-managed' - -- id: agent-spine - name: '@deepseek-ai/dsh-agent-spine-demo' - config: - agents: - - id: main - provider: mock - model: mock-delegate - cwd: !!js process.cwd() - persona: 'Test SDK subagent cwd inheritance.' - workspaceContext: false - -- id: persistence - name: '@deepseek-ai/dsh-session-persistence-jsonl' - config: - root: './.sessions' - compression: 'none' - -- id: checkpoint-policy - name: '@deepseek-ai/dsh-session-checkpoint-policy' diff --git a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/driver.ts b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/driver.ts index 7ed378b8b5..d9ca8897a9 100644 --- a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/driver.ts +++ b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/driver.ts @@ -1,13 +1,18 @@ #!/usr/bin/env node /** Test driver: one delegation turn through a headless Loader composition. */ -import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke' +import { bootProductionProfile } from '../../../../../test-support/loader-smoke/tests/fixtures/production-profile.ts' const configPath = process.argv[2] if (configPath === undefined) throw new Error('sdk-subagent cwd driver requires a config path') -const ctx = await boot('sdk-subagent-cwd-e2e', resolveConfigPath(configPath, undefined)) +const ctx = await bootProductionProfile({ + binName: 'sdk-subagent-cwd-e2e', + profile: 'headless', + overlayPaths: [resolveConfigPath(configPath, undefined)], +}) try { await runFixtureTurn(ctx, { task: 'delegate' }) } finally { diff --git a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/dsh-sdk.patch.yml b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/dsh-sdk.patch.yml new file mode 100644 index 0000000000..5bd7fa4df3 --- /dev/null +++ b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/dsh-sdk.patch.yml @@ -0,0 +1,57 @@ +# Test-only SDK provider patch over the shipped headless profile. The child +# is a complete sdk profile; the parent omits `cwd` to exercise inheritance. +- id: headless-startup + disabled: true + +- id: headless-runner + disabled: true + +- id: agent-loop + config: + agents: + - id: main + provider: mock + model: mock-delegate + cwd: !!js process.cwd() + +- id: session-persistence-jsonl + config: + root: './.sessions' + compression: none + +- id: tool-subagent + disabled: true + +- insert: + - id: mock-llm + name: './mock-delegating-llm.ts' + + - id: subagent-model-selection-settings + name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings' + config: + enabled: true + allowedModels: + - provider: mock + model: mock-routed + + - id: test-tool-subagent + name: './scoped-tool-subagent.ts' + config: + provider: dsh-sdk + toolName: subagent + agentOptions: + maxTokens: 777 + maxDepth: 'provider-managed' + + # providerName is omitted to exercise the shipped `dsh-sdk` default. + - id: subagent-dsh-sdk + name: '@deepseek-ai/dsh-subagent-dsh-sdk' + config: + profile: sdk + patches: !!js JSON.parse(process.env.DSH_TEST_CHILD_PATCHES ?? '[]') + dshHome: !!js process.env.DSH_TEST_CHILD_HOME + provider: mock + model: mock-routed + env: + DSH_TELEMETRY_DISABLED: '1' + DSH_TEST_CHILD_FAILURE: !!js String(process.env.DSH_TEST_CHILD_FAILURE ?? '') diff --git a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/scoped-tool-subagent.ts b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/scoped-tool-subagent.ts index 79e53d1a35..90eff71d9c 100644 --- a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/scoped-tool-subagent.ts +++ b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/scoped-tool-subagent.ts @@ -13,7 +13,7 @@ export const inject = ['agents', 'subagentModelSelection'] * @param config - delegation-tool configuration forwarded into each Agent scope. */ export function apply(ctx: Context, config: Config): void { - ctx.on('agent/created', ({ agent }) => { + const install = (agent: NonNullable): void => { agent.ctx.plugin(ToolSubagent, { provider: config.provider, modelSelectionSettings: true, @@ -27,5 +27,7 @@ export function apply(ctx: Context, config: Config): void { ...(config.toolFilter === undefined ? {} : { toolFilter: config.toolFilter }), ...(config.maxDepth === undefined ? {} : { maxDepth: config.maxDepth }), }) - }) + } + ctx.on('agent/created', ({ agent }) => { install(agent) }) + for (const agent of ctx.agents.list()) install(agent) } diff --git a/packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts b/packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts index 55290ea7cd..5ea51ec585 100644 --- a/packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts +++ b/packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts @@ -1,6 +1,6 @@ /** * Keyless REAL-composition coverage for dynamic child routing and parent cwd - * inheritance across the SDK wire. A test-only cordis.yml boots through the + * inheritance across the SDK wire. A test-only patch boots through the * Loader, a scripted model selects provider/model/reasoning, tool config adds * maxTokens, and a COMPLETE second harness runtime echoes the effective route * and cwd. The same path also verifies model-visible child-failure diagnostics @@ -18,8 +18,8 @@ import { runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' const fixtureDir = new URL('./fixtures/loader/', import.meta.url) const driver = fileURLToPath(new URL('driver.ts', fixtureDir)) -const configPath = fileURLToPath(new URL('cordis.yml', fixtureDir)) -const childConfigPath = fileURLToPath(new URL('child.cordis.yml', fixtureDir)) +const configPath = fileURLToPath(new URL('dsh-sdk.patch.yml', fixtureDir)) +const childConfigPath = fileURLToPath(new URL('child.patch.yml', fixtureDir)) const childMockPath = fileURLToPath(new URL('child-mock-llm.ts', fixtureDir)) const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) @@ -52,7 +52,7 @@ async function childLaunch(failure = false): Promise<{ env: Record }> { const childHome = await mkdtemp(join(tmpdir(), 'dsh-sdk-subagent-home-')) - const childPatch = join(childHome, 'child.cordis.yml') + const childPatch = join(childHome, 'child.patch.yml') await writeFile(childPatch, (await readFile(childConfigPath, 'utf8')) .replace("'./child-mock-llm.ts'", JSON.stringify(pathToFileURL(childMockPath).href))) return { @@ -65,7 +65,7 @@ async function childLaunch(failure = false): Promise<{ } } -describe('SDK subagent routing and diagnostics through a real cordis.yml', () => { +describe('SDK subagent routing and diagnostics through the production profile', () => { it('runs the selected child route in the parent session workspace', async () => { const child = await childLaunch() let events: SessionEvent[] = [] diff --git a/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts b/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts index 1170c59f0b..aed1956d5a 100644 --- a/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts +++ b/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts @@ -38,7 +38,7 @@ import { const fakeRuntime = fileURLToPath(new URL('../../../sdk/client/tests/fake-runtime.ts', import.meta.url)) const existingPatch = fileURLToPath(new URL( - './fixtures/loader/child.cordis.yml', + './fixtures/loader/child.patch.yml', import.meta.url, )) const defaultCreateHarness = runInternals.createHarness.bind(runInternals) diff --git a/packages/test-support/loader-smoke/README.i18n.yaml b/packages/test-support/loader-smoke/README.i18n.yaml index 100ed34810..1e5305c761 100644 --- a/packages/test-support/loader-smoke/README.i18n.yaml +++ b/packages/test-support/loader-smoke/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/test-support/loader-smoke/README.md -README.md: 7e080273fd4ac9cb2745f6c722d3ed17057af208 -README.zh.md: 3973a0602a6451c64f4d145240a4e6f9a3a7f5d7 +README.md: 47083f07615d76b394bf21887799eb1841f304ac +README.zh.md: f8229686213aa4f2d4a4059d6e23367694346691 diff --git a/packages/test-support/loader-smoke/README.md b/packages/test-support/loader-smoke/README.md index 7e080273fd..47083f0761 100644 --- a/packages/test-support/loader-smoke/README.md +++ b/packages/test-support/loader-smoke/README.md @@ -43,6 +43,10 @@ const result = await runLoaderSmoke({ Set `expectedExitCode` when the scenario pins a designed failure surface — a one-shot turn ending in an error result — and a run that exits any other way, including succeeding, still fails the smoke. +### Testing a shipped profile + +Profile integration drivers use the repository-only `tests/fixtures/production-profile.ts` helper. It loads the named shipped profile and its bundle patches through `loadProfile`, reconciles the profile's module fallback, and passes the bundle patches followed by the test's `*.patch.yml` files to the root `cordis:include` mounted by `boot`. Those patches should contain only the test provider or model, isolated persistence paths, and subject-specific changes. Package-level unit tests that need an agent loop without profile integration mount `dsh-agent-loop-testkit` locally instead. + ### Driving a fixture turn `runFixtureTurn(ctx, options)` drives one task through exactly one configured root agent: it waits for the task to reach the durable inbox, forwards canonical events to your observer, flushes the session, and returns the final assistant text plus accumulated usage. Example-local drivers keep configuration, rendering, and assertion ownership. @@ -78,6 +82,7 @@ The harness is built on one separation: the smoke runs in a child process under | [`src/index.ts`](src/index.ts) | Mode resolver, `runLoaderSmoke` subprocess harness, options and result types | | [`src/agent-turn.ts`](src/agent-turn.ts) | `runFixtureTurn` direct-agent driver and result envelope | | [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; consuming test suites exercise the harness) | +| [`tests/fixtures/production-profile.ts`](tests/fixtures/production-profile.ts) | Repository-only shipped-profile composition helper for integration fixtures |
diff --git a/packages/test-support/loader-smoke/README.zh.md b/packages/test-support/loader-smoke/README.zh.md index 3973a0602a..f822968621 100644 --- a/packages/test-support/loader-smoke/README.zh.md +++ b/packages/test-support/loader-smoke/README.zh.md @@ -43,6 +43,10 @@ const result = await runLoaderSmoke({ 当场景固定一个设计好的失败面——例如一次性轮次以错误结果结束——时设置 `expectedExitCode`;以任何其他方式退出(包括成功退出)都会使冒烟测试失败。 +### 测试交付 profile + +Profile 集成 driver 使用仅限仓库内部的 `tests/fixtures/production-profile.ts` helper。它通过 `loadProfile` 加载指定的已交付 profile 及其组合包 patch,修复 profile 的模块回退,然后把组合包 patch 与测试 `*.patch.yml` 文件依次交给 `boot` 挂载的根 `cordis:include`。这些 patch 应只包含测试提供方或模型、隔离持久化路径及被测对象专用变更。只需要 agent loop 而不测试 profile 集成的包级单元测试改为在本地挂载 `dsh-agent-loop-testkit`。 + ### 驱动 fixture 轮次 `runFixtureTurn(ctx, options)` 让一项任务通过恰好一个已配置的根 agent:它等待任务进入持久收件箱,把规范事件转发给你的观察器,刷写会话,并返回最终 assistant 文本与累计用量。示例本地的 driver 继续负责配置、渲染与断言。 @@ -78,6 +82,7 @@ harness 建立在一个分离之上:冒烟测试在隔离世界中的子进程 | [`src/index.ts`](src/index.ts) | 模式解析器、`runLoaderSmoke` 子进程 harness、选项与结果类型 | | [`src/agent-turn.ts`](src/agent-turn.ts) | `runFixtureTurn` 直接 agent driver 与结果信封 | | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;消费它的测试套件会检验该 harness) | +| [`tests/fixtures/production-profile.ts`](tests/fixtures/production-profile.ts) | 仅限仓库内部、供集成 fixture 使用的交付 profile 组装 helper |
diff --git a/packages/test-support/loader-smoke/src/index.ts b/packages/test-support/loader-smoke/src/index.ts index ecdbfb9bbb..86317f20bc 100644 --- a/packages/test-support/loader-smoke/src/index.ts +++ b/packages/test-support/loader-smoke/src/index.ts @@ -188,7 +188,11 @@ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise { return { @@ -36,13 +40,13 @@ class CliMockAdapter extends LlmAdapter { const toolResult = options.messages.at(-1)?.content.find(block => block.type === 'tool-result') if (toolResult === undefined) { const reasoning = 'Inspecting the task before the tool call.' - const args = JSON.stringify({ command: 'printf CLI_TOOL_ROUND_TRIP', description: 'Prove the CLI tool round trip.' }) + const args = JSON.stringify({ command: SHELL_COMMAND, description: 'Prove the CLI tool round trip.' }) yield { type: 'block-start', index: 0, blockType: 'reasoning' } yield { type: 'reasoning-delta', index: 0, text: reasoning } yield { type: 'block-end', index: 0, block: { type: 'reasoning', text: reasoning } } yield { type: 'block-start', index: 1, blockType: 'tool-call' } - yield { type: 'tool-call-delta', index: 1, id: ToolCallId('cli-smoke-call'), name: 'bash', argumentsDelta: args } - yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('cli-smoke-call'), name: 'bash', arguments: args } } + yield { type: 'tool-call-delta', index: 1, id: ToolCallId('cli-smoke-call'), name: SHELL_TOOL, argumentsDelta: args } + yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('cli-smoke-call'), name: SHELL_TOOL, arguments: args } } yield { type: 'usage', usage: { inputTokens: 11, outputTokens: 3, cacheReadTokens: 2 } } yield { type: 'finish', reason: { kind: 'tool-calls' } } return diff --git a/packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts b/packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts index 8020129bfb..d617b98794 100644 --- a/packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts +++ b/packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts @@ -2,9 +2,10 @@ /** Snapshot-only Loader driver: stream one fixture turn as canonical JSONL. */ import type { Context } from '@deepseek-ai/cordis' -import { boot, installFailLoud, loadEnv, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' +import { installFailLoud, loadEnv, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke' import type { SessionEvent } from '@deepseek-ai/dsh-session' +import { bootProductionProfile } from './production-profile.ts' const NAME = 'headless-test-driver' const [configPath, ...taskParts] = process.argv.slice(2) @@ -16,7 +17,11 @@ const uninstallFailLoud = installFailLoud(NAME) let ctx: Context | undefined try { loadEnv(NAME) - ctx = await boot(NAME, resolveConfigPath(configPath, undefined)) + ctx = await bootProductionProfile({ + binName: NAME, + profile: 'headless', + overlayPaths: [resolveConfigPath(configPath, undefined)], + }) const result = await runFixtureTurn(ctx, { task: taskParts.join(' '), onEvent: (sessionId: string, event: SessionEvent) => { diff --git a/packages/test-support/loader-smoke/tests/fixtures/production-profile.ts b/packages/test-support/loader-smoke/tests/fixtures/production-profile.ts new file mode 100644 index 0000000000..7db8b1fcc6 --- /dev/null +++ b/packages/test-support/loader-smoke/tests/fixtures/production-profile.ts @@ -0,0 +1,101 @@ +/** Boot a test overlay over the shipped profile bundle layers. */ + +import { writeFile } from 'node:fs/promises' +import { createRequire } from 'node:module' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { Context } from '@deepseek-ai/cordis' +import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' +import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import { + boot, + healProfilesModuleFallback, + loadOverlayPatches, + loadProfile, + type ProfileLayer, +} from '@deepseek-ai/dsh-app-boot' + +const installAnchor = fileURLToPath(new URL('../../../../../apps/cli/package.json', import.meta.url)) + +function insertedPluginNames(entries: readonly EntryOptions[]): string[] { + return entries.flatMap((entry) => { + const children = entry.group && Array.isArray(entry.config) + ? insertedPluginNames(entry.config as EntryOptions[]) + : [] + return [entry.name, ...children] + }) +} + +function packageName(specifier: string): string | undefined { + if (specifier.startsWith('.') || specifier.startsWith('file:') || specifier.includes(':')) return undefined + const segments = specifier.split('/') + return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0] +} + +function overlayModuleLayers(path: string, patches: readonly PatchOptions[]): ProfileLayer[] { + const require = createRequire(path) + const packages = new Map() + const inserted = patches.flatMap(patch => patch.insert ?? []) + for (const specifier of insertedPluginNames(inserted)) { + const name = packageName(specifier) + if (name === undefined || packages.has(name)) continue + packages.set(name, dirname(require.resolve(`${name}/package.json`))) + } + return [...packages].map(([name, packageDir], index) => ({ + packageName: `test-overlay:${index}:${name}`, + packageDir, + patchPath: path, + patches: [], + })) +} + +/** Inputs for {@link bootProductionProfile}. */ +export interface ProductionProfileOptions { + /** Diagnostic prefix for profile and Loader failures. */ + readonly binName: string + /** Shipped profile whose bundle layers form the test tree. */ + readonly profile: string + /** Test overlay files, named `*.patch.yml`, applied above the shipped bundle layers in order. */ + readonly overlayPaths: readonly string[] + /** Optional host setup before any composed entry mounts. */ + readonly prepare?: (ctx: Context) => Promise | void +} + +/** + * Load and compose one shipped profile with narrow test overlays. + * + * The helper owns no Cordis rows or defaults. Each overlay owns only the + * test's provider, model, persistence directory, and subject-specific changes. + * @param options - profile, overlay files, and optional host setup. + * @returns the settled Loader root context. + */ +export async function bootProductionProfile(options: ProductionProfileOptions): Promise { + for (const path of options.overlayPaths) { + if (!path.endsWith('.patch.yml')) { + throw new Error(`${options.binName}: test overlay path must end in .patch.yml: ${path}`) + } + } + const profile = loadProfile(options.binName, options.profile, installAnchor, undefined, { userLayer: false }) + const rootConfig = join(profile.dir, 'cordis.yml') + await writeFile(rootConfig, '[]\n') + + const overlays = options.overlayPaths.map(path => loadOverlayPatches(options.binName, path)) + // Overlay-only packages need profile module visibility in plain-Node mode, + // but these synthetic layers do not contribute Cordis patches. + const moduleLayers = options.overlayPaths.flatMap((path, index) => ( + overlayModuleLayers(path, overlays[index] ?? []) + )) + await healProfilesModuleFallback({ + installAnchor, + profile: { ...profile, layers: [...profile.layers, ...moduleLayers] }, + }) + return boot( + options.binName, + rootConfig, + [ + ...profile.layers.flatMap(layer => layer.patches), + ...overlays.flat(), + ], + options.prepare, + ) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 828cd5971a..4fbeaa0ade 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,9 @@ importers: '@deepseek-ai/dsh-session-reference': specifier: workspace:^ version: link:../../packages/context/session-reference + '@deepseek-ai/dsh-shell-env': + specifier: workspace:^ + version: link:../../packages/shell/shell-env '@deepseek-ai/dsh-skill': specifier: workspace:^ version: link:../../packages/skill/skill @@ -343,9 +346,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../packages/core/agent - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../packages/examples/agent-spine-demo '@deepseek-ai/dsh-attachment-local': specifier: workspace:^ version: link:../../packages/attachment/attachment-local @@ -1394,18 +1394,36 @@ importers: packages/bundle/sdk-minimal: dependencies: - '@deepseek-ai/dsh-agent-spine-demo': + '@deepseek-ai/cordis-plugin-timer': specifier: workspace:^ - version: link:../../examples/agent-spine-demo + version: link:../../../vendor/timer + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-agent-loop': + specifier: workspace:^ + version: link:../../core/agent-loop '@deepseek-ai/dsh-deepseek-llm-api-extensions': specifier: workspace:^ version: link:../../llm/deepseek-llm-api-extensions '@deepseek-ai/dsh-fs-local': specifier: workspace:^ version: link:../../fs/fs-local + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../runtime-diagnostics/invariants + '@deepseek-ai/dsh-jobs-local': + specifier: workspace:^ + version: link:../../jobs/jobs-local + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm '@deepseek-ai/dsh-llm-deepseek': specifier: workspace:^ version: link:../../llm/llm-deepseek + '@deepseek-ai/dsh-llm-retry': + specifier: workspace:^ + version: link:../../llm/llm-retry '@deepseek-ai/dsh-plugin-package-inventory-deepseek': specifier: workspace:^ version: link:../../llm/plugin-package-inventory-deepseek @@ -1415,12 +1433,18 @@ importers: '@deepseek-ai/dsh-sandbox-policy': specifier: workspace:^ version: link:../../sandbox/sandbox-policy + '@deepseek-ai/dsh-scope': + specifier: workspace:^ + version: link:../../core/scope '@deepseek-ai/dsh-sdk-app': specifier: workspace:^ version: link:../sdk-app '@deepseek-ai/dsh-sdk-jsonrpc-server': specifier: workspace:^ version: link:../../sdk/server + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session '@deepseek-ai/dsh-session-log-deepseek': specifier: workspace:^ version: link:../../session/session-log-deepseek @@ -1430,9 +1454,15 @@ importers: '@deepseek-ai/dsh-session-projection': specifier: workspace:^ version: link:../../session/session-projection + '@deepseek-ai/dsh-session-title': + specifier: workspace:^ + version: link:../../session/session-title '@deepseek-ai/dsh-subprocess-local': specifier: workspace:^ version: link:../../subprocess/subprocess-local + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt '@deepseek-ai/dsh-terminal': specifier: workspace:^ version: link:../../terminal/terminal @@ -1448,13 +1478,13 @@ importers: '@deepseek-ai/dsh-tool-str-replace-editor': specifier: workspace:^ version: link:../../fs/tool-str-replace-editor + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools devDependencies: '@deepseek-ai/cordis': specifier: workspace:^ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../runtime-diagnostics/invariants packages/bundle/web-app: dependencies: @@ -4271,9 +4301,6 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:^ version: link:../../test-support/agent-loop-testkit - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../boot/app-boot @@ -4730,121 +4757,6 @@ importers: specifier: workspace:^ version: link:../../util/timeout - packages/examples/agent-spine-demo: - dependencies: - '@deepseek-ai/schemastery': - specifier: link:../../../vendor/schemastery - version: link:../../../vendor/schemastery - devDependencies: - '@deepseek-ai/cordis': - specifier: workspace:^ - version: link:../../../vendor/cordis - '@deepseek-ai/cordis-plugin-timer': - specifier: workspace:^ - version: link:../../../vendor/timer - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-agent-instructions': - specifier: workspace:^ - version: link:../../context/agent-instructions - '@deepseek-ai/dsh-agent-loop': - specifier: workspace:^ - version: link:../../core/agent-loop - '@deepseek-ai/dsh-bash-local': - specifier: workspace:^ - version: link:../../shell/bash-local - '@deepseek-ai/dsh-bash-sandbox': - specifier: workspace:^ - version: link:../../shell/bash-sandbox - '@deepseek-ai/dsh-fs-local': - specifier: workspace:^ - version: link:../../fs/fs-local - '@deepseek-ai/dsh-fs-observation-policy': - specifier: workspace:^ - version: link:../../fs/fs-observation-policy - '@deepseek-ai/dsh-fs-sandbox': - specifier: workspace:^ - version: link:../../fs/fs-sandbox - '@deepseek-ai/dsh-goal': - specifier: workspace:^ - version: link:../../goal/goal - '@deepseek-ai/dsh-goal-round-driver': - specifier: workspace:^ - version: link:../../goal/goal-round-driver - '@deepseek-ai/dsh-home-paths': - specifier: workspace:^ - version: link:../../util/home-paths - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../runtime-diagnostics/invariants - '@deepseek-ai/dsh-jobs': - specifier: workspace:^ - version: link:../../jobs/jobs - '@deepseek-ai/dsh-jobs-local': - specifier: workspace:^ - version: link:../../jobs/jobs-local - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-llm-retry': - specifier: workspace:^ - version: link:../../llm/llm-retry - '@deepseek-ai/dsh-sandbox-local': - specifier: workspace:^ - version: link:../../sandbox/sandbox-local - '@deepseek-ai/dsh-sandbox-policy': - specifier: workspace:^ - version: link:../../sandbox/sandbox-policy - '@deepseek-ai/dsh-scope': - specifier: workspace:^ - version: link:../../core/scope - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-projection': - specifier: workspace:^ - version: link:../../session/session-projection - '@deepseek-ai/dsh-session-title': - specifier: workspace:^ - version: link:../../session/session-title - '@deepseek-ai/dsh-shell-env': - specifier: workspace:^ - version: link:../../shell/shell-env - '@deepseek-ai/dsh-skill': - specifier: workspace:^ - version: link:../../skill/skill - '@deepseek-ai/dsh-skill-filesystem': - specifier: workspace:^ - version: link:../../skill/skill-filesystem - '@deepseek-ai/dsh-subprocess-local': - specifier: workspace:^ - version: link:../../subprocess/subprocess-local - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@deepseek-ai/dsh-tool-bash': - specifier: workspace:^ - version: link:../../shell/tool-bash - '@deepseek-ai/dsh-tool-fs': - specifier: workspace:^ - version: link:../../fs/tool-fs - '@deepseek-ai/dsh-tool-goal': - specifier: workspace:^ - version: link:../../goal/tool-goal - '@deepseek-ai/dsh-tool-jobs': - specifier: workspace:^ - version: link:../../jobs/tool-jobs - '@deepseek-ai/dsh-tool-skill': - specifier: workspace:^ - version: link:../../skill/tool-skill - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools - '@deepseek-ai/node-addon-landlock-run': - specifier: workspace:^ - version: link:../../../native/landlock-run/packages/entry - packages/experimental/agent-team: dependencies: '@deepseek-ai/dsh-brand': @@ -5744,9 +5656,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-bash-local': specifier: workspace:^ version: link:../../shell/bash-local @@ -7248,9 +7157,12 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent - '@deepseek-ai/dsh-agent-spine-demo': + '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ - version: link:../../examples/agent-spine-demo + version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../test-support/agent-loop-testkit '@deepseek-ai/dsh-attachment': specifier: workspace:^ version: link:../../attachment/attachment @@ -7748,9 +7660,6 @@ importers: '@deepseek-ai/cordis-plugin-logger-console': specifier: workspace:^ version: link:../../../vendor/logger-console - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-anonymous-user-id': specifier: workspace:^ version: link:../../identity/anonymous-user-id @@ -8743,9 +8652,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../boot/app-boot @@ -8813,9 +8719,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../boot/app-boot @@ -8855,6 +8758,9 @@ importers: '@deepseek-ai/dsh-timeout': specifier: workspace:^ version: link:../../util/timeout + '@deepseek-ai/dsh-tool-jobs': + specifier: workspace:^ + version: link:../../jobs/tool-jobs '@deepseek-ai/dsh-tool-subagent': specifier: workspace:^ version: link:../tool-subagent @@ -8886,9 +8792,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../boot/app-boot @@ -8925,6 +8828,9 @@ importers: '@deepseek-ai/dsh-timeout': specifier: workspace:^ version: link:../../util/timeout + '@deepseek-ai/dsh-tool-jobs': + specifier: workspace:^ + version: link:../../jobs/tool-jobs '@deepseek-ai/dsh-tool-subagent': specifier: workspace:^ version: link:../tool-subagent @@ -8953,9 +8859,6 @@ importers: '@deepseek-ai/dsh-agent-instructions': specifier: workspace:^ version: link:../../context/agent-instructions - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../boot/app-boot @@ -10444,9 +10347,6 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../packages/core/agent-loop - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../packages/examples/agent-spine-demo '@deepseek-ai/dsh-agent-tool-presentation': specifier: workspace:^ version: link:../../packages/core/agent-tool-presentation diff --git a/python/sdk-runtime/package.json b/python/sdk-runtime/package.json index 61dc113188..e901582d90 100644 --- a/python/sdk-runtime/package.json +++ b/python/sdk-runtime/package.json @@ -14,7 +14,6 @@ "@deepseek-ai/dsh-acp": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", - "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-agent-tool-presentation": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-attachment": "workspace:^", diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 0ea6043121..81e3f0b9dd 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -430,7 +430,7 @@ const SERVICE_ROLES: ServiceRole[] = [ pkg: 'agent-loop', title: 'Concrete loop driver', mode: 'bundle', - consumers: ['agent-spine-demo'], + consumers: ['base', 'sdk-minimal'], note: 'The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package.', }, { diff --git a/scripts/rescope-vendor.ts b/scripts/rescope-vendor.ts index 20fb00034f..f75e392a06 100644 --- a/scripts/rescope-vendor.ts +++ b/scripts/rescope-vendor.ts @@ -241,21 +241,6 @@ const EXACT_EDITS: readonly ExactEdit[] = [ replace: '| Directory | npm name | Upstream name | Version | Upstream repo | Commit |\n|---|---|---|---|---|---|', expect: 1, }, - { - // A plain fence listing the bundle's mounted tree: a bare token, no quotes. - id: 'agent-spine-demo-mounted-tree', - file: 'packages/examples/agent-spine-demo/README.md', - find: '@cordisjs/plugin-timer timer service', - replace: '@deepseek-ai/cordis-plugin-timer timer service', - expect: 1, - }, - { - id: 'agent-spine-demo-mounted-tree-zh', - file: 'packages/examples/agent-spine-demo/README.zh.md', - find: '@cordisjs/plugin-timer timer service', - replace: '@deepseek-ai/cordis-plugin-timer timer service', - expect: 1, - }, { // The root contract claimed vendored packages keep their upstream names. id: 'root-agents-vendored-name-contract', diff --git a/scripts/test-invariants.spec.ts b/scripts/test-invariants.spec.ts index 93e12652ba..1404072bae 100644 --- a/scripts/test-invariants.spec.ts +++ b/scripts/test-invariants.spec.ts @@ -155,7 +155,6 @@ describe('global test invariant host', () => { expect(usesManualInvariantTree('/repo/packages/core/session/tests/invariant.spec.ts')).toBe(true) expect(usesManualInvariantTree('/repo/packages/core/session/tests/request-invariant-hmr.spec.ts')).toBe(true) expect(usesManualInvariantTree('C:\\repo\\packages\\runtime-diagnostics\\invariants\\tests\\service.spec.ts')).toBe(true) - expect(usesManualInvariantTree('/repo/packages/examples/agent-spine-demo/tests/agent-core.spec.ts')).toBe(true) expect(usesManualInvariantTree('/repo/packages/core/session/tests/session.spec.ts')).toBe(false) }) diff --git a/scripts/test-invariants.ts b/scripts/test-invariants.ts index a3b96a90a3..9a42eb28d6 100644 --- a/scripts/test-invariants.ts +++ b/scripts/test-invariants.ts @@ -48,7 +48,6 @@ export const testInvariantCompanions: Readonly Promise> = { 'packages/client/ui-settings-plugin-inventory': { kind: 'none', reason: 'Browser-side inventory projection; registers nothing model-facing.' }, 'packages/client/locale': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, - 'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' }, 'packages/context/file-reference': { kind: 'indirect', reason: 'The discovery seam and grammar delegate model guidance to the composed provider.' }, 'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' }, 'packages/e2b/fs-e2b': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' }, diff --git a/scripts/verify-subsystem-pages.ts b/scripts/verify-subsystem-pages.ts index 2906616af1..870d578724 100644 --- a/scripts/verify-subsystem-pages.ts +++ b/scripts/verify-subsystem-pages.ts @@ -18,7 +18,6 @@ export const GROUPS_WITHOUT_SUBSYSTEM_PAGE: Readonly> = { acp: 'Protocol transport entry point; the server package README owns its interoperability contract.', boot: 'Shared application-bin boot library rather than a runtime subsystem.', bundle: 'Composition patch carriers whose mounted packages own all runtime contracts.', - examples: 'Non-product demonstration compositions whose mounted packages own all runtime contracts.', hooks: 'External hook-protocol bridges over existing interception points, not a new Harness service.', sdk: 'Out-of-process protocol and client packages whose package READMEs own the SDK contracts.', util: 'Low-level primitives whose business semantics remain with their consuming subsystems.', diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts index 3376dfdae0..50ececf76a 100644 --- a/snapshots/sdk/sdk.snapshot.ts +++ b/snapshots/sdk/sdk.snapshot.ts @@ -80,7 +80,7 @@ const dshSdkDiagnosticChildPatch = fileURLToPath(new URL( import.meta.url, )) const dshSdkChildConfig = fileURLToPath(new URL( - '../../packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/child.cordis.yml', + '../../packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/child.patch.yml', import.meta.url, )) diff --git a/tsconfig.base.json b/tsconfig.base.json index af94063c9c..ce5d70102c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -257,8 +257,6 @@ "@deepseek-ai/dsh-agent-loop-testkit/invariant": ["./packages/test-support/agent-loop-testkit/src/invariant.ts"], "@deepseek-ai/dsh-agent-presets": ["./packages/preset/agent-presets/src"], "@deepseek-ai/dsh-agent-presets/invariant": ["./packages/preset/agent-presets/src/invariant.ts"], - "@deepseek-ai/dsh-agent-spine-demo": ["./packages/examples/agent-spine-demo/src"], - "@deepseek-ai/dsh-agent-spine-demo/invariant": ["./packages/examples/agent-spine-demo/src/invariant.ts"], "@deepseek-ai/dsh-agent-tool-presentation": ["./packages/core/agent-tool-presentation/src"], "@deepseek-ai/dsh-agent-tool-presentation/invariant": ["./packages/core/agent-tool-presentation/src/invariant.ts"], "@deepseek-ai/dsh-anonymous-user-id": ["./packages/identity/anonymous-user-id/src"], diff --git a/tsconfig.host.json b/tsconfig.host.json index 76b512521f..49472def70 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -212,7 +212,6 @@ { "path": "./packages/context/agent-instructions" }, { "path": "./packages/core/agent-loop" }, { "path": "./packages/llm/llm-retry" }, - { "path": "./packages/examples/agent-spine-demo" }, { "path": "./packages/subprocess/subprocess" }, { "path": "./packages/subprocess/subprocess-local" }, { "path": "./packages/subprocess/win32-process" },