@khorsheed/dsh-local-agent 0.2.0-rc.3 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -1,8 +1,26 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.2.0(2026-10-26)
4
+
5
+ rc.3 之后到正式版的变化(协作间里的 CLI 成员能力为主,详见 room 0.2.3):
6
+
7
+ - **CLI 成员能用所在模式的插件工具**:邀请 Codex / Claude Code / Kimi 成员时选了模式,成员就能调用这个模式的插件工具(经成员桥接的 `dsh-member` MCP 服务列出与转发);工具跑在成员自己的子会话上,模式的面板和插件状态都归这个成员,子会话记下所选模式,别的模式的页签随之隐藏。没有模式或宿主不支持时不多给工具;local-agent 家族自己的委派工具不转给 CLI;刚挂上的模式会等它的工具注册稳定(至多 3 秒)再列出。
8
+ - **模式技能装进成员自己的技能目录**:模式带的技能(如剪辑)在每次列工具时写进该 CLI 的技能目录(`<harness home>/skills/dsh-<名>/SKILL.md`),Codex、Claude Code 与 Kimi 都能读到。
9
+ - **模式工具的确认在协作间里问**:CLI 成员调用需要确认的模式工具时,确认请求交给协作间,由你点「允许一次 / 拒绝」。
10
+ - **CLI 成员能向你提问、向别的会话求助**:成员桥接提供 `ask_user_question`(在协作间里出选项卡,你点选或打字回答)与 `room_help`;Codex / Claude Code 的桥接调用最长可等 30 分钟。
11
+ - **成员的实时输出和宿主自己的流式一样**:思考显示成宿主的思考行(一行滚动最新段落、展开看全文),回答直接显示正文,不再在原始文本上套「思考中/正在生成」标签——成员子会话与协作间里一致。
12
+ - **修复续跑的成员第二轮报「identity changed: model」**:Claude 成员续跑时绑定的模型与首轮不一致;现在原样沿用首轮绑定的模型。
13
+ - **修复 CLI 成员调用模式工具全部失败**:子会话同步与宿主的路由缓冲重复写入,导致工具执行的检查点落盘报「append seq mismatch」;现在先排空缓冲、只补缺的部分,并能自愈。
14
+ - **修复重启打断的一轮让成员再也接不上**:宿主在成员一轮中途重启后,日志里那一轮没有收尾,下一轮起头即被判为损坏(Context Lens 也打不开);重新接上成员时先补上中断收尾。
15
+ - **修复 0.1.5 宿主上实时节点的思考图标**:改用包内生成的图标模块。
16
+ - **README 重写**:改为大白话说明,完整参考移到 `docs/`。
17
+ - **HarnessModelPicker 新增 `fields` 形态**:以带标签的表单下拉框(模型,以及可选的思考强度)呈现,替代下钻菜单,供其它字段都是下拉框的对话框使用(协作间邀请对话框首用)。
18
+ - **版本号去掉 rc**:社区包不再发 prerelease 版本(docs/publishing.md「版本号规则」),家族各包同版 0.2.0;本节原记为 0.2.0-rc.4。
19
+ - **邀请成员对话框的模型列表与「可用模型」短名单一致**(外部 harness):邀请时的模型选择器原来显示端点全量可服务目录;现在按 models.json 短名单的范围与顺序给候选(短名单里端点已不再供应的值保留为裸行,与成员对话框同一约定),无短名单时维持目录原样。成员对话框本就按短名单——邀请与成员管理自此同一份候选。
20
+
3
21
  ## 0.2.0-rc.3(2026-10-22)
4
22
 
5
- (版本号说明:家族的 dsh-headless 包 0.2.0-rc.2 曾被发布后又撤回,npm 永久烧掉该版本号;为守住家族「同一版本线」纪律(check:release-groups),本波家族整体上移 rc.3,内容即 rc.2 计划稿。)
23
+ (版本号说明:家族的 dsh-headless 包 0.2.0-rc.2 版本号已被 npm 发布流水线占用(重复利用被 409 拒绝:「Cannot stage previously published version」——曾发布后被撤回或曾被暂存,外部无法区分,结果相同:该号不可复用);为守住家族「同一版本线」纪律(check:release-groups),本波家族整体上移 rc.3,内容即 rc.2 计划稿。)
6
24
 
7
25
  适配宿主 0.2.1 线:verifiedHost 前移至 0.2.1-rc.1(host-021 波全量构建+测试对 0.2.1-rc.1 树双绿,并经 3080 生产验收);`@deepseek-ai/dsh-*` peer 区间加宽覆盖宿主 0.2.1。
8
26
 
package/README.en.md CHANGED
@@ -2,186 +2,45 @@
2
2
 
3
3
  English | [中文](README.md)
4
4
 
5
- Drive the coding-agent CLIs installed on this machine — Kimi Code, Codex, Claude Code, even another dsh — from the dsh web UI.
5
+ Let DSH hand work to the Kimi Code, Codex, Claude Code, or second DSH on your machine, and manage them all from one card.
6
6
 
7
- This is the local-agent family core: each CLI registers as one harness and gets an isolated scoped home under the shared homes root (created 0700 — your native CLI installation is never touched), a `/<harness> login|sessions|status|logout` slash-command family, and an auth card in Settings; the family's delegation ability — handing session work to a local CLI and resuming it later, even across host restarts — hangs off the same registry. The harnesses themselves ship as separate packages (`@khorsheed/dsh-local-agent-kimi` and friends); this package contains none.
7
+ This is the core of local-agent delegation; Kimi, Codex, Claude Code, and DSH all run on it. Using several coding agents used to mean switching between terminals, signing in to each, and keeping track of each one's sessions. With it installed, you set up every agent's credential and models on one 「本机 Agent 委派」 (Local Agent Delegation) card on the Plugins page. Hand work off in a session and the sub-session isn't a read-only replay: open it and keep talking. If DSH restarts, unfinished hand-offs pick up again.
8
8
 
9
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/08-local-agent.png" width="640" alt="the Local Agent cards under Settings → Plugins, header dots showing each provider's auth state">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-card.png" width="560" alt="The Local Agent Delegation card: Kimi, Codex, Claude Code and DSH sections, a green dot beside each title meaning it is set up">
10
10
 
11
- ## Features
11
+ ## What it does
12
12
 
