@khorsheed/dsh-local-agent-claude-code 0.2.0-rc.1 → 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,4 +1,31 @@
1
1
  # 变更记录
2
+ ## 0.2.0(2026-10-26)
3
+
4
+ rc.3 之后到正式版的变化:
5
+
6
+ - **一次 API 调用就是一条请求、用量只算一次**:Claude 对一条消息的每个内容块各发一个事件并重复带同一份用量,子会话镜像原来把一次调用拆成好几条、每条都显示同样的数字;现在按消息 id 合成一步,用量挂在这次调用的最后一行,且每次调用都带自己的用量(原来只有整轮最后一条有)。live 路等调用结束(`message_delta`)后才落盘,用最终用量,跨工具结果的调用也不再重复计数。Context Lens 里每次请求的 token 因此准确。
7
+ - **修复工具发现循环在子会话里显示不全**:`ToolSearch` 的结果原来接不上,收尾时多出一个假的「已中断」占位,整轮写入还挤在结尾一次落盘;现在如实折叠。
8
+ - **修复子会话步骤乱序、重启后被判损坏**:live 镜像改为写入时才编号、一次只开一个步骤,先开始的先落盘。
9
+ - **修复取消后日志不可读**:取消后迟到的 CLI 中断提示不再写到这一轮结束之后。
10
+ - CLI 注入的文本(技能正文、命令输出)不再被当成回答镜像出来。
11
+ - 协作间的桥接调用最长可等 30 分钟(`MCP_TOOL_TIMEOUT`),供成员等你回答问题。
12
+ - **README 重写**:改为大白话说明,完整参考移到 `docs/`。
13
+ - **版本号去掉 rc**:社区包不再发 prerelease 版本(docs/publishing.md「版本号规则」),家族各包同版 0.2.0;本节原记为 0.2.0-rc.4。
14
+ - **缓存命中 0 与未上报可区分**:用量带任一缓存字段时 `cacheReadTokens` 恒写(读到 0 就写 0)——读日志的一方(Context Lens)不再分不清「命中 0」与「没上报」。
15
+ - **子会话日志写入 `request/header`**:子会话的配置事实(模型 + 推理强度)原来无处记录,Context Lens 整卡「未记录」。现在每轮写入宿主原生 `request/header`(只在变化时记录)——exec 路用 init 事件的观测 model(回退到启动参数),live 路用会话配置;claude 的 wire 从不携带 effort,provider 自己设置的 env 参数即权威来源。
16
+ - **修复成员推理强度整体不可用**:端点目录的 effort 解析只读模型条目的顶层 `effort` 字段,而端点把它嵌在 `capabilities.effort` 下(openproxy 中继 2026-10-22 实测形状;官方 API 同形)——目录条目自此重新带上推理档位,成员对话框的推理强度恢复可选(两个字段位置都认,顶层优先)。
17
+ - **成员会话的模型对话框与「可用模型」短名单严格一致**:成员的模型目录原来会混入配置与历史附加项,与卡片短名单不一致。现在成员对话框的候选即短名单(按名单顺序),名单外只保留成员当前绑定的模型(避免既有绑定显示空白);卡片的添加对话框只列端点真实供应的模型,配置/历史附加项不再混入。
18
+
19
+ ## 0.2.0-rc.3(2026-10-22)
20
+
21
+ (版本号说明:家族的 dsh-headless 包 0.2.0-rc.2 版本号已被 npm 发布流水线占用(重复利用被 409 拒绝:「Cannot stage previously published version」——曾发布后被撤回或曾被暂存,外部无法区分,结果相同:该号不可复用);为守住家族「同一版本线」纪律(check:release-groups),本波家族整体上移 rc.3,内容即 rc.2 计划稿。)
22
+
23
+ - **BREAKING:OAuth 网页授权整体移除,端点凭据成为唯一认证方式**。部署级凭据为插件配置 `baseUrl` / `apiKey` / `authToken` 三元组(`apiKey` 以 `ANTHROPIC_API_KEY`、`authToken` 以 `ANTHROPIC_AUTH_TOKEN` 发出,中转的两种认法都兼容);每个作用域可再以 `<作用域目录>/endpoint-auth.json`(0600)按字段覆盖——优先级就是「scope 文件 > profile 配置」。**升级前必须配好端点凭据**,否则升级后读作未认证(README 带迁移说明);官方订阅(Pro/Max)的 OAuth 路径随之消失,失效 OAuth 残留由 provision 启动时清扫,换 key 下一轮生效、无需重载。
24
+ - **设置卡新增端点凭据表单与「验证端点并获取模型」按钮**:卡片编辑的正是默认 scope 的 endpoint-auth.json;验证即带解析出的凭据探测 `GET <baseUrl>/v1/models`,保存成功翻转认证状态点。凭据等级语义不变:key 在场读 present-unverified,跑通一轮升 verified,一轮 401 打 rejected,重写凭据文件即恢复手势。
25
+ - **「可用模型」有序短名单取代默认模型字段**:持久化为 `<作用域目录>/models.json`,首项即默认模型;添加弹窗的候选全部来自端点当前供应的目录(勾选添加、拖拽排序),任何本地残留别名都混不进选择器。名单为空时完整回退既有链;成员的模型在首轮即绑定,改名单只影响新成员,不会换掉对话中的成员。
26
+ - **模型目录改为端点供给**:目录 = 所配端点实际供应的模型(探测预算 20 秒,传输错误保留上次成功数据)——本地 CLI 的词汇表不再给一把失效 key 打掩护。
27
+ - **设置卡并入「本机 Agent 委派」家族卡**:Plugins 页的 per-harness 卡折叠为一张家族卡、按 harness 分节呈现,本包卡读作「本机 Claude Code 委派」;家族卡随后修复为无条件注册——原先的命名空间门控让它在 prod 上永不挂载(2026-10-21)。与旧版家族核混装时回落为独立卡。
28
+ - **适配宿主 0.2.1 线**:委派链路迁移到 subagent `startActivation`(宿主 0.2.1 删除旧的 `ctx.subagents.start()`);加宽 `@deepseek-ai/dsh-*` peer 区间以覆盖宿主 0.2.1。
2
29
 
3
30
  ## 0.2.0-rc.1(2026-09-30)
4
31
 
package/README.en.md CHANGED
@@ -2,128 +2,46 @@
2
2
 
3
3
  English | [中文](README.md)
4
4
 
5
- Delegate coding tasks to the Claude Code on this machine — thinking, tool calls, and replies stream back live, and your personal `~/.claude` is never touched.
5
+ Hand coding tasks to the Claude Code on your machine: its thinking, tool calls, and replies stream back live, and you can keep the conversation going.
6
6
 
7
- Any dsh agent preset that names this package's `./tool` composition entry (e.g. dev mode) can delegate: the child session is visible end to end — abortable, resumable — and every round's accounting (the actual model, token usage, tool-call count) reads back for real. Login is scope-isolated: the plugin only ever manages the one directory `$DSH_HOME/local-agent/claude-code`, so your personal installation and credentials stay out of it from start to finish. The Claude Code harness of the [local-agent family](../local-agent/README.md).
7
+ Getting Claude Code to help used to mean opening a terminal, pasting things in, and carrying the result back yourself. With this plugin you say "give this to Claude Code" in a session, and the agent hands the task to Claude Code on your machine. Its thinking, tool calls, and replies show up live in a sub-session, and the result comes back to you. The tokens each call used and the model that actually ran are recorded as real numbers. Claude Code runs in its own separate folder; your existing `~/.claude` config and sign-in are never touched.
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 family cards under Settings → Plugins → plugin configuration, the Claude Code row's status dot showing its auth state">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-claude.png" width="560" alt="The expanded Claude Code section: Claude Code signed in with its version, endpoint, API key, Auth Token and the available-models list">
10
10
 
