@khorsheed/dsh-local-agent-dsh-headless 0.1.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,97 @@
1
+ # dsh-local-agent-dsh-headless
2
+
3
+ English | [中文](README.md)
4
+
5
+ A headless sub-dsh runner for the local-agent family, in two modes: one-shot (default) runs one task in a caller-named dsh session, prints the final assistant text on stdout, and exits; resident (`--serve`) stays alive, takes turns over the family-internal stdio JSON-RPC wire, and pushes session events back — backing the parent `local-agent-dsh` provider's live driver (`live: true`). The sibling of the official `@deepseek-ai/dsh-headless` bundle, differing only in who owns the session id — you never install or mount it yourself; the parent `local-agent-dsh` provider auto-provisions it.
6
+
7
+ ## Features
8
+
9
+ - **Caller-supplied session id** — `--session-id <id>` creates a fresh session with exactly that id, `--resume <id>` continues it; the id travels as a flag, never through stdout.
10
+ - **One-shot semantics** — drives the task, prints the final assistant text, exits 0 on a completed turn and 1 otherwise.
11
+ - **Resident serve mode** — `--serve` switches the runner to a long-lived loop: `turn/start` drives one round, `session/event` streams events as they happen, `session/idle` closes the round (after the log flush), `turn/interrupt` lands as an in-process `Agent.cancel` graceful interrupt (the process survives, the session stays continuable), and `shutdown` / stdin EOF exit gracefully. The wire contract lives in `src/wire.ts`.
12
+ - **Isolated session store** — sub-dsh sessions live under their own scoped `$DSH_HOME` and never appear in the parent instance's session list.
13
+ - **Zero manual mounting** — the parent provider provisions the `headless-local-agent-dsh` sub-profile for you; nothing to mount by hand.
14
+
15
+ ## Install
16
+
17
+ This bundle is never installed or mounted on its own — installing the parent harness brings it in:
18
+
19
+ ```sh
20
+ dsh plugin --profile web add @khorsheed/dsh-local-agent-dsh
21
+ ```
22
+
23
+ There is nothing to uninstall separately; the bundle has no presence in any interactive profile.
24
+
25
+ ## Usage
26
+
27
+ Normally the parent provider spawns this bundle; direct invocation works too:
28
+
29
+ ```sh
30
+ dsh --profile headless-local-agent-dsh --session-id 6ba7... "run the tests" # fresh
31
+ dsh --profile headless-local-agent-dsh --resume 6ba7... "run the rest" # resume
32
+ dsh --profile headless-local-agent-dsh --serve # resident live-driver mode (stdio wire)
33
+ ```
34
+
35
+ With neither flag the runner generates its own `session-<uuid>` id (the official headless behavior), so it stays a drop-in replacement for one-shot use. `--serve` takes neither a task nor session flags — turns and session ids all travel over the wire.
36
+
37
+ ### `--model <provider/model>`
38
+
39
+ Overrides this launch's model selection:
40
+
41
+ ```bash
42
+ dsh --profile headless-local-agent-dsh --model deepseek-official/deepseek-v4-pro --session-id 6ba7... "run the tests"
43
+ ```
44
+
45
+ - Absent means the sub-instance's own `agentDefaultModel` selection — byte for byte the behavior before the flag existed.
46
+ - It splits at the FIRST `/` into provider and model, so a model id containing one survives; a bare id (no `/`) names the model only and keeps the instance's provider. A leading or trailing slash is not a split point — that would produce an empty half the agent cannot route — so the whole value is the model.
47
+ - Everything else on the selection is carried through (the reasoning effort in particular): a model swap is not a config reset.
48
+ - It is orthogonal to both modes: a one-shot launch binds that round, and `--serve` binds EVERY session the resident process hosts — a runtime's model is a process fact, which is exactly why the parent refuses a per-delegation model on the live path.
49
+
50
+ The parent `local-agent-dsh` provider supplies it automatically: the harness's `model` plugin-config key, or one delegation's own `DelegationCallOptions.model`, both land as this flag.
51
+
52
+ ## Compatibility
53
+
54
+ - npm release line (`@deepseek-ai/dsh@0.1.2-rc.1`): ✅ full — baseline moved to the 0.1.2-rc.1 API surface (single-arm 0.1.2 API consumption; the 0.1.1-rc.2 runtime arm is retired), full build+test green; minHost moves up to 0.1.2-rc.1 — older hosts stay on the previous release line.
55
+ - source line (deepseek-harness master): ✅ (verifiedHost: 0.1.2-rc.1)
56
+
57
+ ## Known Limitations
58
+
59
+ - **Never add this bundle to an interactive profile's `bundles`** — its sub-profile-only patch rows (persona override, `hmr` off, `tools` mode, `ptc-runtime` insert, member-bridge MCP) collide with an interactive composition and leak overrides into real user sessions. The package **declares no `dsh.bundle`**, so a hand-added bundles row fails loud at boot in `loadProfile` ("declares no dsh.bundle") — that is the gate.
60
+ - **`dsh plugin add` cannot auto-mount it, and need not** — the package used to declare `dsh.bundle`, and reconcilePlugins auto-mounts every `dsh.bundle`-declaring direct dependency into the composition's layer stack: both the 2026-08-23 P0 (duplicate `code-runtime` in the prod web profile) and its 2026-09-03 G3 recurrence were exactly that mount. The declaration is gone (T6); installing the package directly now earns only a "plain dependency" warning and mounts nothing — but it is also unnecessary: a **transitive** install through `@khorsheed/dsh-local-agent-dsh` suffices, and the parent provider auto-provisions. The package's own invariant still fails loud when it detects a web composition (the `webStartup` service).
61
+ - Sub-dsh sessions never appear in the parent instance's session list (separate scoped-home store).
62
+ - No other `local-agent` family bundles belong in this composition — nothing here spawns another dsh.
63
+ - Patch edits must be boot-verified before landing (`dsh preflight` or one real sub-dsh launch): the `!!js` tag is scalar-only, and a mistagged collection fails at profile boot.
64
+
65
+ ## How it works
66
+
67
+ <details>
68
+ <summary>Internals (click to expand)</summary>
69
+
70
+ The local-agent family needs to continue the *same* dsh conversation across delegations: the parent provider generates one uuid and passes it on every round — the fresh round creates the sub-dsh session with it, each resume round continues exactly that session.
71
+
72
+ The bundle's patch rides over `dsh-base` in its own profile (e.g. `headless-local-agent-dsh`):
73
+
74
+ ```yaml
75
+ - id: local-agent-dsh-headless-startup
76
+ name: '@khorsheed/dsh-local-agent-dsh-headless/startup'
77
+
78
+ - id: local-agent-dsh-headless-runner
79
+ name: '@khorsheed/dsh-local-agent-dsh-headless'
80
+ inject: [localAgentDshHeadlessStartup]
81
+ config:
82
+ task: !!js ctx.localAgentDshHeadlessStartup.task
83
+ sessionId: !!js ctx.localAgentDshHeadlessStartup.sessionId
84
+ resumeSessionId: !!js ctx.localAgentDshHeadlessStartup.resumeSessionId
85
+ serve: !!js ctx.localAgentDshHeadlessStartup.serve ?? false
86
+ ```
87
+
88
+ The startup provider parses the task positional plus the mutually exclusive `--session-id` / `--resume` flags (or `--serve`) and publishes the invocation; in one-shot mode the runner creates or resumes that session via `agents.create` / `agents.resume`, drives the task, prints the final assistant text, and exits, while serve mode switches to the resident wire loop (`src/serve.ts`). The sub-profile itself is provisioned at runtime by the parent provider (`provisionDshSubProfile`) under the dsh harness's scoped home: the manifest lists only `@deepseek-ai/dsh-base`, and this bundle's patch is copied byte-for-byte from the package's `cordis.patch.yml` into that profile's own patch layer (the bundle declares no `dsh.bundle`, see Known Limitations), plus one symlink resolving this bundle for the loader's insert rows.
89
+
90
+ **Preset roster (optional).** When the sub-profile's patch carries an extra `@deepseek-ai/dsh-agent-presets` layer — written by the parent provider when the scope declares a preset — the agent loader joins it inside `setup`, before the agent is published, so the preset's tools and prompt sections exist before the first prompt assembly. Without the layer it does nothing: the model-facing rows stay in the host plane and the agent reads the global layer, byte for byte the behavior before this existed. A roster that REFUSES (unknown preset id, broken composition) is not degraded past: the mount rejects, the agent creation rolls back, and the launch fails naming the preset — a sub-dsh that silently ran the global layer after being told to run preset X would attribute the round to a capability face it never had.
91
+
92
+
93
+ </details>
94
+
95
+ ## Development
96
+
97
+ Part of the [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo (`packages/local-agent-dsh-headless`). Issues and contributions welcome there.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record: the git blob hash of each side as of the
2
+ # last confirmed-consistent state. Both languages carry equal authority; after
3
+ # editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/local-agent-dsh-headless/README.en.md
5
+ packages/local-agent-dsh-headless/README.en.md: f15f1ef82b5c98476c35ee323728ccd4fc6378b7
6
+ packages/local-agent-dsh-headless/README.md: fe74b4651a99d4f0aeccd695ee9c6a4f7bddc4a4
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # dsh-local-agent-dsh-headless
2
+
3
+ [English](README.en.md) | 中文
4
+
5
+ local-agent 家族的 headless 子 dsh 运行器,两种模式:一次性(默认)在一个由调用方命名的 dsh 会话里跑一个任务,把最终助手文本打到 stdout,然后进程退出;常驻(`--serve`)驻留进程,经家族内部 stdio JSON-RPC wire 接收 turn 并回推会话事件,支撑父侧 `local-agent-dsh` 的长驻驱动(`live: true`)。它是官方 `@deepseek-ai/dsh-headless` bundle 的兄弟版本,唯一区别是会话 id 归谁所有——你永远不需要自己安装或挂载它;父侧 `local-agent-dsh` provider 会在运行时自动 provision。
6
+
7
+ ## 特性
8
+
9
+ - **调用方提供的会话 id**——`--session-id <id>` 用该确切 id 新建会话,`--resume <id>` 续接该会话;id 以调用参数传递,绝不经过 stdout。
10
+ - **一次性语义**——驱动任务、打印最终助手文本,完成轮退出 0、否则退出 1。
11
+ - **常驻 serve 模式**——`--serve` 把 runner 切成长驻循环:`turn/start` 驱动一轮、`session/event` 逐事件推流、`session/idle` 关轮(落盘 flush 之后)、`turn/interrupt` 落地为进程内 `Agent.cancel` 优雅中断(进程不死、会话可续)、`shutdown`/stdin EOF 优雅退出。wire 契约见 `src/wire.ts`。
12
+ - **隔离的会话存储**——子 dsh 会话存在自己的 scoped `$DSH_HOME` 下,绝不会出现在父实例的会话列表里。
13
+ - **零手工挂载**——`headless-local-agent-dsh` 子 profile 由父级 provider 自动 provision,任何地方都不需要手工挂载。
14
+
15
+ ## 安装
16
+
17
+ 本 bundle 从不单独安装或挂载——安装父级 harness 即一并带入:
18
+
19
+ ```sh
20
+ dsh plugin --profile web add @khorsheed/dsh-local-agent-dsh
21
+ ```
22
+
23
+ 没有需要单独卸载的东西;本 bundle 在任何交互式 profile 里都不存在。
24
+
25
+ ## 用法
26
+
27
+ 通常由父级 provider 拉起本 bundle;直接调用也可以:
28
+
29
+ ```sh
30
+ dsh --profile headless-local-agent-dsh --session-id 6ba7... "run the tests" # fresh
31
+ dsh --profile headless-local-agent-dsh --resume 6ba7... "run the rest" # resume
32
+ dsh --profile headless-local-agent-dsh --serve # 常驻 live-driver 模式(stdio wire)
33
+ ```
34
+
35
+ 两个 flag 都不给时,runner 自生成 `session-<uuid>` id(官方 headless 行为),因此仍是一次性使用的即插即用替代。`--serve` 不接受 task 与 session flag——turn 与会话 id 全部走 wire。
36
+
37
+ ### `--model <provider/model>`
38
+
39
+ 覆盖本次启动的模型选择:
40
+
41
+ ```bash
42
+ dsh --profile headless-local-agent-dsh --model deepseek-official/deepseek-v4-pro --session-id 6ba7... "run the tests"
43
+ ```
44
+
45
+ - 不给 = 用子实例自己 `agentDefaultModel` 的当前选择,与这个 flag 出现之前逐字节相同。
46
+ - 按**第一个** `/` 切分成 provider 与 model,所以模型 id 里再带斜杠也不会被切坏;只写模型名(没有 `/`)则只换模型、沿用实例的 provider。开头或结尾的斜杠不算切分点——那会切出一个空的一半,agent 路由不了——整个值当模型名。
47
+ - 选择的其余部分原样带过(尤其是 reasoning effort):换模型不是重置配置。
48
+ - 与两种模式都正交:一次性轮次绑定这一轮,`--serve` 绑定这个常驻进程托管的**每一个**会话——runtime 的模型是进程事实,这正是父侧拒绝在长驻路径上按次给模型的原因。
49
+
50
+ 父侧 `local-agent-dsh` provider 自动带上它:harness 的 `model` 插件配置键、或某一次委派自带的 `DelegationCallOptions.model`,都落成这个 flag。
51
+
52
+ ## Compatibility
53
+
54
+ - npm 发布线(`@deepseek-ai/dsh@0.1.2-rc.1`):✅ 完整——基线迁移至 0.1.2-rc.1 API 面(单臂消费 0.1.2 API,0.1.1-rc.2 运行臂已退役),全量构建测试通过;minHost 前移至 0.1.2-rc.1,旧宿主请停留在旧发布线。
55
+ - 源码线(deepseek-harness master):✅(verifiedHost: 0.1.2-rc.1)
56
+
57
+ ## 已知限制
58
+
59
+ - **绝不要把这个 bundle 加进交互式 profile 的 `bundles`**——它的子 profile 专属 patch 行(persona 覆盖、`hmr` 禁用、`tools` mode、`ptc-runtime` insert、member-bridge MCP)会撞交互式组合,并把覆盖泄漏进真实用户会话。本包**不声明 `dsh.bundle`**,因此手工加的 bundles 行在 boot 的 `loadProfile` 处直接 fail loud("declares no dsh.bundle"),这正是一道闸。
60
+ - **它不会被 `dsh plugin add` 自动挂载,也无需如此**——本包曾声明 `dsh.bundle`,而 reconcilePlugins 会把声明该字段的直接依赖自动挂进组合的 layer 栈:2026-08-23 P0(prod web profile 撞 duplicate `code-runtime`)与 2026-09-03 G3 复发都是这一挂。声明已撤(T6),装成直接依赖只会得到一条 "plain dependency" 警告、不挂载——但也没有必要:经 `@khorsheed/dsh-local-agent-dsh` **传递**安装即可,父级 provider 会自动 provision。包内不变量对挂进 web 组合(检出 `webStartup` 服务)依旧 fail loud。
61
+ - 子 dsh 会话绝不会出现在父实例的会话列表里(独立的 scoped-home 存储)。
62
+ - 此 composition 里不装任何其他 `local-agent` 家族 bundle——这里没有任何东西再 spawn 一个 dsh。
63
+ - patch 改动落地前必须做启动级验证(`dsh preflight` 或真实拉起一次子 dsh):`!!js` 标签只支持标量,误标集合会在 profile 启动时直接失败。
64
+
65
+ ## 实现原理
66
+
67
+ <details>
68
+ <summary>内部结构(点击展开)</summary>
69
+
70
+ local-agent 家族需要在多次委派之间续接**同一个** dsh 对话:父级 provider 生成一个 uuid,并在每一轮传同一个值——fresh 轮用它创建子 dsh 会话,之后的 resume 轮恰好续接该会话。
71
+
72
+ 本 bundle 的 patch 在自己的 profile 之上叠加 `dsh-base`(如 `headless-local-agent-dsh`):
73
+
74
+ ```yaml
75
+ - id: local-agent-dsh-headless-startup
76
+ name: '@khorsheed/dsh-local-agent-dsh-headless/startup'
77
+
78
+ - id: local-agent-dsh-headless-runner
79
+ name: '@khorsheed/dsh-local-agent-dsh-headless'
80
+ inject: [localAgentDshHeadlessStartup]
81
+ config:
82
+ task: !!js ctx.localAgentDshHeadlessStartup.task
83
+ sessionId: !!js ctx.localAgentDshHeadlessStartup.sessionId
84
+ resumeSessionId: !!js ctx.localAgentDshHeadlessStartup.resumeSessionId
85
+ serve: !!js ctx.localAgentDshHeadlessStartup.serve ?? false
86
+ ```
87
+
88
+ startup provider 解析 task 位置参数与互斥的 `--session-id` / `--resume`(或 `--serve`)并发布调用;一次性模式下 runner 通过 `agents.create` / `agents.resume` 创建或续接该会话、驱动任务、打印最终助手文本并退出,serve 模式下转入常驻 wire 循环(`src/serve.ts`)。子 profile 本身由父级 provider 在运行时 provision(`provisionDshSubProfile`)到 dsh harness 的 scoped home 下:manifest 只列 `@deepseek-ai/dsh-base`,本 bundle 的 patch 从包内 `cordis.patch.yml` 逐字节拷贝为该 profile 自己的 patch 层(本包不声明 `dsh.bundle`,见已知限制),另加一条解析本 bundle 的符号链接供 loader 解析 insert 行。
89
+
90
+ **preset roster(可选)。** 如果子 profile 的 patch 多带一层 `@deepseek-ai/dsh-agent-presets`(父级 provider 在这个 scope 声明了 preset 时写进去的),agent loader 会在 `setup` 里 join 它——在 agent 发布之前,因此 preset 的工具与提示词片段在第一次组装提示词之前就在位。没有这一层就什么也不做,模型可见的行留在宿主面、agent 从全局层读,与本行为出现之前逐字节相同。roster **拒绝**(preset id 不存在、composition 坏了)不会被降级绕过:mount 抛错,agent 创建回滚,启动带着 preset 名失败——一个被要求跑 preset X 却悄悄跑了全局层的子 dsh,会把这一轮归到它从没有过的能力面上。
91
+
92
+
93
+ </details>
94
+
95
+ ## 开发
96
+
97
+ 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo(`packages/local-agent-dsh-headless`)。问题与贡献请移步该仓库。
@@ -0,0 +1,81 @@
1
+ # The dsh-local-agent-dsh-headless bundle patch: the sub-dsh direct-Agent app
2
+ # over dsh-base, sibling to the official @deepseek-ai/dsh-headless bundle but
3
+ # caller-session-aware. The local-agent-dsh provider on the parent side spawns
4
+ #
5
+ # dsh --profile headless-local-agent-dsh --session-id <uuid> "<task>" (fresh, one-shot)
6
+ # dsh --profile headless-local-agent-dsh --resume <uuid> "<task>" (resume, one-shot)
7
+ # dsh --profile headless-local-agent-dsh --serve (resident live driver)
8
+ #
9
+ # Any of the three may carry `--model <provider/model>`, which overrides the
10
+ # instance's default model selection for that launch.
11
+ #
12
+ # The startup provider parses the flags; one-shot mode creates (or resumes)
13
+ # exactly that session through the core registry, drives the task, prints the
14
+ # final assistant text, and exits; serve mode stays resident and drives turns
15
+ # over the family-internal stdio wire (src/wire.ts), with runtime-level
16
+ # interrupt via the in-process Agent.cancel. Like the official headless
17
+ # bundle, this composition mounts no Host, HTTP, or browser layer. The sub-dsh
18
+ # session id comes from the caller, never from stdout parsing — the official
19
+ # headless stdout stays format-pure.
20
+
21
+ - id: system-prompt
22
+ config:
23
+ persona: >-
24
+ You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.
25
+
26
+ # The shared module-reload HMR row stays off; the launcher's watch-only
27
+ # fallback still keeps the user patch layers live until the run exits.
28
+ - id: hmr
29
+ disabled: true
30
+
31
+ - id: tools
32
+ config:
33
+ # Keep the same temporary process-wide Code Mode opt-in as the Web surface.
34
+ mode: !!js process.env.DSH_TOOLS_MODE
35
+
36
+ - insert:
37
+ # Code Mode is a core execution capability, not a Web component.
38
+ - id: ptc-runtime
39
+ name: '@deepseek-ai/dsh-ptc-runtime-node'
40
+
41
+ - id: local-agent-dsh-headless-startup
42
+ name: '@khorsheed/dsh-local-agent-dsh-headless/startup'
43
+
44
+ # Reads its invocation from the startup provider above. `serve` switches
45
+ # the runner from the one-shot task to the resident live-driver loop
46
+ # (`--serve`; turns arrive over stdio, see src/wire.ts).
47
+ - id: local-agent-dsh-headless-runner
48
+ name: '@khorsheed/dsh-local-agent-dsh-headless'
49
+ inject: [localAgentDshHeadlessStartup]
50
+ config:
51
+ task: !!js ctx.localAgentDshHeadlessStartup.task
52
+ sessionId: !!js ctx.localAgentDshHeadlessStartup.sessionId
53
+ resumeSessionId: !!js ctx.localAgentDshHeadlessStartup.resumeSessionId
54
+ serve: !!js ctx.localAgentDshHeadlessStartup.serve ?? false
55
+ # `--model`: the launch's model override (provider/model). Absent
56
+ # leaves the instance's own default selection in place.
57
+ model: !!js ctx.localAgentDshHeadlessStartup.model
58
+ effort: !!js ctx.localAgentDshHeadlessStartup.effort
59
+
60
+ # The member bridge (local-agent member channel, M3): the parent provider
61
+ # hands the per-run socket path, token, and bridge entry through the
62
+ # sub-dsh spawn env (the DSH_MEMBER_* names survive only via the explicit
63
+ # env layer — see dsh-cli-provider.ts, MEMBER_BRIDGE_ENTRY_ENV is the
64
+ # family-internal contract). @deepseek-ai/dsh-mcp-client resolves from the
65
+ # installation's dependency closure (linked into the scoped home's
66
+ # profiles/node_modules fallback at boot). Absent env (a core predating
67
+ # the member channel) yields an unconnectable stdio server; with
68
+ # failOnStartupError off the run simply proceeds without member_message.
69
+ - id: member-bridge
70
+ name: '@deepseek-ai/dsh-mcp-client'
71
+ config:
72
+ serverName: dsh-member
73
+ transport: stdio
74
+ command: !!js process.execPath
75
+ args:
76
+ - !!js process.env.DSH_MEMBER_BRIDGE_ENTRY || ''
77
+ cwd: !!js process.cwd()
78
+ env:
79
+ DSH_MEMBER_SOCKET: !!js process.env.DSH_MEMBER_SOCKET ?? ''
80
+ DSH_MEMBER_TOKEN: !!js process.env.DSH_MEMBER_TOKEN ?? ''
81
+ failOnStartupError: false