13
- - **Scoped homes per CLI** — isolated credential/session home under a shared root, created 0700; your native CLI installation is never touched.
14
- - **Slash-command families** — `/<harness> login|sessions|status|logout`, with the device-code login URL in the reply; plus the family commands `/local-agent list` (the registered-harness roster) and `/local-agent stop <childSessionId>` (cancel an in-flight delegation).
15
- - **One「Local Agent Delegation」family settings card** — on the Plugins page (a single `plugins.item` entry owned by the core, mounted with the core's client bundle — no namespace gate: the core's Config has no volatile field, so its namespace never appears in the settings describe answer and the companions' `whileServed` convention cannot apply): each provider's card body renders as one collapsible section (auth status, endpoint credential, available models, the hot-swappable resident-mode toggle), its header carrying the credential dot and a grey「CLI not detected」tag; providers register their sections through the core client's `localAgentUi.registerHarnessCard` (on an older core each provider falls back to its own `plugins.item` card). Bodies compose this package's shared `ProviderAuthBlock` and `ModelListSection` (the「Available models」section: an ordered shortlist whose first entry is the default model, edited through the gateway's `modelList`/`configureModelList` Remotes; the host-side `models.json` read/write helpers ride this package's root export, and the list's first entry outranks the `model` config key in each harness's resolution chain).
16
- - **Subagent delegation with resume** — hand work to a local CLI and resume it later, even across host restarts; delegation records persist per harness in the scoped home's `delegations.jsonl`.
17
- - **Member sessions stay writable** — a delegated child session is not a read-only replay: the MemberComposer keeps it open for input (sending resumes it), live output streams in, and the model and reasoning effort switch from the member composer; a selection made while busy takes effect on the next complete turn, and frozen evaluation members refuse changes.
18
- - **Member sessions continue in the sidebar (host 0.1.6+)** — the official subagent directory gained an "Open in sidebar" row action: member sessions are one-shot subagents, so opened aside, this family's MemberComposer is elected as the writable composer — a zero-change benefit of this package; 0.1.5 hosts have no such entry and behave as before.
19
- - **Several logins per harness (named scopes)** — `/<harness> login --scope <name>` opens a second scoped home at `<homesRoot>/<harness>@<name>`: its own login, its own session records, its own `delegations.jsonl`, and nothing copied from the default one. An evaluation can therefore compare two accounts of one harness in a single run.
13
+ - **One card for every local agent**: one section each; expand it to enter the credential, order "Available models", and switch "Resident mode (live)". An agent whose CLI isn't installed is marked "CLI not detected".
14
+ - **Keep talking in the sub-session**: open a handed-off sub-session and message the member from the composer at the bottom; it's the same session, and new output appears live.
15
+ - **Switch model and effort any time**: right in the sub-session's composer; pick while it's running and it applies on the next round.
16
+ - **Survives restarts**: hand-off records stay on your machine, so after DSH restarts an unfinished sub-session can still continue.
17
+ - **Stop on demand**: `/local-agent stop <sub-session id>` stops a running hand-off, and `/local-agent list` shows which agents are installed.
18
+ - **Your own CLI setup stays untouched**: each agent runs in the plugin's own folder; your existing config, sign-ins, and sessions stay as they are.
19
+ - **Local members in a room can ask for help**: in a 协作间 (room), Kimi, Codex, and Claude Code members can ask other sessions too, and the reply comes back to them.
20
20
 
21
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-member.png" width="640" alt="a member session delegated to a local CLI: live output streaming in, with the writable member composer and its model picker at the bottom">
21
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-member.png" width="560" alt="A sub-session handed to a local CLI: output appearing live, with the member composer at the bottom for continuing and switching models">
22
22
 
23
23
  ## Install
24
24
 
25
- The core must be installed alongside at least one harness package — `dsh plugin add` activates only *direct* dependencies, so a harness package's transitive dependency alone never mounts this core:
25
+ In DSH, open Plugins → Add plugin, enter this package name, and press Install:
26
26
 
27
- ```sh
28
- dsh plugin --profile web add @khorsheed/dsh-local-agent
29
- dsh plugin --profile web add @khorsheed/dsh-local-agent-kimi # or -codex / -claude-code / -dsh
30
27
  ```
31
-
32
- To install the whole family at once (core + the four delegation providers), add the meta-package `@khorsheed/dsh-bundle-local-agent` instead.
33
-
34
- Restart the web instance to activate. Uninstall:
35
-
36
- ```sh
37
- dsh plugin --profile web remove @khorsheed/dsh-local-agent
28
+ @khorsheed/dsh-bundle-local-agent
38
29
  ```
39
30
 
40
- ## Config
31
+ This plugin comes with the 「多Agent协作」 (multi-agent) suite, together with the four local agents and the room; the Dev Suite `@khorsheed/dsh-dev-suite` includes it too. Install each agent's CLI (`kimi`, `codex`, `claude`) yourself first. Restart DSH, set up the agents you want on the 「本机 Agent 委派」 card, then start a "开发模式" (dev mode) session and tell the agent "give this to Codex".
41
32
 
42
- A custom composition mounts the core once:
33
+ ## Known limitations
43
34
 
44
- ```yaml
45
- - id: local-agent
46
- name: '@khorsheed/dsh-local-agent'
47
- config:
48
- homesRoot: !!js dshHomePath('local-agent')
49
- ```
35
+ - The CLIs themselves aren't installed for you; one that's missing shows "CLI not detected" on the card.
36
+ - Hand-off records only grow; after long use you clean up the plugin folder yourself.
50
37
 
51
38
  ## Compatibility
52
39
 
53
- - npm release line (`@deepseek-ai/dsh@0.1.5-rc.1`): ✅ full — adapted to 0.1.5-rc.1 (format v2/v3; handle-based sessionPersistence), full build+test green; minHost moves up to 0.1.5-rc.1 — older hosts stay on the previous release line. The「Local Agent Delegation」family settings card mounts with the core's client bundle onto the Plugins page's `plugins.item` seat on host 0.1.7-alpha.1+ (no `configForms.whileServed` gate — the core's Config has no volatile field, so its namespace never enters the settings describe answer); older hosts lack that seat and render no family card, and the companion cards are unaffected.
54
- - source line (deepseek-harness master): ✅ (verifiedHost: 0.2.1-rc.1) — host-021 wave: full build+test green against the 0.2.1-rc.1 tree; delegation moved to `ctx.subagents.startActivation` (managed activation), and run.id is now unique per execution.
55
-
56
- ## Known Limitations
57
-
58
- - **Login is not an interactive terminal** — the web GUI has no interactive terminal surface, so each harness with a login flow takes an adapted lane: kimi's device-code URL lands in the command reply while the CLI polls in the background; codex's and claude's (≥2.1) auth runs only on a TTY, so the login command is spawned under a pseudo-terminal wrapper — the CLI opens the user's browser itself, an OAuth URL is scraped from its output as a fallback, and a code prompt is answered with `/<harness> code <value>` — while the registry watches the scoped home for the credential. The contract also carries a manual-handoff variant (the reply names the exact command for the user's own terminal); no family harness needs it today. The dsh harness has no login flow at all (it inherits the host instance's credential), and `/dsh login` says so.
59
- - **Homes root placement** — defaults to `$DSH_HOME/local-agent`, pending a standardized `var/state` layout.
60
- - **Delegation log growth** — each scoped home's `delegations.jsonl` is append-only with no rotation.
61
- - **The harness contract is not frozen** — the four existing providers (kimi / codex / claude-code / dsh) each induced their share of it, and the registration interface may still move before a new harness joins.
62
- - **The member channel authenticates on the per-run token alone** — the token is delivered through the CLI's scoped MCP config (the 0700 scoped home keeps other users out) and invalidated the moment the run settles; host 0.1.5 removed the child pid, so there is no second factor. **Residual exposure**: a same-host, same-user sibling member CLI (its model-driven bash) can read another member's token and replay it — the old pid check was a self-reported field and never stopped a deliberate forgery, so dropping it loses little; but a deliberately crafted cross-member call is now possible. Hardening (kernel-level socket peer credentials, or a spawn-time injected capability token) is tracked in `proposals/active/2026-09-10-member-channel-auth-hardening.md`.
63
-
64
- ## How it works
65
-
66
- <details>
67
- <summary>Internals (click to expand)</summary>
68
-
69
- Each harness registers into `ctx.localAgent`: a scoped home, an optional login declaration (a device-code command, a pty-wrapped TTY login, or the manual-handoff variant), a session-records adapter, and optional auth-status and sign-out probes. The glue provisions each home and registers the `/<harness> login|sessions|status|logout` command family; per-harness differences are just `homeEnvVar`, the login invocation, the records adapter, and the auth/sign-out probes.
70
-
71
- **Delegation stays out of this seam.** Each harness bundle mounts its own subagent-provider row into the existing `subagent` capability (subagent-acp for ACP-over-stdio harnesses, an app-server provider for Codex), reading the scoped home through `localAgent.homeDir(name)`.
72
-
73
- **Program queries ride a read-only Remote channel.** A `LocalAgentGateway` (service key `localAgentGateway`, generated `./remote`) exposes roster, per-harness status, and scoped sessions to the browser. It emits no session events, so UI polls leave no command nodes in the session log; login and logout stay on the slash-command channel, where a visible command node is the expected feedback.
74
-
75
- **The member live-output pipeline.** A member's (delegated child session's) live output rides a streaming Remote and public Conversation nodes: the provider hands the round's increments to a `LiveStreamPublisher`, browser-side updates coalesce within at most 50ms (`LIVE_FLUSH_INTERVAL_MS`), and incremental recovery checkpoints append `local-agent/stream` events on an independent one-second cadence (`LIVE_CHECKPOINT_INTERVAL_MS`); once the native final message lands it replaces the transient presentation, keeping usage, tools, and session navigation. A reconnect reads a fresh baseline; a host crash can lose the uncheckpointed tail, and the recovered content is explicitly marked partial. Model/effort configuration, model directories, and the visible members' live output share one member subscription per page (`MemberFeeds`), so many members and member-session entries never saturate the HTTP/1 connection pool. Browser P95 acceptance remains part of the room coordinator proposal.
76
-
77
- **Evaluation snapshot (`effectiveSettings`).** Every harness may declare a snapshot of the fairness-relevant settings currently in force: drive (exec/live), the sandbox or permission boundary (in each harness's own vocabulary — codex reports its sandbox policy, claude-code its permission mode, kimi its auto-approve state; a harness with no such knob omits the field, and the absence is itself the honest condition-hash input), the reasoning effort, the configured model (each harness reads its own configuration surface — kimi's `default_model`, codex's `model`, claude-code's scoped `settings.json`, dsh's inherited host selection; unreadable means the field drops out, never a guessed default), whether a non-default endpoint is pinned (hostname only, never the full URL), and the CLI version (asked of the CLI itself: `probeCliVersion` runs one `<cli> --version`, cached against the executable's resolved path + mtime + size, so an upgrade re-probes by itself while a missing CLI, a timeout, a non-zero exit, or output with no version-shaped token all just leave the field out). The snapshot is a live read — a person-edited scoped config reports the edited values — pure JSON, never credentials. `registry.effectiveSettings(name)` serves the evaluator's condition hash, and the same snapshot rides `/<harness> status` and the `LocalAgentStatus` Remote (additive fields; existing clients are unaffected).
78
-
79
- **The credential status says only what is known (`credentialState`).** Beside the `authenticated` boolean, `LocalAgentStatus` carries a grade: `absent` (no credential record in the scoped home), `present-unverified` (a record exists, but nothing in this host process has exercised it — an expired, unrefreshable grant looks exactly like a working one, and saying so is the whole point of this grade), `verified` (a delegation round reached the endpoint and completed since the last login/logout), and `rejected` (a round's endpoint rejected the credential and no fresh login has rewritten the credential marker since). `authenticated` is unchanged and exactly equals `verified || present-unverified`, so the settings card and every existing client keep reading the boolean they always read. The grade is per host process: after a restart a present credential reports `present-unverified`, because that is what is actually known — an eager pre-run liveness probe is not this field's job. Providers call `reportAuthSuccess` when a round settles completed, the symmetric counterpart of the existing `reportAuthFailure`.
80
-
81
- **Browser half ships in this package.** The `./client` export mounts automatically via the `dsh.client` manifest: the member composer (delegated child sessions stay writable), the shared settings-card building blocks (`ProviderAuthBlock`, `ModelListSection`, the auth-status bus, `AuthStatusDot`), and the「Local Agent Delegation」family card — the single Plugins-page entry rendering every provider-registered card body as a section, keeping the UI provider-neutral (it consumes only the `/<harness>` command family and the read-only gateway).
82
-
83
- ### Adding a harness
84
-
85
- A harness bundle registers into the core and mounts its own delegation row:
86
-
87
- ```ts
88
- import type { Context } from '@deepseek-ai/cordis'
89
- import type { LocalAgentSessionRecord } from '@khorsheed/dsh-local-agent'
90
-
91
- const listCodexSessions = async (homeDir: string): Promise<readonly LocalAgentSessionRecord[]> => []
92
-
93
- /** Register the Codex harness into the local-agent registry. */
94
- export function registerCodex(ctx: Context): void {
95
- ctx.localAgent.register({
96
- name: 'codex', // command prefix + scoped-home dir
97
- displayName: 'Codex',
98
- homeEnvVar: 'CODEX_HOME',
99
- delegationProvider: 'codex', // subagent provider name; cross-checked at load
100
- login: { command: 'codex', args: ['login'] },
101
- records: { listSessions: listCodexSessions },
102
- // Optional: report auth from the scoped home and sign out of it, so
103
- // /codex status|logout work and the settings tab can switch accounts.
104
- isAuthenticated: async () => false,
105
- logout: async () => {},
106
- })
107
- }
108
- ```
109
-
110
- `login` is optional: a harness without a device-code flow (e.g. dsh itself, which authenticates through the host instance's `DEEPSEEK_API_KEY`) omits it, and `/dsh login` answers that the harness has no login flow instead of spawning a CLI. Such harnesses still report `status` through `isAuthenticated` and list sessions normally; the status snapshot carries explicit `loginable`/`logoutable` capability flags so surfaces never offer an action the harness would reject.
111
-
112
- ### Delegation registry (resume carrier)
113
-
114
- The registry also owns the family's **delegation registry**: a per-child-session record of which provider and CLI session a delegation used, plus a per-(parent, provider) FIFO of delegation intents. The family tool (each provider package's `./tool` composition entry, sharing this package's `./tool-factory` export — the former `@khorsheed/dsh-local-agent-tool-subagent` package retired at the family fold) stages exactly one intent per call before `ctx.subagents.startActivation()`, and the owning provider consumes exactly one per start — so fresh and resume rounds stay paired even under parallel delegation. A resume handle (the dsh child session id) resolves through the registry, which rejects a handle naming an unknown child, another parent's session, or the wrong provider; the subagent request descriptor cannot carry the target, so this service is the family-internal carrier. Mappings persist per harness in an append-only `delegations.jsonl` under the harness's scoped home (last line per child session wins), so a resume handle survives a host restart.
115
-
116
- ### Delegation call options (facade)
117
-
118
- The `options` argument of `registry.start(parent, provider, prompt, options)` and `registry.resume(…)` (`DelegationCallOptions`) — purely additive, every field absent meaning the behavior from before it existed:
119
-
120
- | Field | What it does |
121
- |---|---|
122
- | `label` | Child display label; absent uses the harness's own display name |
123
- | `signal` | The caller's own cancellation channel, fused with the facade's internal controller |
124
- | `onProgress` | Per-call progress callback, the same payload the `localAgent/run-progress` event carries |
125
- | `reattach` | `resume` only: restore a non-live child session from persistence (default true); `false` fails loud instead |
126
- | `cwd` | The round's CLI working directory; a resume round must repeat the first round's, or it fails loud before any spawn |
127
- | `exec` | Run this round inside an **already-acquired container**: `{ container, workdir, env? }` |
128
- | `model` | The model THIS DELEGATION runs: accepted on `start` only — passing it to `resume` fails loud, because the first round's request is recorded and every later round re-requests it (none, when the first named none). Accepted for exec and live alike (the provider binds it when spawning the process/runtime). Order below |
129
- | `scope` | Run this round against the harness's **named scoped home** (`<homesRoot>/<harness>@<scope>`); a resume round must repeat the first round's scope, absence included, or it fails loud |
130
-
131
- **One settled observation per round.** A provider calls `recordRoundSettled` once its round's output stream is fully parsed, and the family turns it into one `settled` progress event: the round's `observedModel`, `cliVersion`, `usage`, and its `toolCalls` (`{ count, byName }`). Every field stays absent when the round did not yield it — never guessed, never zero-filled.
132
-
133
- `toolCalls` is counted only from the transcript events the provider ALREADY parses — no new parse path and no second pass over the stream. `byName` keys are each CLI's own tool vocabulary (codex's `command_execution`, claude's `Bash`/`Read`, the tool names kimi's and dsh's own events carry), kept verbatim and **never normalized across harnesses**: cross-harness comparison is therefore `count` only, and `byName` is for a reader. Normalizing would invent an equivalence the CLIs never agreed to.
134
-
135
- `observedModel` and `cliVersion` merge into the delegation record (which states the delegation's latest state); `toolCalls` rides the EVENT only — it belongs to one round, and merging it would silently overwrite the previous round's count with the newest one. A settle that lands before the record exists (the record point is each provider's own: a live round records at session/new, an exec round at the post-settle output parse) does not lose its observation either — the family stashes it and `recordDelegation` merges it when the record lands; both the exec and the live drive report.
136
-
137
- **The model's five layers.** Which model a round starts its CLI with is the first of these that names one:
138
-
139
- 1. **the session-level override** (set through the member composer's model picker; in-memory, gone with a host restart)
140
- 2. **the delegation's own `model`** (the `start` call option; a resume round reads the same value back off the delegation record)
141
- 3. **the harness's `model` plugin-config key** (T30a, harness-wide)
142
- 4. **the harness's scoped configuration file** (codex's `model`, claude's `settings.json`, kimi's `default_model`; dsh inherits the host instance's default model selection)
143
- 5. **the CLI's own default**
144
-
145
- All five absent means no model flag on the argv at all — byte for byte the behavior before any of these keys existed. Blank counts as absent.
146
-
147
- **Why `resume` does not take a model.** A model belongs to the DELEGATION, not to one of its rounds: the first round records what it requested and every later round re-requests it. Switching mid-conversation is something the CLI would honour and the transcript would not show, so it is a caller error that fails loud rather than a silently ignored field. To switch models, use the member composer's picker (the session-level override below) — a layer the transcript can account for.
148
-
149
- **Member model and reasoning configuration.** The member composer and Room coordinator share the core panel, showing current applied values, pending selections and preparation errors. The compact menu shows model and effort, and selections submit immediately; inheritance and CLI defaults remain backend compatibility for existing configurations. Settings and invitations retain a saved choice, otherwise selecting the first visible directory entry; settings still require Save. The selector shows model and effort without custom IDs, catalog details or persistent provenance diagnostics; catalogs still update in the background. Settings and member menus dismiss on outside interaction or Escape and adapt their placement to available space. Missing native effort options show an unavailable explanation rather than guessed choices. Busy selections prepare after the current complete turn and tool continuations settle. Intent, revisions and pending state are durable and reconciled against native state after restart; users can cancel pending selections or retry failed preparation. Frozen evaluation members reject changes. Legacy `setMemberModel` uses the same controller instead of a separate busy-refusal path.
150
-
151
- `effectiveSettings.model` still reports the answer from **the settings layer down** — "what would a round with no model of its own run", which is the harness-wide setting the condition snapshot is asking about; delegation records and session overrides never enter the snapshot.
152
-
153
- **Container delegation (the `exec` target).** Given one, the provider spawns `docker exec -w <workdir> [-e NAME…] <container> <the same argv>`; stdio stays piped, and the stream parse, settle, readback and `delegations.jsonl` record are byte-for-byte the host path's. The family owns exactly one docker verb, `exec` — acquiring, mounting and destroying a container belong to the caller (lab).
154
-
155
- - **Values never ride the argv.** Each forwarded variable appears only as `-e NAME`, and the docker CLI resolves it from its own environment — so a credential the provider resolved stays out of the host process table. What is forwarded: the defined entries of the provider's explicit env layer, overridden per key by `target.env`; the ambient allowlist (`PATH`, `HOME`, the proxy variables) is **not** forwarded — inside the container those belong to the image and the `docker run` that created it.
156
- - **The caller must name the scoped home.** `target.env` must carry the in-container scoped-home variable (`CODEX_HOME` / `CLAUDE_CONFIG_DIR` / `KIMI_CODE_HOME` / `DSH_HOME`), or the round fails loud before spawning: the host path means nothing inside the unit, and forwarding it would start the CLI from an empty directory — no credentials, no rollout to read back, and nothing in the output naming the cause.
157
- - **The scoped home is a HOST directory, bind-mounted read-write.** Readback (codex's rollout, kimi's wire log, the sub-dsh session log) reads it straight off the host filesystem, and credential refreshes land back on the host. **The caller stages what it mounts**: mount a live scoped home and its host-only settings ride along (measured: the `https_proxy` in claude's scoped `settings.json`, meant for the host daemon, points at nothing inside the unit and the round dies with `Connection refused`).
158
- - **A container round is exec-only and carries no member channel.** The live drivers run resident processes on the HOST — the transport the target exists to replace; the member bridge is a host unix socket whose MCP declaration names a host node path. Both are given up on purpose, not missing by accident.
159
- - **The caller repeats the target on resume.** The recorded anchor is the host `cwd`, which a swapped container leaves equal — that one the record cannot catch.
160
-
161
- ### Named scopes: several scoped homes per harness
162
-
163
- The default scoped home is `<homesRoot>/<harness>` — byte for byte the directory it always was. Give a NAME (`[a-z0-9-]` only; a scope is a name, never a path) and you get a SIBLING of it: `<homesRoot>/<harness>@<name>`. Not a child, because that directory belongs to the harness's own CLI, and a second state tree inside it is something that CLI will eventually prune or misread.
164
-
165
- - **Materialized lazily.** The directory is created the first time anything names the scope (`/<harness> login|status|sessions|logout --scope <name>`, a delegation carrying `scope`, the evaluation's mount source): `mkdir` 0700, then the harness's own provisioning (codex's `config.toml`, kimi's provider/model config and permission rules, claude's scoped home, dsh's sub-profile). The default scope's provisioning is unmoved — each harness bundle's `apply` still owns it.
166
- - **Provisioning one scope deliberately: `provisionScope(name, scope, options)`.** The materialization above is a side effect of naming a directory: once per (harness, scope) per host process, no options, a failure that is only a log line. This verb is the deliberate one — it **waits** (including on a materialization it may have just started, since both writers write the same files), it **throws** (a question asked must have an answer, and a failure must not read as a success), and it hands back the harness's **read-back**. Its only option today is `preset`, because a preset is a property of the SUBJECT rather than of the deployment: when an evaluation condition says "this scope composes this preset", the condition document is what knows, and plugin config has one answer per instance. Only dsh reads it; the other three take the parameter and ignore it, which keeps their provisioning byte-identical.
167
- - **Credentials are never copied.** A new scope is empty: `status` reports `credentialState: absent` and a delegation fails exactly as it does today, so using one starts with `/<harness> login --scope <name>`. claude's keychain item is keyed by the config directory's path, so a named scope gets its own item for free — the one part of this that is path-safe by nature.
168
- - **Everything follows the directory.** That scope's session records, its `delegations.jsonl`, its effective-settings snapshot (the harness's `effectiveSettings(homeDir)` takes the directory as a parameter), its CLI-version probe, its credential grade and its delegation read-back all live in its own directory. The delegation record carries the `scope`, and a resume round is anchored to it: another scope — or none, when the first round had one — is refused before any process spawns. Continuing one CLI session under another account's credentials is not a mistake that can be repaired afterwards.
169
- - **Boundaries (named absences, not gaps).** A scoped delegation is **exec-only**: the resident drivers (codex's app-server, claude's and kimi's ACP, the sub-dsh `serve`) are bound per member to the DEFAULT scoped home, so a scoped round meeting an active live driver is refused rather than quietly downgraded. kimi's member-bridge declaration is written INTO a scoped home's `mcp.json` and `member-bridge.sock` is a single homes-root socket, so a scoped kimi round carries no member channel. dsh's sub-profile follows the directory and needs nothing special.
170
-
171
- ### Active-delegation registry and `/local-agent stop`
172
-
173
- The registry also owns the **active-delegation registry**: an in-flight run table keyed by dsh child session id. Facade-started runs (`start`/`resume` — the member composer, room, and other programmatic entry points) and runs the family tool starts directly through `ctx.subagents.startActivation()` (registered via `trackDelegationRun`) all land in the same table, and an entry clears itself when the run's result settles. `/local-agent stop <childSessionId>` cancels the in-flight delegation by this table — semantics aligned with the official `subagents.interrupt(targetSessionId)`: fire-and-return (the cancel signal goes out before the reply), and an absent target (unknown child or no in-flight run) is an explicitly-named accepted no-op, not an error. This gives surfaces like taskpilot a landing point for their stop buttons: for a one-shot subagent row with no live agent, the button dispatches `/local-agent stop <childSessionId>` instead, degrading to an explicit "cannot stop" error when the local-agent core is absent.
174
-
175
- ### Model Experience
176
-
177
- **What the model sees** — nothing from the registry itself: `/<harness>` command replies and the `/local-agent list` roster text are user-visible command text, never model prompts. Model-visible effects start only when a harness bundle mounts a subagent provider; the parent then sees the child's final answer through the subagent tool result.
178
-
179
- **Token effect** — command discovery, execution, and reply text add no model tokens. Delegation tokens belong to the harness bundle's provider, which pays for an independent child context.
180
-
181
- **KV Cache effect** — registry metadata and command replies never enter a model request and do not affect its cache; a delegated child owns its cache independently.
182
-
183
- </details>
40
+ Needs DSH 0.1.5-rc.1 or later; verified on 0.2.1-rc.1.
184
41
 
185
- ## Development
42
+ ## For developers
186
43
 
187
- Part of the [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo (`packages/local-agent`). Issues and contributions welcome there.
44
+ - How an agent registers, delegation call options, model resolution order, named scopes (several sign-ins per agent), in-container delegation, the member channel, and live output are in the [full reference](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/local-agent/docs/reference.en.md).
45
+ - To install it alone, name the core together with at least one agent: `dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-kimi` (installing a single agent does not bring the core along).
46
+ - Part of [dsh-plugins](https://github.com/Khorsheed/dsh-plugins); open issues and contributions there. Changes: [CHANGELOG.md](CHANGELOG.md).
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/local-agent/README.en.md
5
- packages/local-agent/README.en.md: 01cbc613f5a6f87dd18682213d717d44b1dda2d7
6
- packages/local-agent/README.md: 628a274e903c27eb056e7af666f47fcd6a2bcd9e
5
+ packages/local-agent/README.en.md: 6049d893d401c989d0e6e50d0905c1e62b73d9f0
6
+ packages/local-agent/README.md: 0ad5de3fb420edcefdc1ec61ae4ecc9ac4704302
package/README.md CHANGED
@@ -2,186 +2,45 @@
2
2
 
3
3
  [English](README.en.md) | 中文
4
4
 
5
- 从 dsh web 界面驱动本机装好的编码 agent CLI——Kimi Code、Codex、Claude Code,甚至另一个 dsh。
5
+ 让 DSH 把活交给你电脑上的 Kimi Code、Codex、Claude Code 或另一个 DSH,并在一张卡片里管好它们。
6
6
 
7
- 这是 local-agent 家族的核心包:每家 CLI 注册成一家 harness,得到共享 homes 根下一份 0700 的隔离作用域目录(你机器上原装的 CLI 安装绝不被动到)、一族 `/<harness> login|sessions|status|logout` 斜杠命令、设置里的一张认证卡片;家族的委派能力——把会话工作交给本机 CLI、之后跨宿主重启也能续跑——同样挂在这份注册表上。各家 harness 的实现在独立包里(`@khorsheed/dsh-local-agent-kimi` 等),本包自己一家都不含。
7
+ 这是本机 agent 委派的底座,Kimi、Codex、Claude Code、DSH 四家都靠它运行。以前要同时用几个编码 agent,得在几个终端之间来回切,各自登录、各自记会话。装上之后,你在「插件」页的一张「本机 Agent 委派」卡片里给每家配好凭据和模型;在会话里把活交出去,交出去的子会话不是只读回放,你可以打开它直接接着聊;DSH 重启了,没做完的委派也接得上。
8
8
 
9
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/08-local-agent.png" width="640" alt="设置 → 插件 → 可配置插件里的 Local Agent 卡片,卡头状态点一眼可见各 provider 授权状态">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-card.png" width="560" alt="「本机 Agent 委派」卡片:Kimi、Codex、Claude Code、DSH 四节,每节标题旁的绿点表示已经配好">
10
10
 
11
- ## 特性
11
+ ## 能做什么
12
12
 
13
- - **每个 CLI 一个作用域目录**——共享 homes 根下的隔离凭据/会话目录,以 0700 创建;你的本地 CLI 安装绝不被动到。
14
- - **斜杠命令族**——`/<harness> login|sessions|status|logout`,device-code 登录 URL 通过命令回复呈现;另有家族命令 `/local-agent list`(已注册 harness 花名册)与 `/local-agent stop <childSessionId>`(取消在飞委派)。
15
- - **一张「本机 Agent 委派」家族设置卡**——落在 Plugins 页(core 的一个 `plugins.item` 条目,随 core 的 client bundle 挂载,无命名空间门控——core 的 Config 无 volatile 字段,命名空间不出现在 settings describe 里,不能照 companion 的 `whileServed` 惯例走):每家 provider 的卡体渲染为卡内一节(认证状态、端点凭据、可用模型、常驻模式热切开关),节头带认证状态点与「未检测到 CLI」灰标签;provider 包经 core client 的 `localAgentUi.registerHarnessCard` 注册各自的节(旧版 core 上各家仍回落为自己的 `plugins.item` 卡)。卡体直接复用本包 client 面的共享 `ProviderAuthBlock` 与 `ModelListSection`(「可用模型」区:有序短名单,第一个为默认模型,经 gateway 的 `modelList`/`configureModelList` Remote 编辑;宿主侧的 `models.json` 读写助手从本包根导出,名单首项在各家解析链里排在 `model` 配置键之前)。
16
- - **子 agent 委派与续跑**——把会话工作交给本机 CLI 并在之后 resume,宿主重启也能续上;委派记录按 harness 持久化在作用域目录的 `delegations.jsonl`。
17
- - **成员会话保持可写**——委派的子会话不是只读回放:MemberComposer 让它继续接受输入(发送即 resume),实时输出流式呈现,模型与推理强度可在成员输入栏切换;运行中选择排到下一完整轮次生效,冻结的评测成员拒绝变更。
18
- - **成员会话侧栏续写(宿主 0.1.6 起)**——官方 subagent 目录每行新增「在侧边栏打开」入口:成员会话本就是 one-shot subagent,侧栏内本家族 MemberComposer 自动当选为可写 composer,本包零改动受益;0.1.5 宿主没有该入口,行为不变。
19
- - **一家多份登录(命名 scope)**——`/<harness> login --scope <名>` 在 `<homesRoot>/<家名>@<名>` 里另开一份作用域目录:各自登录、各自会话记录、各自 `delegations.jsonl`,凭证不复制。评测因此能在同一次 run 里比较同一家的两个账号。
13
+ - **一张卡片管所有本机 agent**:每家一节,展开就能填凭据、排「可用模型」、开关「常驻模式(live)」;没装 CLI 的一家会标出「未检测到 CLI」。
14
+ - **子会话可以接着聊**:打开交出去的子会话,在底部输入框里给成员发消息,继续的是同一个会话;生成中的内容实时出现。
15
+ - **随时换模型和推理强度**:子会话的输入栏里就能切;正在跑的时候选好,下一轮生效。
16
+ - **重启也接得上**:委派记录留在本机,DSH 重启之后,没做完的子会话照样能续上。
17
+ - **说停就停**:`/local-agent stop <子会话编号>` 停掉一个正在跑的委派,`/local-agent list` 列出装了哪几家。
18
+ - **你电脑上原来的 CLI 一点不碰**:每家都在插件自己的目录里运行,原有的配置、登录和会话保持原样。
19
+ - **协作间里的本机成员也能求助**:在协作间里,Kimi、Codex、Claude Code 成员也能去问别的会话,回信回到它自己手上。
20
20
 
21
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-member.png" width="640" alt="委派给本机 CLI 的成员会话:实时输出流式呈现,底部成员输入栏可继续对话并切换模型">
21
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-member.png" width="560" alt="交给本机 CLI 的子会话:输出实时出现,底部的成员输入栏可以继续对话、切换模型">
22
22
 
23
23
  ## 安装
24
24
 
25
- core 必须与至少一个 harness 包一起装——`dsh plugin add` 只激活**直接**依赖,仅靠 harness 包的传递依赖不会挂载本 core:
25
+ 在 DSH 的「插件 → 添加插件」里输入下面的包名,点安装:
26
26
 
27
- ```sh
28
- dsh plugin --profile web add @khorsheed/dsh-local-agent
29
- dsh plugin --profile web add @khorsheed/dsh-local-agent-kimi # 或 -codex / -claude-code / -dsh
30
27
  ```
31
-
32
- 想一次装齐整个家族(core + 四个委派 provider),改装元包 `@khorsheed/dsh-bundle-local-agent`。
33
-
34
- 重启 web 实例后生效。卸载:
35
-
36
- ```sh
37
- dsh plugin --profile web remove @khorsheed/dsh-local-agent
28
+ @khorsheed/dsh-bundle-local-agent
38
29
  ```
39
30
 
40
- ## 配置
41
-
42
- 自定义组合挂载一次 core:
43
-
44
- ```yaml
45
- - id: local-agent
46
- name: '@khorsheed/dsh-local-agent'
47
- config:
48
- homesRoot: !!js dshHomePath('local-agent')
49
- ```
50
-
51
- ## Compatibility
52
-
53
- - npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 完整——适配 0.1.5-rc.1(format v2/v3;handle 制 sessionPersistence),全量构建测试通过;minHost 前移至 0.1.5-rc.1,旧宿主请停留在旧发布线。家族设置卡「本机 Agent 委派」随 core 的 client bundle 挂到 0.1.7-alpha.1+ 的 Plugins 页 `plugins.item` 席位(无 `configForms.whileServed` 门控——core 的 Config 无 volatile 字段,其命名空间不进 settings describe);更旧的宿主没有该席位,不呈现该卡,各 companion 卡也不受影响。
54
- - 源码线(deepseek-harness master):✅(verifiedHost: 0.2.1-rc.1)——host-021 波对 0.2.1-rc.1 树全量 build+test 绿;委派迁移到 `ctx.subagents.startActivation`(受管 activation),run.id 改为每次执行唯一。
31
+ 本插件会跟着「多Agent协作」套件一起装好,连同四家本机 agent 和协作间;开发套件 `@khorsheed/dsh-dev-suite` 也包含它。各家的 CLI(`kimi`、`codex`、`claude`)要你自己先装好。装完重启 DSH,到「插件」页的「本机 Agent 委派」卡片里配好要用的那几家,然后开一个「开发模式」会话,对 agent 说「把这个交给 Codex」。
55
32
 
56
33
  ## 已知限制
57
34
 
58
- - **登录不是交互式终端**——web GUI 没有交互式终端面,各家有登录流程的 harness 各走一条适配通道:kimi 的 device-code URL 由命令回复呈现、CLI 在后台轮询;codex 与 claude(≥2.1)的认证只在 TTY 上跑,登录命令在伪终端包装下 spawn——CLI 自己打开浏览器,输出里兜底解析 OAuth URL,要输码时用 `/<harness> code <值>` 粘贴——registry 监听作用域目录识别登录完成。契约里另有 manual 交接变体(回复一条让用户在自己终端运行的完整命令),目前家族没有 harness 需要它;dsh harness 没有登录流程(继承宿主实例的凭据),`/dsh login` 如实回答。
59
- - **homes 根位置**——默认 `$DSH_HOME/local-agent`,待 `var/state` 布局标准化后再议。
60
- - **委派日志增长**——每个作用域目录的 `delegations.jsonl` 只增不减、无轮转。
61
- - **harness 契约尚未冻结**——现有四家 provider(kimi / codex / claude-code / dsh)各自归纳了自己的一份,新 harness 入局前注册接口仍可能调整。
62
- - **成员通道是 per-run token 单因子认证**——token 经 CLI 作用域 MCP 配置下发(0700 的 scoped home 挡住其他用户),run 落定即焚;宿主 0.1.5 移除子进程 pid 后没有第二因子。**残余风险**:同机同用户的兄弟成员 CLI(其模型驱动的 bash)能读到另一个成员的 token 并回放它——原 pid 校验也是自报字段,本就不防刻意伪造,去掉它没有实质降级;但刻意构造的跨成员调用现在是可能的。加固(socket 内核级 peer 凭证,或 spawn 时注入的 capability token)由 `proposals/active/2026-09-10-member-channel-auth-hardening.md` 跟踪。
63
-
64
- ## 实现原理
65
-
66
- <details>
67
- <summary>内部结构(点击展开)</summary>
68
-
69
- 每个 harness 向 `ctx.localAgent` 注册:一个作用域目录、一个可选的登录声明(device-code 命令、伪终端包装的 TTY 登录,或 manual 交接变体)、一个会话记录适配器,以及可选的认证状态与退出登录探测。glue 供给每个作用域目录并注册 `/<harness> login|sessions|status|logout` 命令族;harness 间差异只剩 `homeEnvVar`、登录调用、records 适配器与认证/退出探测。
70
-
71
- **委派不属于这个 seam。** 每个 harness bundle 各自向既有的 `subagent` 能力挂载 subagent-provider 行(讲 stdio ACP 的 harness 用 subagent-acp,Codex 用其 app-server provider),经 `localAgent.homeDir(name)` 读取作用域目录。
72
-
73
- **程序查询走只读 Remote 通道。** `LocalAgentGateway`(服务键 `localAgentGateway`,生成物 `./remote`)通过 Typert Gateway 向浏览器暴露 roster、各 harness 状态与作用域会话。它不产生任何会话事件,因此 UI 轮询不会在会话日志里留下命令节点;登录与退出仍走斜杠命令通道——用户主动操作产生可见命令节点正是预期反馈。
74
-
75
- **成员实时输出管线。** 成员(委派的子会话)的实时输出经流式 Remote 与公开 Conversation 节点呈现:provider 把一轮的增量交给 `LiveStreamPublisher`,浏览器侧更新最多合并 50ms(`LIVE_FLUSH_INTERVAL_MS`),增量恢复检查点按独立的一秒节奏追加 `local-agent/stream` 事件(`LIVE_CHECKPOINT_INTERVAL_MS`);原生最终消息落定后替换临时展示,用量、工具与会话导航全部保留。重连读取新基线;宿主崩溃可能丢失未落检查点的末段,恢复内容明确标为部分记录。模型/effort 配置、模型目录与可见成员的实时输出在每个页面共用一条成员订阅(`MemberFeeds`),多成员及成员会话入口不会占满 HTTP/1 连接池。浏览器 P95 仍需在 room 协调者提案中验收。
76
-
77
- **评测快照(effectiveSettings)。** 每个 harness 可声明一份当前生效的公平性相关设置:drive(exec/live)、沙箱或权限模式(各用自家词汇:codex 报 sandbox 策略、claude 报 permissionMode、kimi 报自动批准与否;没有该旋钮的 harness 字段缺位,缺位本身就是诚实的条件输入)、推理强度、已配置模型(各家读自家配置面:kimi 的 `default_model`、codex 的 `model`、claude 的作用域 `settings.json`、dsh 继承的宿主选择;读不到就不给字段,绝不猜默认值)、端点是否固定(只报主机名,绝不报完整 URL)、CLI 版本(问 CLI 自己:`probeCliVersion` 跑一次 `<cli> --version`,按可执行文件的解析路径+mtime+大小缓存,所以一次升级会自动重探,而没装、超时、非零退出、输出里没有版本形状的 token 都只是字段缺位)。快照是实时读——人改过的作用域配置报人改过的值——纯 JSON、绝不含凭证,`registry.effectiveSettings(name)` 供编排器读条件哈希,`/<harness> status` 与 `LocalAgentStatus` Remote 附带同一份(增量字段,旧客户端不受影响)。
78
-
79
- **凭证只说知道的(credentialState)。** `LocalAgentStatus` 除 `authenticated` 布尔外还带一档 `credentialState`:`absent`(作用域家目录里没有凭证记录)、`present-unverified`(有记录,但本宿主进程里还没有任何一轮委派碰过它——一份过期且刷不动的凭证和一份能用的凭证形状完全一样,说不知道正是这一档的意义)、`verified`(自上次登录/登出以来有一轮委派真的打通了端点并完成)、`rejected`(某轮的端点拒了这份凭证,且此后没有新登录重写凭证标记)。`authenticated` 保持不变,恒等于 `verified || present-unverified`,所以设置卡片与既有客户端读到的还是原来那个布尔。这一档是每宿主进程的:重启后一份在场的凭证回到 `present-unverified`,因为那才是当下真正知道的;开跑前的主动活性探测不归这个字段。provider 在一轮 settle 成 completed 时调 `reportAuthSuccess`,与既有的 `reportAuthFailure` 对称。
80
-
81
- **浏览器半身随本包提供。** `./client` 导出通过本包的 `dsh.client` manifest 自动挂载:成员 composer(委派的子会话可继续对话)、共享设置卡片构件(`ProviderAuthBlock`、`ModelListSection`、认证状态总线、`AuthStatusDot`),以及「本机 Agent 委派」家族卡——Plugins 页上的唯一条目,按节渲染各 provider 注册来的卡体,UI 保持 provider 无关(只消费 `/<harness>` 命令族和只读 gateway)。
82
-
83
- ### 新增一个 harness
84
-
85
- harness bundle 向 core 注册,并挂载自己的委派行:
86
-
87
- ```ts
88
- import type { Context } from '@deepseek-ai/cordis'
89
- import type { LocalAgentSessionRecord } from '@khorsheed/dsh-local-agent'
90
-
91
- const listCodexSessions = async (homeDir: string): Promise<readonly LocalAgentSessionRecord[]> => []
92
-
93
- /** Register the Codex harness into the local-agent registry. */
94
- export function registerCodex(ctx: Context): void {
95
- ctx.localAgent.register({
96
- name: 'codex', // command prefix + scoped-home dir
97
- displayName: 'Codex',
98
- homeEnvVar: 'CODEX_HOME',
99
- delegationProvider: 'codex', // subagent provider name; cross-checked at load
100
- login: { command: 'codex', args: ['login'] },
101
- records: { listSessions: listCodexSessions },
102
- // Optional: report auth from the scoped home and sign out of it, so
103
- // /codex status|logout work and the settings tab can switch accounts.
104
- isAuthenticated: async () => false,
105
- logout: async () => {},
106
- })
107
- }
108
- ```
109
-
110
- `login` 是可选的:没有登录流程的 harness(例如 dsh 自身——它通过宿主实例的 `DEEPSEEK_API_KEY` 认证,而非 device-code 流程)省略它,`/dsh login` 会回答"该 harness 无登录流程"而不是 spawn 一个 CLI。这类 harness 仍通过 `isAuthenticated` 报告 `status`,并正常列出会话;status 快照携带显式的 `loginable`/`logoutable` 能力标志,设置界面等表面因此不会提供会得到报错回复的操作。
35
+ - 各家 CLI 本体不代装;没装的那一家在卡片上显示「未检测到 CLI」。
36
+ - 委派记录只增不减,用久了要你自己清理插件目录。
111
37
 
112
- ### 委派 registry(resume 载体)
113
-
114
- registry 还持有家族的**委派 registry**:每个子会话一条记录,记下该委派用的 provider 与 CLI 会话,以及按 (parent, provider) 分组的委派 intent FIFO。家族工具(各 provider 包的 `./tool` 组合入口,实现共享自本包的 `./tool-factory` 导出——原 `@khorsheed/dsh-local-agent-tool-subagent` 已于家族折叠退役)在每次调用 `ctx.subagents.startActivation()` 前恰好 stage 一个 intent,归属 provider 每次 start 恰好消费一个——因此即使并行委派,fresh 轮与 resume 轮也能正确配对。resume 轮的句柄(dsh 子会话 id)经 registry 解析,凡是未知子会话、他人 parent 的会话、或错误 provider 的句柄都会被拒绝;subagent 请求 descriptor 无法携带该目标,因此本服务就是家族内部的载体。映射按 harness 持久化在其作用域目录下的 append-only `delegations.jsonl`(同一子会话最后一行生效),resume 句柄因此能跨宿主重启存活。
115
-
116
- ### 委派调用选项(facade)
117
-
118
- `registry.start(parent, provider, prompt, options)` 与 `registry.resume(…)` 的 `options`(`DelegationCallOptions`)——纯增量,每个字段缺位就是它出现之前的行为:
119
-
120
- | 字段 | 作用 |
121
- |---|---|
122
- | `label` | 子会话显示名;缺位用 harness 自己的显示名 |
123
- | `signal` | 调用方自己的取消通道,与 facade 内部 controller 熔合 |
124
- | `onProgress` | 本次调用的进度回调,与 `localAgent/run-progress` 事件同样的载荷 |
125
- | `reattach` | 仅 `resume`:子会话不在线时从持久化恢复(默认 true),传 `false` 则 fail loud |
126
- | `cwd` | 本轮 CLI 的工作目录;resume 轮必须与首轮记录一致,否则进程启动前 fail loud |
127
- | `exec` | 本轮在**已取得的容器里**跑:`{ container, workdir, env? }` |
128
- | `model` | 本**次委派**跑哪个模型:只在 `start` 上接受,`resume` 传了即 fail loud——首轮请求的值记进委派记录,之后每轮照它重发(首轮没写就一直不写)。exec 与 live 都接受(provider 在起进程/runtime 时绑定它)。取值顺序见下 |
129
- | `scope` | 本轮跑该家的**命名作用域目录**(`<homesRoot>/<家名>@<scope>`);resume 轮必须与首轮记录的 scope 一致(含"都没有"),否则 fail loud |
130
-
131
- **每轮的 settle 观测。** provider 在一轮的输出流解析完之后调用 `recordRoundSettled`,家族把它转成一条 `settled` 进度事件:本轮的 `observedModel`、`cliVersion`、`usage`,以及本轮的 `toolCalls`(`{ count, byName }`)。每个字段取不到就缺位,绝不猜、绝不补零。
132
-
133
- `toolCalls` 只从 provider **已经解析过**的转录事件里数出来——不新开解析路径、不再走一遍流。`byName` 的键是各家 CLI 自己的工具词汇(codex 的 `command_execution`、claude 的 `Bash`/`Read`、kimi 与 dsh 各自事件里的工具名),原样保留、**不跨家归一**:因此横比只比 `count`,`byName` 是给人读的。归一化会凭空造出一份 CLI 之间从未约定过的等价关系。
134
-
135
- `observedModel` 与 `cliVersion` 会并进委派记录(记录讲的是这次委派的最新状态),`toolCalls` **只走事件**:它属于某一轮,合并进记录等于用最新一轮悄悄盖掉上一轮的计数。settle 先于记录到达(记录点在各 provider 自己手里:live 轮在 session/new 时记录,exec 轮在 settle 后解析输出时记录)也不丢观测——家族先暂存,`recordDelegation` 落地时并入;exec 与 live 两条驱动路径都会上报。
136
-
137
- **模型的五层取值顺序。** 一轮 CLI 用哪个模型,按固定顺序取第一个有值的:
138
-
139
- 1. **会话级覆盖**(成员 composer 的模型切换器设置;存内存,宿主重启即失效)
140
- 2. **本次委派的 `model`**(`start` 的调用选项;resume 轮从委派记录里读回同一个值)
141
- 3. **该家插件配置的 `model` 键**(T30a,按 provider 全局生效)
142
- 4. **该家作用域配置文件**(codex 的 `model`、claude 的 `settings.json`、kimi 的 `default_model`、dsh 继承宿主实例的默认模型选择)
143
- 5. **CLI 自己的默认**
144
-
145
- 五层都没有 = argv 上一个模型参数都不加,与这些键出现之前逐字节相同。空白算没写。
146
-
147
- **为什么 resume 不接受 model。** 模型属于这次**委派**,不属于它的某一轮:首轮请求什么就记什么,之后每轮照发。中途换模型 CLI 会照办,而转录里看不出来——所以那是调用方的错误,fail loud,而不是被悄悄忽略的字段。要换模型走成员 composer 的切换器(下面的会话级覆盖),它在转录里是有迹可循的一层。
148
-
149
- **成员输入栏的模型与推理配置。** 成员输入栏与 Room 协调者入口共用 core 的配置面板,显示本轮有效值、待生效选择及准备错误。常用菜单仅展示模型与强度,选择即提交;继承与 CLI 默认语义只保留为旧配置的后台兼容。设置和邀请保留已有选择,未选择时采用目录第一个可见候选;设置仍需保存。选择菜单只显示模型与强度,不提供自定义 ID、目录详情或常驻的来源诊断;目录仍在后台自动更新。设置与成员菜单均支持点击外部、按 Escape 关闭,浮层按可用空间调整方向。没有原生强度选项时显示不可用说明,不猜测候选。忙时可以选择,新值在当前完整轮次及工具续跑结束后准备生效。配置意图、版本与待生效项持久化,重启后核对原生状态;用户可撤销待生效项或重试失败准备。冻结评测成员禁止变更。legacy `setMemberModel` 接入同一控制器,不再保留忙时拒绝的另一套语义。
150
-
151
- `effectiveSettings.model` 报的仍是**设置层往下**的答案——「一轮没有自带模型时会跑什么」,也就是条件快照要问的那个 harness 级设置;委派记录与会话覆盖不进快照。
152
-
153
- **容器内委派(`exec` 目标)。** 给了它,provider 把 argv 换成 `docker exec -w <workdir> [-e NAME…] <container> <原 argv>`;stdio 仍是 pipe,流解析、settle、回读、`delegations.jsonl` 记录全部与宿主路径逐字节相同。家族只用 `exec` 这一个 docker 动词——取得、挂载、销毁容器是调用方(lab)的事。
154
-
155
- - **值不上 argv。** 每个转发的变量只以 `-e NAME` 出现,值留在 docker 客户端自己的环境里由它解析——provider 解析出的凭据因此不进宿主进程表。转发集 = provider 显式 env 层里有值的项,按键被 `target.env` 覆盖;`PATH`/`HOME`/代理这些走继承白名单的变量**不转发**,容器里它们属于镜像与 `docker run`。
156
- - **作用域目录必须由调用方点名。** `target.env` 必须给出容器内的作用域目录变量(`CODEX_HOME` / `CLAUDE_CONFIG_DIR` / `KIMI_CODE_HOME` / `DSH_HOME`),否则进程启动前 fail loud:宿主路径在容器里什么都不是,照转会让 CLI 从一个空目录起步——没凭据、没有可回读的 rollout,而且失败原因不出现在任何输出里。
157
- - **作用域目录是宿主目录、rw bind 挂进容器。** 回读(codex 的 rollout、kimi 的 wire log、子 dsh 的会话日志)直接读宿主文件系统,凭据续期也回写到宿主目录。**挂什么由调用方备好**:把活的作用域目录整个挂进去,宿主专用的设置会跟着进去(实测:claude 作用域 `settings.json` 里给宿主守护进程用的 `https_proxy` 在容器里指向不存在的地址,本轮当场 `Connection refused`)。
158
- - **容器轮是 exec-only、且没有成员通道。** 长驻驱动跑的是宿主上的常驻进程,正是 `exec` 目标要替换的传输;成员桥是宿主 unix socket,其 MCP 声明还带着宿主 node 路径。两者都是明确放弃,不是没接上。
159
- - **resume 由调用方重复同一个目标。** 记录里的锚是宿主 `cwd`,换了容器它仍然相等——这一条记录抓不到。
160
-
161
- ### 命名 scope:一家多份作用域目录
162
-
163
- 缺省作用域目录是 `<homesRoot>/<家名>`——逐字节还是那一份。给一个**名字**(只允许 `[a-z0-9-]`,是名字不是路径)就得到与它**同级**的另一份:`<homesRoot>/<家名>@<名>`。不嵌在缺省目录里面,因为那份目录归各家 CLI 自己管,往里塞第二棵状态树迟早被它自己清掉或读串。
164
-
165
- - **惰性建立。** 目录在第一次被点名时创建(`/<家> login|status|sessions|logout --scope <名>`、带 `scope` 的委派、评测的挂载源都算点名):`mkdir` 0700,然后跑该家自己的 provision(codex 的 `config.toml`、kimi 的 provider/model 配置与权限、claude 的作用域目录、dsh 的子 profile)。缺省目录的 provision 时机不变——仍由各 harness bundle 的 apply 负责。
166
- - **刻意 provision 一个 scope:`provisionScope(家, 名, 选项)`。** 上面那次是「有人点了这个目录的名」的副作用:每进程每 (家, scope) 只跑一次、不带选项、失败只留一行日志。`provisionScope` 是刻意的那一次——它**等**(并且先等掉上面那次可能刚启动的那一遍,两个写者写的是同一批文件)、**抛**(问了要有答复,失败不能读成成功),并把该家的**回读**交回来。选项目前只有一项 `preset`:preset 是**受试对象**的属性而不是部署的,一条评测条件说「这个 scope 组这个 preset」时,知道答案的是条件文档,而插件 config 一个实例只有一份。只有 dsh 读这一项;另三家收下参数并忽略,provisioning 因此逐字节不变。
167
- - **凭证不复制。** 新 scope 是空的:`status` 报 `credentialState: absent`,委派按今天的规则失败,要用它先 `/<家> login --scope <名>` 登一次。claude 的 keychain 项按配置目录路径哈希,命名 scope 因此自动拿到自己的项——这是四家里唯一天然按路径安全的部分。
168
- - **一切按目录走。** 该 scope 的会话记录、`delegations.jsonl`、effective-settings 快照(harness 的 `effectiveSettings(homeDir)` 收目录参数)、CLI 版本探测、登录态与委派回读,全部落在它自己的目录里。委派记录带 `scope`,resume 轮拿它当锚:换了 scope(或首轮有、这轮没有)在进程启动前就拒绝——继续同一个 CLI 会话却换了账号,是事后修不回来的那种错。
169
- - **边界(明确放弃,不是没接上)。** 带 scope 的委派**只走 exec**:常驻驱动(codex 的 app-server、claude/kimi 的 ACP、子 dsh 的 serve)按成员绑的是缺省作用域目录,撞上 live 直接拒绝而不是悄悄降级。kimi 的成员桥声明写在作用域目录的 `mcp.json` 里、`member-bridge.sock` 又是 homes 根级单例,所以带 scope 的 kimi 轮不带成员通道。dsh 的子 profile 随目录走,不需要特殊处理。
170
-
171
- ### 活跃委派 registry 与 `/local-agent stop`
172
-
173
- registry 另持有**活跃委派 registry**(以 dsh 子会话 id 为键的在飞 run 表):facade 启动的 run(`start`/`resume`——成员 composer、room 等程序化入口)与家族工具直接 `ctx.subagents.startActivation()` 启动的 run(经 `trackDelegationRun` 登记)都落在同一张表里,条目在 run 结果 settle 时自清。`/local-agent stop <childSessionId>` 命令按这张表取消在飞的委派——语义对齐官方 `subagents.interrupt(targetSessionId)`:fire-and-return(发出取消信号即回复),目标缺席(未知子会话或无在飞 run)是显式说明的 accepted no-op,而非报错。这为 taskpilot 等表面提供了停止按钮的落点:对没有 live agent 的一次性子代理行,按钮改发 `/local-agent stop <childSessionId>`,local-agent 缺席时降级为无法停止的明确报错。
174
-
175
- ### Model Experience
176
-
177
- **模型看到什么**——registry 本身不提交任何内容:`/<harness>` 命令回复与 `/local-agent list` roster 文本都是用户可见的命令文本,绝不是模型 prompt。模型可见效果只从 harness bundle 挂载 subagent provider 开始;父级随后通过 subagent 工具结果看到子会话的最终回答。
178
-
179
- **Token 影响**——命令发现、执行与回复文本不产生任何模型 token。委派 token 属于 harness bundle 的 provider,由它为一个独立的子上下文付费。
180
-
181
- **KV Cache 影响**——registry 元数据与命令回复从不进入模型请求、不影响其缓存;被委派的子会话独立拥有自己的缓存。
38
+ ## Compatibility
182
39
 
183
- </details>
40
+ 需要 DSH 0.1.5-rc.1 或更新版本,已在 0.2.1-rc.1 上验证。
184
41
 
185
- ## 开发
42
+ ## 给开发者
186
43
 
187
- 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo(`packages/local-agent`)。问题与贡献请移步该仓库。
44
+ - 各家怎么注册进来、委派调用的选项、模型的取值顺序、命名 scope(一家多份登录)、容器内委派、成员通道与实时输出等,都在[完整参考](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/local-agent/docs/reference.md)里。
45
+ - 单独安装时,底座要和至少一家一起点名:`dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-kimi`(只装某一家不会自动带上底座)。
46
+ - 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins),问题与贡献请到该仓库;变更见 [CHANGELOG.md](CHANGELOG.md)。