11
- ## Features
11
+ ## What it does
12
12
 
13
- - **Per-session delegation grant** — the `subagent_claude_code` tool is this package's `./tool` composition entry, granted to the sessions of an agent preset that names it; no per-preset variants.
14
- - **Scoped home** — every Claude process runs with `CLAUDE_CONFIG_DIR` under `$DSH_HOME/local-agent/claude-code`; your personal `~/.claude` stays out of it.
15
- - **Guided login, one command** — `/claude-code login` answers with the exact command to run in your own terminal (claude ≥2.1 prints its OAuth URL only on a TTY, so the host no longer spawns and scrapes) and watches the scoped home for the credential; `sessions`/`status`/`logout` and a Settings → 本地 Agent panel complete the family.
16
- - **Resume a delegation** — pass back the result's `resume` handle to continue the same Claude session, with per-round accounting.
17
- - **Live stream mirror** — the child session mirrors Claude's thinking, tool calls, and replies live; aborting keeps the partial transcript and real token usage.
18
- - **Model readback and per-cell working directory** — every settled round reads back the model from stream-json's system/init into the delegation record; orchestrators pass a `cwd` per cell, and a resume in a different directory is rejected.
19
-
20
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-claude-code-card.png" width="640" alt="the expanded Local Agent · Claude Code settings card: the authentication block, the default-model picker, and the resident-mode (live) switch">
13
+ - **Hand it off in one sentence**: tell the agent which part goes to Claude Code; it writes up the brief, and the result comes back to your session.
14
+ - **Streams back live**: thinking, tool calls, and replies appear in the sub-session as they happen; stop midway and the finished part and its usage are kept.
15
+ - **Clear accounting**: each call's usage is recorded on its own in the sub-session, so Context Lens shows where every call's tokens went.
16
+ - **Keep talking, no cold start**: have the agent follow up, or open the sub-session and message it yourself; it's the same Claude Code session.
17
+ - **Paste a key and go**: enter the endpoint and API key (or Auth Token) on the settings card, press "Save credential", then "Verify endpoint & fetch models" to check it connects.
18
+ - **Pick your models**: under "Available models", tick and drag to reorder; the top one is the default. You can also switch model and reasoning effort inside a sub-session.
19
+ - **Resident mode is faster**: turn on "Resident mode (live)" and the Claude Code process stays up; stopping doesn't kill it, and after a crash it resumes the same session.
21
20
 
22
21
  ## Install
23
22
 
24
- Prerequisites: a running dsh profile, and the Claude Code CLI (`claude`) on `PATH` — the plugin does not install it. Install the family core together with this bundle (`dsh plugin add` reconciles only *direct* dependencies, so name both):
23
+ First install the Claude Code CLI yourself (`claude` runs in your terminal); the plugin doesn't install it. Then in DSH, open Plugins → Add plugin, enter this package name, and press Install:
25
24
 
26
- ```sh
27
- dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-claude-code
28
25
  ```
29
-
30
- Restart the web instance to activate, then run `/claude-code login` once from a session. Uninstall:
31
-
32
- ```sh
33
- dsh plugin --profile web remove @khorsheed/dsh-local-agent-claude-code
26
+ @khorsheed/dsh-bundle-local-agent
34
27
  ```
35
28
 
36
- The scoped home is kept on uninstall so a reinstall needs no fresh login; delete `$DSH_HOME/local-agent/claude-code` to remove every trace.
37
-
38
- ## Config
39
-
40
- Optional fields on the bundle row:
41
-
42
- - `permissionMode` — `skip` (default) passes `--dangerously-skip-permissions` so the child can write files without an approval prompt; `normal` runs without it, so approval-requiring actions (e.g. writing files) are denied.
43
- - `model` — every delegation round starts the CLI with it (`claude -p --model <model>`); absent passes no model flag at all. See below.
44
- - `baseUrl` — sets `ANTHROPIC_BASE_URL` for the child CLI (e.g. a self-hosted router or proxy); absent, the child inherits the host process environment. Config wins over environment.
45
- - `proxyUrl` — an HTTP proxy for the child CLI's own traffic (model calls AND OAuth refresh), provisioned into the scoped `settings.json` env block; for when the host process environment carries no proxy (a supervisor-spawned instance does not inherit your shell exports).
46
- - `live` — live driver: one resident stream-json process per member (`--input-format stream-json`), one stdin message per round (runtime-level graceful control interrupt, same-shape push stream); off — or a channel that cannot come up — means the one-shot `claude -p` path.
47
- - `liveIdleMs` — idle lifetime of a resident runtime before reclaim (default 30 min).
48
-
49
- > **Migration note** — live runs always consume incremental output. The old `liveMirrorGranularity: event | token` key is accepted for existing profiles but ignored; changing it never changes a running process. Exec remains available for evaluation. Final provider items remain authoritative, including tool history and usage.
50
-
51
- ### Default model (`model`)
52
-
53
- **Absent = today's behavior.** Without this key the plugin adds no model flag to the argv at all: the scoped `settings.json`'s `model` decides which model runs, and claude's own default decides when that is missing too (the plugin never guesses what it is).
29
+ Claude Code comes with the 「多Agent协作」 (multi-agent) suite; all the local agents share one core, so on its own it won't work. If you mostly write code, the Dev Suite `@khorsheed/dsh-dev-suite` includes it too. Restart DSH, then on the Plugins page open the 「本机 Agent 委派」 (Local Agent Delegation) card, expand the Claude Code section, and fill in the endpoint and key.
54
30
 
55
- **Set = every delegation round starts the CLI with it.** All four argv variants — fresh and resume, one-shot and resident (`live: true`) — carry `--model <model>`, placed before the member channel's `--allowedTools … --`: that flag is variadic and everything after its `--` is the task text.
31
+ Handing work to Claude Code happens in a mode that grants delegation, such as "开发模式" (dev mode): start a new session, pick that mode, and tell the agent "give this to Claude Code". Not seeing it in other modes is expected.
56
32
 
57
- One-shot (exec) rounds never rewrite the scoped `settings.json`: `--model` overrules it per round and the file stays exactly as you edited it. A resident (live) spawn scratches the member's effective model into that file (a `--resume` reattach honors the file's model over the `--model` flag) and restores the previous value before a spawn that binds none; the "CLI config" layer that status and the settings card report is always the value you configured, never a member's scratch.
33
+ ## Known limitations
58
34
 
59
- The settings card's "Default model" writes the provider setting for subsequent rounds. Its shared picker displays the scoped model directory, discovery source and completeness, plus an explicit model-ID input when needed. Clearing the selection follows the effective configuration/default chain. Saving does not interrupt an active round and requires no reload. If the shared picker is unavailable, the card retains its text-input fallback. Per-member model and effort changes use the durable controls described below.
60
-
61
- The model surface now also reads Claude's native `initialize` directory, preserving selection aliases, display labels, resolved names and supported effort levels. It shares the core cache and refresh subscription, retains successful data after a failed refresh, and labels historical suggestions separately. This scoped control probe sends no user task and writes no model settings. An older CLI without this directory reports unsupported; native candidates do not establish account entitlement. The shared model picker and member effort controls are connected. Busy selections apply at the next complete turn boundary, including tool continuations; core owns current/pending state, cancellation and retry, while frozen evaluation members reject changes.
62
-
63
- **A delegation's own model outranks this key.** An orchestrator may name the model for ONE delegation through the facade's `DelegationCallOptions.model`, which sits above this key (the layer order is in the family core README). The first round's request is recorded and every resume round re-requests it — `resume` takes no model of its own. In resident mode a fresh delegation's model binds as the member's start model at the runtime's spawn: a runtime bound to a different model retires first, and the same CLI session resumes on the new process.
64
-
65
- **Session-level member switching.** The member composer's model picker writes a **session-level override** through the family gateway's `setMemberModel` — it outranks the delegation model and this key, lives in memory only, and deliberately neither survives a host restart nor enters the settings. A switch is refused while the member has a round in flight; with the live driver on, switching to a different model retires the member's resident runtime so the next round respawns onto the new model while resuming the SAME CLI session (the conversation carries over), and a same-model switch is a no-op.
66
-
67
- **This is not an evaluation gap.** A run freezes its condition at setup: change the key mid-run and the next round's model read-back sees declared ≠ observed and fails the run as misattributed (frozen decision 5). "A new run follows the new value, a running one is never switched underneath you" is the design, not an oversight.
68
-
69
- ⚠️ `skip` has no OS-level sandbox — the child can write anywhere the host user can, including outside the workspace — and the scoped login's OAuth token is sent to whatever `baseUrl` points at. Prefer `normal` when a delegation needs confinement; point `baseUrl` only at endpoints you trust.
70
-
71
- **Evaluation snapshot (`effectiveSettings`).** The harness declares a live-read snapshot of its fairness-relevant settings for the evaluation condition hash: drive (exec/live), the permission mode (`skip`/`normal`, plugin config), whether a non-default endpoint is in force (following the provider's own resolution order — the config item wins over the host environment's `ANTHROPIC_BASE_URL`; hostname only), and the configured model (the plugin config's `model` key first — it overrules the file on every argv — then the scoped `settings.json`'s `model`; the CLI's own default model stays the CLI's business and is never guessed — neither key, no field), and the CLI version (`claude --version`, cached against the executable's path + mtime; absent when the CLI cannot be asked). `/claude-code status` and the `LocalAgentStatus` Remote attach the same snapshot.
35
+ - Only an endpoint plus key is supported; there is no web sign-in.
36
+ - With a wrong key, Claude Code keeps retrying first (about 8 minutes in testing) before it fails, and the card shows the credential as rejected only after that round ends.
37
+ - The `/claude-code` commands only work in web sessions.
72
38
 
73
39
  ## Compatibility
74
40
 
75
- - npm release line (`@deepseek-ai/dsh@0.1.5-rc.1`): ✅ public API compatible. Live generation uses the local-agent transient Remote and public Conversation nodes; incremental durable recovery checkpoints handle persistence, and native final messages retain transcript and usage semantics. Browser P95 acceptance is tracked by the room coordinator proposal. minHost is 0.1.5-rc.1 — older hosts stay on the previous release line.
76
- - source line (deepseek-harness master): ✅ (verifiedHost: 0.1.5-rc.1)
77
-
78
- ## Known Limitations
79
-
80
- - **macOS logout is config-only** — the keychain entry is left for the CLI's own `claude auth logout`; a fresh login rewrites the same slot, so the stale entry is harmless.
81
- - **One grant cannot be shared between a host round and a container round** — the two use different stores on macOS, and a rotation on either side invalidates the other's token family. A containerized condition takes its own named scope; see Container delegation.
82
- - **Linux credentials are not isolated** (upstream bug #47661) — claude reads the default `~/.claude/.credentials.json` even when the scoped home has none.
83
- - **Interactive-session records may be missing** — under a custom `CLAUDE_CONFIG_DIR` the CLI may not write transcripts (upstream behavior).
84
- - **Headless caveat** — `/claude-code` commands need a Web session; `--profile headless` delegates through a composition that mounts the tool row.
85
-
86
- ## How it works
87
-
88
- <details>
89
- <summary>Internals (click to expand)</summary>
90
-
91
- **Model readback and the cwd override.** After every settled round the provider reports the actual model from the stream-json `system` (init) event's `model` field (e.g. `claude-opus-5[1m]`, verbatim — context-variant suffixes included) through the `settled` run-progress event, and merges it into the delegation record's `observedModel` field; absence is recorded, never guessed. When no layer and no record names a model, the 默认 surface's last-observed hint reads the member's OWN transcript (`projects/<cwd-slug>/<sessionId>.jsonl`, or the tree's newest for the memberless card) — the history backstop for rounds that predate the settle channel; read-only, never a write into `delegations.jsonl`. An orchestrator may also pass a per-round working directory through the facade's `DelegationCallOptions.cwd` (recorded as the record's `cwd`); when a resume round resolves to a different directory than the recorded first round, it fails loud before any process spawns — a CLI session continues in the directory its first round ran in.
92
-
93
- **Tool-call accounting.** Every settled round also reports how many tool calls it made, on the `settled` run-progress event (`toolCalls: { count, byName }`). The count is taken in the `tool_use` branch the stream fold ALREADY walks, and `byName` keys are the block's own `name` (`Bash`, `Read`, `TodoWrite`, or an MCP tool's full `mcp__server__tool`), carried verbatim and never normalized across harnesses. `TodoWrite` counts too: the fold diverts it into the todo snapshot instead of the transcript, but the CLI still called it. One accounting per round, never cumulative; a round that called nothing carries no field at all (absent is not zero). Measured: a "read two files, then list the directory" round reads back `{count: 3, byName: {Read: 2, Bash: 1}}`, matching the mirrored tool cards one for one.
94
-
95
- **Named scopes.** `/claude-code login --scope <name>` opens a second `CLAUDE_CONFIG_DIR` at `<homesRoot>/claude-code@<name>`, and the login argv is built from the directory BEING logged in (`env CLAUDE_CONFIG_DIR=… claude auth login` — that assignment outranks the spawn env, so a hard-coded default would authorize the wrong directory under `--scope`). claude's keychain item is keyed by the config directory's path, so a named scope gets its own keychain item for free — the one credential store among the four harnesses that is path-isolated by nature. A scoped delegation runs `claude -p` against that directory, reconciles keychain↔file there, and reads back from there; exec-only.
96
-
97
- **Container delegation.** An orchestrator may run the round inside an **already-acquired container** through the facade's `DelegationCallOptions.exec` (`{ container, workdir, env? }`): the argv becomes `docker exec -w <workdir> [-e NAME…] <container> claude -p …` and everything else (stream-json parse, settle, recording) is byte-for-byte unchanged. `env` must name the in-container `CLAUDE_CONFIG_DIR`, and the caller bind-mounts the host scoped home read-write onto that path. Measured on claude 2.1.272 inside the evaluation image, the in-container CLI reads AND writes `<CLAUDE_CONFIG_DIR>/.credentials.json`, and its whole state tree (`projects/`, `sessions/`, …) lands there too; the older upstream-#47661 behavior (Linux writes the scoped dir but reads the default home) does not describe that version. The credential reconcile before every spawn still runs against the **host** scoped home, so a refreshed grant reaches the container through the mount — and it is **newer-wins, not a mirror**, because the unit is a SECOND WRITER to that same file. The CLI inside a unit has no keychain, so it rotates the grant in the mounted file; an unconditional keychain→file mirror would then hand the next run a refresh token the unit already spent, the endpoint rejects it, and the CLI answers by CLEARING the file — which logs the whole instance out, because that file is the instance's credential. The reconcile therefore writes the keychain blob only when it is not older than the file's (access-token expiry decides; an undefined expiry on either side keeps the keychain writing, and an unusable file never wins). The mount stays read-write for a second reason as well: the round's transcripts land in `projects/` under the same directory, and the evaluation's model read-back parses them from the host side of that mount. A container round always takes the exec one-shot and declares no member bridge.
98
-
99
- **A container round needs its OWN scope, and the discipline is not optional.** claude is the only harness here with two credential stores, and measured on macOS 2.1.274 they do not have one reader: the HOST CLI reads and writes the KEYCHAIN, while the CLI inside a unit — Linux, no keychain — reads and writes the mounted file. One grant refreshed from both is one grant refreshed twice, and the endpoint rotates single-use refresh tokens by invalidating the whole token FAMILY: whichever side did not refresh last is left holding a dead token. Measured end to end — a container round rotated the file, and the next host round answered `OAuth session expired and could not be refreshed` although the file's own access token was still hours from expiry, recoverable only by a fresh login. Newer-wins keeps the FILE side correct, which is what the unit and every status probe read, but nothing can write the unit's rotation back into the keychain (`security add-generic-password` would put the secret in argv, so it is refused). So the container side and the host side must be two INDEPENDENT grants: give every containerized claude condition its own named scope, authorize it once with `/claude-code login --scope <name>`, and never run a host-side claude round in that scope. The evaluation refuses a plan that breaks this before it acquires a unit. **Mind the host-only entries in the scoped `settings.json`**: measured, the `https_proxy` there — meant for the host daemon — points at nothing inside the unit and the round dies with `Connection refused`. The caller stages what it mounts.
100
-
101
- ```
102
- src/index.ts harness registration, /claude-code command family, config schema
103
- src/claude-cli-provider.ts one-shot provider: spawn, stream mirror, turn/token accounting
104
- src/provision.ts scoped-home provisioning and file-based logout
105
- src/records.ts auth probe and session-record listing from the scoped home
106
- src/tool.ts the `./tool` composition entry: the `subagent_claude_code` delegation tool (preset-granted)
107
- ```
108
-
109
- The bundle patch registers the `claude-code` harness into the family core (`@khorsheed/dsh-local-agent`, declared as a dependency — the claude bundle deliberately does not re-insert the core row, which would mount it twice) and mounts the `claude-local` provider, which spawns `claude -p --verbose --output-format stream-json` under the harness's scoped home. The `subagent_claude_code` tool is the family-owned tool (this package's `./tool` composition entry, sharing the family core's `./tool-factory` implementation), granted per session by an agent preset that names the row: the official subset (`description`/`prompt`) plus an optional `resume` parameter.
110
-
111
- **Scope isolation.** On macOS the real credential lives in the OS keychain under a hashed entry (`Claude Code-credentials-<sha256(configDir)[:8]>`) keyed to the scoped home path — file-based logout cannot reach it, but a fresh login rewrites the same slot. Which store the CLI itself uses is VERSION-dependent and was measured, not assumed: 2.1.236 wrote the keychain and read the file (the split this mirror exists for), while 2.1.274 on macOS reads and writes the keychain, and 2.1.272 on Linux reads and writes the scoped file. The file therefore stays authoritative for this package's own probes and for a container round; the keychain is what a host CLI on current macOS actually uses. Auth detection is a light file check of the scoped `.claude.json`'s `oauthAccount` — the CLI is never spawned just to probe. The probe also reconciles the two stores, so it obeys the same newer-wins rule as the spawn sites: it runs on every status read and every readiness check, and an unconditional mirror there would undo a containerized round's refresh just as surely.
112
-
113
- **Session records.** `/claude-code sessions` lists the scoped home's `projects/<cwd-slug>/<uuid>.jsonl` session files — this agent's delegations, never your personal sessions. The slug is a lossy encoding of the workspace path, so the listed `workDir` comes from the file content (the first `user` event's `cwd` field), never the directory name. The Settings panel shows the same listing narrowed to the current workspace.
114
-
115
- **Resume security.** The handle never rides the prompt: it is read only from the `resume` parameter, and the localAgent registry resolves it only for the same parent session and provider that recorded the delegation — a forged handle (unknown child session, another parent's session, or the wrong provider) is rejected before any CLI process starts.
116
-
117
- **Stream mirror & accounting.** `thinking` blocks fold to `reasoning` blocks, `tool_use`/`tool_result` to tool lines (`[工具 Bash] <command> → output`), reply `text` to assistant text; resumed rounds append their own turn without duplication. Edit-family calls (Edit/Write/MultiEdit/NotebookEdit) fold into ApplyPatch-style cards — the path on the args line, the actual edit (old/new strings or the whole file body) as the card body, the same shape codex's fileChange mirror uses; server-side tools (`server_tool_use` web_search/web_fetch) pair with their results by id into visible WebSearch/WebFetch cards; a `redacted_thinking` block folds to a visible placeholder think line (the content is server-encrypted by design — the line says so); every other tool's input keeps its one-scalar summary, with a truncated JSON summary as the fallback instead of dropping. Every mirrored step is wrapped in a `step/start`–`step/end` pair at its own (turn, step) — the host's live conversation view registers steps only on those boundaries, so a boundary-less assistant message stays unrendered until a full rebuild. The provider opens `turn/start` at spawn and closes `turn/end` on settle — including failure and cancellation — so `subagentTiming` duration equals actual CLI runtime and no window stays open; the final `assistant/message` carries token usage parsed from the JSON result (`input_tokens`, `output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens` — Anthropic reports each separately).
118
-
119
- **Model experience.** The child is a fresh one-shot `claude -p` in the delegating session's workspace; the parent submits only the standalone task text and sees only the final answer or the exact error, plus the resume self-description on a fresh delegation — Claude commentary, tool activity, and workspace diffs are not copied over. Child tokens never enter the parent's context; the child pays for its own Claude context and turn (cache reuse depends only on the scoped installation's provider, model, and history); the parent's KV cache is unaffected.
120
-
121
- **Live driver (`live: true`).** Replaces the per-round spawn: the member's first delegation brings up one resident stream-json process (`claude -p --verbose --input-format stream-json --output-format stream-json`), the first stdin `user` message triggers `system/init` (the server-assigned session id becomes the delegation record's `cliSessionId`), and every later round is one stdin message. The event stream is byte-identical in shape to exec's stream-json, so each turn folds through the same `ClaudeStreamParser` (the last line held back until `result` carries usage, as in exec), and `cancel` lands as a `control_request interrupt` — the process survives and the session stays continuable. Reclaim is stdin EOF → SIGTERM; after a crash the next round reattaches with `--resume <session_id>`. A runtime binds the member's effective model at spawn (session-level override → delegation model → settings key) and scratches it into the scoped `settings.json` (a `--resume` reattach honors the file over the flag); a runtime whose bound model no longer matches the round's resolution retires before the respawn. A `permissionMode: normal` spawn omits `--dangerously-skip-permissions`, so the CLI's server→client control requests (the `can_use_tool` approval surface) are auto-answered with allow by the driver — a subagent has no approval surface, matching exec's effective behavior under skip; any other subtype gets an error answer instead of a silent hang. The auth discipline is byte-identical to exec: only the scoped `CLAUDE_CONFIG_DIR` (plus an optional `baseUrl` override) is injected — the global `~/.claude` is never touched and no `auth` verb ever runs.
122
-
123
- **Config notes.** The host environment is a startup-time snapshot — a later `export ANTHROPIC_BASE_URL` in another shell has no effect until the host restarts; check the host's environment, not your current shell, when diagnosing routing. Every delegation logs the effective endpoint at info level (`subagent-claude: delegating via <endpoint>`), and a failed run's error text names it. `skip` is the default because a one-shot CLI subagent has no approval surface; scope the host's own sandbox (e.g. run the host inside a sandboxed workspace) when a delegation needs confinement.
124
-
125
- </details>
41
+ Needs DSH 0.1.5-rc.1 or later; verified on 0.2.1-rc.1.
126
42
 
127
- ## Development
43
+ ## For developers
128
44
 
129
- Part of the [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo (`packages/local-agent-claude-code`). Issues and contributions welcome there.
45
+ - Config keys, the available-models list, where the endpoint credential is stored, the live stream mirror, and usage read-back are in the [full reference](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/local-agent-claude-code/docs/reference.en.md).
46
+ - To install it alone, name the core too: `dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-claude-code`; the delegation tool is granted by a mode and won't appear unless a mode names it.
47
+ - 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-claude-code/README.en.md
5
- packages/local-agent-claude-code/README.en.md: 7d04fd0b7905b0aa38d38d0ba4caaddce6cd593b
6
- packages/local-agent-claude-code/README.md: b1b767a4fb9b032308dc2e61bc8656d758073bdb
5
+ packages/local-agent-claude-code/README.en.md: 22413d23e74d101e43dfe95b710e6af81c6701a7
6
+ packages/local-agent-claude-code/README.md: 1355e6937b403ddd340fc10a5c0f0e0a76d4d88f
package/README.md CHANGED
@@ -2,128 +2,46 @@
2
2
 
3
3
  [English](README.en.md) | 中文
4
4
 
5
- 把编码任务委派给本机的 Claude Code——思考、工具调用与回复实时流回,你的私人 `~/.claude` 分文不动。
5
+ 把编码任务交给你电脑上的 Claude Code:思考、工具调用和回复实时流回来,还能接着聊。
6
6
 
7
- 点名本包 `./tool` 组合入口的 agent preset(如开发模式)即可发起委派:子会话全程可见、可中止、可续聊,每轮的账目(实际模型、token 用量、工具调用数)都真实回读。登录是作用域隔离的——插件只管理 `$DSH_HOME/local-agent/claude-code` 这一份目录,你的个人安装与凭据自始至终置身事外。[local-agent 家族](../local-agent/README.md) 的 Claude Code harness。
7
+ 想让 Claude Code 帮一把,以前得自己开终端、复制粘贴、再把结果搬回来。装上之后,你在会话里说一句「这个交给 Claude Code」,agent 就把任务交给本机的 Claude Code;它的思考、工具调用和回复实时出现在子会话里,跑完把结果带回来。每一次调用用了多少 token、实际跑的是哪个模型,都按真实数字记下。Claude Code 在自己单独的目录里运行,你电脑上原来的 `~/.claude` 配置和登录一点不碰。
8
8
 
9
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/08-local-agent.png" width="640" alt="设置 → 插件 → 插件配置里的 Local Agent 家族卡片,Claude Code 一行的状态点显示授权状态">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-claude.png" width="560" alt="展开的 Claude Code 一节:已登录的 Claude Code 和版本号,端点地址、API Key、Auth Token 和可用模型名单">
10
10
 
11
- ## 特性
11
+ ## 能做什么
12
12
 
13
- - **按会话授予的委派**——`subagent_claude_code` 工具是本包的 `./tool` 组合入口,由点名它的 agent 预设授予该预设的会话,无需逐 preset 变体。
14
- - **作用域隔离**——每个 Claude 进程都以 `$DSH_HOME/local-agent/claude-code` 为 `CLAUDE_CONFIG_DIR` 运行;你的个人 `~/.claude` 置身事外。
15
- - **一条命令完成登录指引**——`/claude-code login` 在会话中给出需要在你自己终端运行的完整命令(claude ≥2.1 只在 TTY 打印 OAuth URL,宿主不再 spawn 抓取),并监听作用域目录识别登录完成;`sessions`/`status`/`logout` 与 设置 → 本地 Agent 面板构成完整一族。
16
- - **续聊委派**——把结果自述的 `resume` 句柄传回,即可继续同一个 Claude 会话,并按轮记账。
17
- - **实时流镜像**——子会话实时镜像 Claude 的思考、工具调用与回复;中止会保留部分转写与真实 token 用量。
18
- - **模型回读与独立工作目录**——每轮从 stream-json 的 system/init 回读实际模型,写进委派记录;编排器可用 `cwd` 选项给每格独立目录,resume 换目录即拒绝。
19
-
20
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-claude-code-card.png" width="640" alt="展开的「Local Agent · Claude Code」设置卡:认证块、默认模型选择与常驻模式(live)开关">
13
+ - **一句话交出去**:告诉 agent 哪段活给 Claude Code,它会自己写好要求交过去,结果回到当前会话。
14
+ - **过程实时流回**:思考、工具调用和回复边跑边出现在子会话里;中途停下,已经做完的部分和用量都留着。
15
+ - **账算得清**:子会话里每一次调用的用量单独记录,在上下文透镜里能逐次看到花在哪。
16
+ - **接着聊,不冷启动**:让 agent 追问,或者打开子会话直接发消息,继续的是同一个 Claude Code 会话。
17
+ - **填好 key 就能用**:在设置卡片里填端点地址和 API Key(或 Auth Token),点「保存凭据」,再点「验证端点并获取模型」确认能连上。
18
+ - **自己挑模型**:在「可用模型」里勾选、拖动排序,排第一的就是默认模型;子会话里也能临时换模型和推理强度。
19
+ - **常驻模式更快**:打开「常驻模式(live)」,Claude Code 进程常驻,停下不杀进程,崩溃后自动续上原来的会话。
21
20
 
22
21
  ## 安装
23
22
 
24
- 前置依赖:一个可运行的 dsh profile,以及 `PATH` 上的 Claude Code CLI(`claude`)——插件不负责安装。家族 core 与本包一起安装(`dsh plugin add` 只调和**直接**依赖,所以两个包都点名):
23
+ 先在电脑上装好 Claude Code CLI(命令行里能运行 `claude`),插件不替你装。然后在 DSH 的「插件 → 添加插件」里输入下面的包名,点安装:
25
24
 
26
- ```sh
27
- dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-claude-code
28
25
  ```
29
-
30
- 重启 web 实例后生效,然后在会话里运行一次 `/claude-code login`。卸载:
31
-
32
- ```sh
33
- dsh plugin --profile web remove @khorsheed/dsh-local-agent-claude-code
26
+ @khorsheed/dsh-bundle-local-agent
34
27
  ```
35
28
 
36
- 作用域目录在卸载时刻意保留,重装无需重新登录;删除 `$DSH_HOME/local-agent/claude-code` 即可清除全部痕迹。
37
-
38
- ## 配置
39
-
40
- bundle 行接受这些可选字段:
41
-
42
- - `permissionMode`——`skip`(默认)传 `--dangerously-skip-permissions`,子代理写文件无需审批;`normal` 不传,需审批的动作(如写文件)被拒绝。
43
- - `model`——每轮委派以它起 CLI(`claude -p --model <模型>`);不写就一个模型参数都不传。见下。
44
- - `baseUrl`——为子 CLI 设置 `ANTHROPIC_BASE_URL`(例如自部署路由器或代理);缺省继承宿主进程环境。配置优先于环境。
45
- - `proxyUrl`——子 CLI 自身流量(模型调用与 OAuth 续期)的 HTTP 代理,provision 进作用域 `settings.json` 的 env 块;宿主进程环境不带代理时(例如 supervisor 拉起的实例不继承 shell export)用这项。
46
- - `live`——长驻驱动:每成员常驻一个 stream-json 进程(`--input-format stream-json`),按轮发 stdin 消息(runtime 级优雅中断 control interrupt、同形推送流);关闭或通道不可用即回一次性 `claude -p`。
47
- - `liveIdleMs`——长驻 runtime 空闲回收时限(默认 30 分钟)。
48
-
49
- > **迁移说明**——live 轮次统一消费增量输出;旧 `liveMirrorGranularity: event | token` 配置键继续兼容读取,但不再影响行为,也不会改变运行中的进程。评测继续保留 exec;最终内容仍以 provider 完成项为准,包括工具记录和用量。
50
-
51
- ### 默认模型(`model`)
52
-
53
- **不写 = 今天的表现。** 没有这个键时,本插件在 argv 上一个模型参数都不加:跑哪个模型由作用域 `settings.json` 的 `model` 决定,它也没有时由 claude 自己的默认决定(本插件从不猜它是什么)。
54
-
55
- **写了 = 每轮委派以它起 CLI。** 新起一轮与续接(resume)、一次性与常驻(`live: true`)四种 argv 都带上 `--model <模型>`,位置在成员通道的 `--allowedTools …--` 之前——那个旗标是变长的,`--` 之后就是任务文本。
56
-
57
- 一次性(exec)轮不改写作用域 `settings.json`——`--model` 每轮覆盖它,文件仍是你编辑的样子。常驻(live)spawn 会把成员的生效模型暂写进该文件(`--resume` 重挂时 CLI 认文件里的模型而不认 `--model` 旗标),绑定空模型的 spawn 前再还原;状态页与设置卡读到的「CLI 配置」层永远是你自己配的值,不会读到某个成员留下的暂写。
58
-
59
- 设置卡「默认模型」修改后续轮次使用的 provider 配置。共享选择器展示当前作用域的模型目录、发现来源和完整性,并保留按需填写模型 ID 的入口。清空选择后跟随有效配置及默认值链。保存不会打断当前轮次,也无需重载;共享选择器不可用时,卡片保留文本输入兜底。成员级模型和推理强度修改使用下文的持久控制面。
60
-
61
- 模型读面现已读取 Claude 原生 `initialize` 目录,保留选择别名、显示名、解析名称和支持的 effort 值。目录共用 core 缓存和刷新订阅,刷新失败保留成功数据,并独立标识历史建议。此 scoped 控制查询不发送用户任务、不写模型设置。旧 CLI 缺少目录时显示不支持;原生候选不证明账号权限。共用模型菜单和成员 effort 控制已接入。运行中选择排到下一完整轮次(含工具续跑);core 统一持有当前/待生效配置、撤销与重试,冻结评测成员禁止变更。
62
-
63
- **委派级的模型优先。** 编排器可以经门面 `DelegationCallOptions.model` 给**某一次委派**点名模型,它排在这个键之前(顺序见家族核心 README 的层序)。首轮请求的值记进委派记录,resume 轮照它重发——resume 不接受 model 参数。常驻模式下,首轮点名的模型作为该成员的起始模型在 runtime spawn 时绑定:已绑定不同模型的 runtime 先退役,同一个 CLI 会话经 `--resume` 在新进程上续起。
64
-
65
- **成员会话级切换。** 成员会话的 composer 模型选择器经家族网关 `setMemberModel` 写入一个**会话级覆盖**——它排在委派模型与这个键之前,只存内存、刻意不跨宿主重启、也不写进设置。切换进行中轮次的成员会被拒绝;live 下换到不同模型会退役该成员的常驻 runtime,下一轮以新模型重挂**同一个** CLI 会话(对话不丢),换成相同模型则什么都不发生。
66
-
67
- **这不是评测的缺口。** 评测 run 的条件在建立时冻结:run 跑到一半改这个键,下一轮的模型回读会发现声明模型 ≠ 实测模型,run 直接判为 misattributed 而失败(冻结决策 5)。「切了新 run 照新走、进行中的 run 不被悄悄换掉」是设计出来的,不是漏掉的。
68
-
69
- ⚠️ `skip` 没有 OS 级沙箱——子进程可以写宿主用户能写的任何地方,包括工作区之外;作用域登录的 OAuth token 会发送给 `baseUrl` 指向的端点。委派需要限制时改用 `normal`;`baseUrl` 只指向你信任的地址。
70
-
71
- **评测快照(effectiveSettings)。** 本 harness 向注册表声明一份实时读取的公平性设置快照,供评测条件哈希使用:drive(exec/live)、permissionMode(skip/normal,插件配置)、端点是否固定(按 provider 的解析顺序——配置项优先于宿主进程环境的 `ANTHROPIC_BASE_URL`,只报主机名)、已配置模型(先看插件配置的 `model` 键——它每轮覆盖文件;没有才读作用域 `settings.json` 的 `model`;CLI 自身的默认模型由 CLI 决定,绝不猜值,都没有就不给字段)、CLI 版本(`claude --version`,按可执行文件路径+mtime 缓存;探测不到即字段缺位)。`/claude-code status` 与 `LocalAgentStatus` Remote 附带同一份快照。
72
-
73
- ## Compatibility
29
+ Claude Code 会跟着「多Agent协作」套件一起装好(各家本机 agent 共用同一个底座,单装用不起来);如果你主要写代码,直接装开发套件 `@khorsheed/dsh-dev-suite` 也包含它。装完重启 DSH,到「插件」页的「本机 Agent 委派」卡片里,展开 Claude Code 一节填好端点和 key。
74
30
 
75
- - npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 公开 API 兼容。生成中的内容走 local-agent 瞬时 Remote 与公开 Conversation 节点;增量耐久恢复检查点负责落盘,最终原生消息保留转写与用量语义。浏览器 P95 验收由 room 协调者提案跟踪。minHost 0.1.5-rc.1——更旧宿主请停留在前一发布线。
76
- - 源码线(deepseek-harness master):✅(verifiedHost: 0.1.5-rc.1)
31
+ 把活交给 Claude Code 要在带委派的模式里进行,比如「开发模式」:开一个新会话、选这个模式,对 agent 说「把这个交给 Claude Code」。在其它模式里看不到它是正常的。
77
32
 
78
33
  ## 已知限制
79
34
 
80
- - **macOS logout 只删配置**——keychain 条目留给 CLI 自己的 `claude auth logout`;下次登录会重写同一槽位,残留无害。
81
- - **同一次授权不能被宿主轮与容器轮共用**——macOS 上两者用的是不同存储,任一侧轮换都会作废另一侧的 token family。容器化的条件用自己的命名 scope,见「容器内委派」。
82
- - **Linux 凭据不隔离**(上游 bug #47661)——即使作用域目录没有凭据,claude 也会读默认 `~/.claude/.credentials.json`。
83
- - **交互会话记录可能缺失**——自定义 `CLAUDE_CONFIG_DIR` 下 CLI 可能不写转写(上游行为)。
84
- - **headless 注意**——`/claude-code` 命令需要 Web 会话;`--profile headless` 可通过挂载工具行的组合委派。
85
-
86
- ## 实现原理
87
-
88
- <details>
89
- <summary>内部结构(点击展开)</summary>
90
-
91
- **模型回读与 cwd 覆盖。** 每轮 settle 后,provider 把 stream-json `system`(init)事件 `model` 字段里的实际模型(如 `claude-opus-5[1m]`,原样回读,上下文变体后缀照留)随 `settled` 进度事件上报,并合并进 `delegations.jsonl` 的 `observedModel` 字段;取不到即缺位,绝不猜测。当任何一层、任何记录都没有模型时,「默认」面的最近观测提示读成员**自己的**转录(`projects/<cwd-slug>/<sessionId>.jsonl`;无成员面读全树最新的那份)——这是为 settle 通道之前的旧轮次准备的历史兜底;只读,绝不回写 `delegations.jsonl`。编排器还可以经门面 `DelegationCallOptions.cwd` 给本轮指定工作目录(记录进 `cwd` 字段);resume 轮解析出的目录若与首轮记录不一致,进程启动前即 fail loud——CLI 会话延续的是首轮所在目录的上下文。
92
-
93
- **工具调用计数。** 每轮 settle 时,provider 顺带数出本轮的工具调用,随 `settled` 进度事件上报(`toolCalls: { count, byName }`)。计数就在流解析**已经走过**的 `tool_use` 分支里,`byName` 的键是该块自己的 `name`(`Bash`、`Read`、`TodoWrite`,MCP 工具则是 `mcp__server__tool` 全名),原样保留、不跨家归一。`TodoWrite` 也计——折叠逻辑把它挪去了 todo 快照、不进 transcript,但 CLI 确实调了它。本轮一份,绝不累计;一次都没调用就整个字段缺位(缺席 ≠ 0)。实测:一轮「先 Read 两个文件再 Bash 列目录」回读 `{count: 3, byName: {Read: 2, Bash: 1}}`,与镜像出的工具卡片逐个对得上。
35
+ - 只支持端点和 key,没有网页登录。
36
+ - key 填错时,Claude Code 会先反复重试(实测约 8 分钟)才报错,这一轮结束后卡片上才显示凭据被拒。
37
+ - `/claude-code` 这组命令只能在网页会话里用。
94
38
 
95
- **命名 scope。** `/claude-code login --scope <名>` 在 `<homesRoot>/claude-code@<名>` 里另开一份 `CLAUDE_CONFIG_DIR`:登录的 argv 由**被登录的那份目录**现算(`env CLAUDE_CONFIG_DIR=… claude auth login` 的赋值压过 spawn env,写死缺省目录会让 `--scope` 登错地方)。claude 的 keychain 项按配置目录路径哈希,命名 scope 因此自动拿到自己的 keychain 项——四家里唯一天然按路径隔离的凭据。带 scope 的委派用该目录跑 `claude -p`、按它做 keychain↔文件协调、从它回读;只走 exec。
96
-
97
- **容器内委派。** 编排器可以经门面 `DelegationCallOptions.exec`(`{ container, workdir, env? }`)让本轮跑在一个**已取得的容器**里:argv 变成 `docker exec -w <workdir> [-e NAME…] <container> claude -p …`,其余(stream-json 解析、settle、记录)逐字节不变。`env` 必须给出容器内的 `CLAUDE_CONFIG_DIR`,调用方把宿主作用域目录 rw bind 到那条路径上。实测评测镜像里的 claude 2.1.272:容器内的 CLI **读写 `<CLAUDE_CONFIG_DIR>/.credentials.json`**,它整棵状态树(`projects/`、`sessions/` 等)也落在那里;上游 #47661 那种「Linux 写作用域目录、读默认 home」的老行为不描述这个版本。每次 spawn 前的凭据协调照常在**宿主**作用域目录上跑,续期结果因此经挂载对容器可见——而且它是**新者胜,不是单向镜像**,因为单元是同一个文件的**第二个写者**。单元里的 CLI 没有 keychain,只能在挂进去的文件里就地轮换凭证;此时若无条件地把 keychain 盖到文件上,下一轮拿到的就是单元已经用掉的 refresh token,端点拒绝,CLI 随即**清空该文件**——整台实例随之登出,因为那个文件就是实例的凭据。所以协调只在 keychain 那份不比文件旧时才写(以 access token 过期时间为准;任一侧没有过期时间就仍由 keychain 写,不可用的文件永远不会赢)。挂载保持读写还有第二个理由:本轮的会话记录落在同一目录的 `projects/` 下,评测的模型回读正是从这条挂载的宿主侧解析它们。容器轮固定走 exec 一次性驱动,且不声明成员桥。
98
-
99
- **容器轮必须用自己的 scope,这条纪律不是可选项。** claude 是这几家里唯一有两个凭据存储的,而实测 macOS 2.1.274 它们并没有同一个读者:**宿主 CLI 读写 keychain**,而单元里的 CLI——Linux、没有 keychain——读写挂进去的文件。同一次授权被两个存储各续一次,就是被续了两次;服务端轮换一次性 refresh token 的做法是把整个 **token family 作废**,于是没有最后续到的那一侧手里就是一个死 token。端到端实测过:容器轮轮换了文件,紧接着的宿主轮报 `OAuth session expired and could not be refreshed`,而文件自己的 access token 离过期还有好几个小时,只能重登才能恢复。新者胜保住的是**文件那一侧**(单元与所有状态探针读的就是它),但没有任何东西能把单元的轮换写回 keychain(`security add-generic-password` 只能把密文放进 argv,因此否决)。所以容器侧与宿主侧必须是**两次独立授权**:每个容器化的 claude 条件用自己的命名 scope,用 `/claude-code login --scope <名>` 授权一次,且**永不**在该 scope 上跑宿主轮。评测会在取单元之前就拒绝违反这条的计划。**注意作用域 `settings.json` 里的宿主专用项**:实测那里给宿主守护进程用的 `https_proxy` 在容器内指向不存在的地址,本轮当场 `Connection refused`——挂进去的目录要由调用方备好。
100
-
101
- ```
102
- src/index.ts harness 注册、/claude-code 命令族、config schema
103
- src/claude-cli-provider.ts 一次性 provider:spawn、流镜像、turn/token 记账
104
- src/provision.ts 作用域目录 provisioning 与基于文件的 logout
105
- src/records.ts 认证探测与作用域目录的会话记录列表
106
- src/tool.ts `./tool` 组合入口:`subagent_claude_code` 委派工具(预设点名授予)
107
- ```
108
-
109
- bundle patch 把 `claude-code` harness 注册进家族 core(`@khorsheed/dsh-local-agent`,声明为依赖——claude 包刻意不重复插入 core 行,重复会挂载两次),并挂载 `claude-local` provider,后者在 harness 作用域目录下 spawn `claude -p --verbose --output-format stream-json`。`subagent_claude_code` 工具是家族自有工具(本包的 `./tool` 组合入口,实现共享自家族 core 的 `./tool-factory`),由点名该行的 agent 预设按会话授予:官方子集(`description`/`prompt`)加一个可选 `resume` 参数。
110
-
111
- **作用域隔离。** macOS 上真实凭据在系统 keychain 的哈希条目里(`Claude Code-credentials-<sha256(configDir)[:8]>`,以作用域目录路径为键)——基于文件的 logout 够不到它,但下次登录会重写同一个槽位。**CLI 自己用哪个存储是随版本变的,且由实测而非假定确定**:2.1.236 写 keychain、读文件(这个镜像正是为那条分裂而存在),而 2.1.274 在 macOS 上读写 keychain,2.1.272 在 Linux 上读写作用域文件。因此文件对本包自己的探针与容器轮仍是权威;keychain 才是当前 macOS 上宿主 CLI 真正用的那个。认证探测只做轻量文件检查——看作用域 `.claude.json` 的 `oauthAccount`,绝不为了探测而 spawn CLI。该探测同样会协调两个存储,因此遵守与 spawn 点一致的新者胜规则:它在每次状态读与每次就绪检查时都会跑,那里若是无条件镜像,同样会把容器轮的续期抹掉。
112
-
113
- **会话记录。** `/claude-code sessions` 列出作用域目录下的 `projects/<cwd-slug>/<uuid>.jsonl` 会话文件——即本 agent 的委派,绝不是你的私人会话。slug 是工作区路径的有损编码,所以列出的 `workDir` 取自文件内容(第一条 `user` 事件的 `cwd` 字段),绝不取自目录名。设置面板呈现同一份列表并收窄到当前工作区。
114
-
115
- **续聊安全。** 句柄绝不进 prompt:它只从 `resume` 参数读取,localAgent registry 只对记录该委派的同一 parent 会话与 provider 解析句柄——伪造的句柄(未知子会话、他人 parent 的会话、或错误的 provider)在任何 CLI 进程启动前就被拒绝。
116
-
117
- **流镜像与记账。** `thinking` 块折叠为 `reasoning` 块、`tool_use`/`tool_result` 为工具行(`[工具 Bash] <command> → output`)、回复 `text` 为 assistant 文本;续聊轮各自追加自己的 turn,不会重复。编辑类调用(Edit/Write/MultiEdit/NotebookEdit)折叠为 ApplyPatch 式卡片——路径在参数行,真正的改动(旧/新串或整个文件体)在卡身,与 codex 的 fileChange 镜像同一形态;服务端工具(`server_tool_use` 的 web_search/web_fetch)与其结果按 id 配对成可见的 WebSearch/WebFetch 卡;`redacted_thinking` 折叠为一行可见的占位思考(内容按设计由服务端加密,不可见——行内注明);其余工具的输入取单一标量摘要,取不到就给一份截断的 JSON 摘要而不是丢空。每个镜像 step 都包在同 (turn, step) 的 `step/start`–`step/end` 边界对内——宿主实时会话视图只在边界上登记 step,缺了边界 assistant 消息要等整页重建才渲染。provider 在 spawn 时开 `turn/start`、settle 时关 `turn/end`——失败与被取消同样关闭——因此 `subagentTiming` 时长等于实际 CLI 运行时长,不会留下未闭合窗口;最终 `assistant/message` 携带从 JSON 结果解析的 token 用量(`input_tokens`、`output_tokens`、`cache_read_input_tokens`、`cache_creation_input_tokens`——Anthropic 各桶独立上报)。
118
-
119
- **模型体验。** 子会话是委派会话工作区内一个全新的一次性 `claude -p`;父级只提交独立的任务文本,只看到最终回答或精确错误,以及首次委派时的续聊自述——Claude 的评论、工具活动、工作区 diff 不会复制进父级。子会话 token 永不进入父级上下文;子会话为独立的 Claude 上下文与回合付费(缓存复用只取决于作用域安装自身的 provider、模型与历史);父级 KV cache 不受影响。
120
-
121
- **长驻驱动(`live: true`)。** 替代每轮 spawn:成员首轮委派拉起一个常驻 stream-json 进程(`claude -p --verbose --input-format stream-json --output-format stream-json`),第一条 stdin `user` 消息触发 `system/init`(server 分配 session id,即委派记录的 `cliSessionId`),之后每轮 = 一条 stdin 消息;事件流与 exec 的 stream-json **完全同形**,逐轮过同一个 `ClaudeStreamParser` 折叠(末行留置到 `result` 挂用量,与 exec 一致),`cancel` 落地为 `control_request interrupt`——进程不死、会话可续。回收阶梯是 stdin EOF → SIGTERM;崩溃后下一轮以 `--resume <session_id>` 重挂盘上会话。runtime 在 spawn 时绑定成员的生效模型(会话级覆盖 → 委派模型 → 设置键),并把它暂写进作用域 `settings.json`(`--resume` 重挂认文件不认旗标);绑定模型与新一轮解析不一致的 runtime 先退役再重挂。`permissionMode: normal` 的 spawn 不带 `--dangerously-skip-permissions`,CLI 的 server→client 控制请求(`can_use_tool` 审批面)由驱动自动应答 allow——子代理没有审批面,与 exec 在 skip 下的有效行为一致;其余子类型应答 error 而不是沉默挂起。授权纪律与 exec 逐字一致:只注入作用域 `CLAUDE_CONFIG_DIR`(与可选的 `baseUrl` 覆盖),绝不碰全局 `~/.claude`,绝不执行任何 `auth` 命令。
122
-
123
- **配置注意。** 宿主环境是启动时的快照——之后在别的 shell 里 `export ANTHROPIC_BASE_URL` 对运行中的进程无效,除非重启宿主;排查路由异常时检查宿主的实际环境而非你当前的 shell。每次委派在 info 级别记录有效端点(`subagent-claude: delegating via <endpoint>`),失败运行的报错文本会点名它。`skip` 作为默认是因为一次性 CLI 子代理没有审批面;委派需要被限制时,给宿主自身套沙箱(例如在沙箱化工作区内运行 dsh 宿主)。
39
+ ## Compatibility
124
40
 
125
- </details>
41
+ 需要 DSH 0.1.5-rc.1 或更新版本,已在 0.2.1-rc.1 上验证。
126
42
 
127
- ## 开发
43
+ ## 给开发者
128
44
 
129
- 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo(`packages/local-agent-claude-code`)。问题与贡献请移步该仓库。
45
+ - 配置项、可用模型名单、端点凭据的存放、实时流镜像与用量回读等细节,都在[完整参考](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/local-agent-claude-code/docs/reference.md)里。
46
+ - 单独安装要连同底座一起点名:`dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-claude-code`;委派工具由模式授予,没有模式点名时不会出现。
47
+ - 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins),问题与贡献请到该仓库;变更见 [CHANGELOG.md](CHANGELOG.md)。