@khorsheed/dsh-local-agent-claude-code 0.1.0-rc.8 → 0.2.0-rc.3
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 +15 -0
- package/README.en.md +38 -31
- package/README.i18n.yaml +2 -2
- package/README.md +34 -31
- package/cordis.patch.yml +17 -24
- package/lib/client.js +843 -316
- package/lib/index.js +1491 -1520
- package/lib/tool.js +40 -0
- package/lib/types/claude-cli-provider.d.ts +13 -4
- package/lib/types/claude-cli-provider.js +61 -49
- package/lib/types/client/SettingsCard.d.ts +75 -24
- package/lib/types/client/SettingsCard.js +106 -128
- package/lib/types/client/index.d.ts +12 -25
- package/lib/types/client/index.js +135 -50
- package/lib/types/client/locales.d.ts +36 -24
- package/lib/types/client/locales.js +40 -28
- package/lib/types/endpoint-auth.d.ts +181 -0
- package/lib/types/endpoint-auth.js +281 -0
- package/lib/types/index.d.ts +32 -16
- package/lib/types/index.js +128 -62
- package/lib/types/live-driver.d.ts +9 -1
- package/lib/types/live-driver.js +18 -18
- package/lib/types/live-switch.d.ts +4 -1
- package/lib/types/model-broker.d.ts +4 -2
- package/lib/types/model-broker.js +19 -8
- package/lib/types/model-catalog.d.ts +35 -6
- package/lib/types/model-catalog.js +58 -68
- package/lib/types/provision.d.ts +34 -27
- package/lib/types/provision.js +49 -31
- package/lib/types/records.d.ts +4 -62
- package/lib/types/records.js +5 -382
- package/lib/types/tool.d.ts +54 -0
- package/lib/types/tool.js +34 -0
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +25 -23
- package/lib/invariant.js +0 -31
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.js +0 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.2.0-rc.3(2026-10-22)
|
|
4
|
+
|
|
5
|
+
(版本号说明:家族的 dsh-headless 包 0.2.0-rc.2 曾被发布后又撤回,npm 永久烧掉该版本号;为守住家族「同一版本线」纪律(check:release-groups),本波家族整体上移 rc.3,内容即 rc.2 计划稿。)
|
|
6
|
+
|
|
7
|
+
- **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 下一轮生效、无需重载。
|
|
8
|
+
- **设置卡新增端点凭据表单与「验证端点并获取模型」按钮**:卡片编辑的正是默认 scope 的 endpoint-auth.json;验证即带解析出的凭据探测 `GET <baseUrl>/v1/models`,保存成功翻转认证状态点。凭据等级语义不变:key 在场读 present-unverified,跑通一轮升 verified,一轮 401 打 rejected,重写凭据文件即恢复手势。
|
|
9
|
+
- **「可用模型」有序短名单取代默认模型字段**:持久化为 `<作用域目录>/models.json`,首项即默认模型;添加弹窗的候选全部来自端点当前供应的目录(勾选添加、拖拽排序),任何本地残留别名都混不进选择器。名单为空时完整回退既有链;成员的模型在首轮即绑定,改名单只影响新成员,不会换掉对话中的成员。
|
|
10
|
+
- **模型目录改为端点供给**:目录 = 所配端点实际供应的模型(探测预算 20 秒,传输错误保留上次成功数据)——本地 CLI 的词汇表不再给一把失效 key 打掩护。
|
|
11
|
+
- **设置卡并入「本机 Agent 委派」家族卡**:Plugins 页的 per-harness 卡折叠为一张家族卡、按 harness 分节呈现,本包卡读作「本机 Claude Code 委派」;家族卡随后修复为无条件注册——原先的命名空间门控让它在 prod 上永不挂载(2026-10-21)。与旧版家族核混装时回落为独立卡。
|
|
12
|
+
- **适配宿主 0.2.1 线**:委派链路迁移到 subagent `startActivation`(宿主 0.2.1 删除旧的 `ctx.subagents.start()`);加宽 `@deepseek-ai/dsh-*` peer 区间以覆盖宿主 0.2.1。
|
|
13
|
+
|
|
14
|
+
## 0.2.0-rc.1(2026-09-30)
|
|
15
|
+
|
|
16
|
+
- **家族折叠:委派工具收进本包**。新增 `./tool` 组合入口(`src/tool.ts`)——`subagent_claude_code` 委派工具由 agent 预设点名 `name: '@khorsheed/dsh-local-agent-claude-code/tool'` 按会话授予,Config 两字段(`provider` / `toolName`)全部可选、默认即本包取值,预设行无需配置;行 id `tool-subagent-claude-code-local` 不变。bundle patch 不再插 profile 根工具行(官方 `tool-subagent-claude-code` 行的停用覆盖保留),本包自挂载恰好一行(provider 行)。实现共享自家族核 `@khorsheed/dsh-local-agent` 的 `./tool-factory` 导出;对 `@khorsheed/dsh-local-agent-tool-subagent` 的依赖移除(该包退役)。
|
|
17
|
+
|
|
3
18
|
## 0.1.0-rc.8(2026-09-30)
|
|
4
19
|
|
|
5
20
|
家族同版联动:加宽 `@deepseek-ai/dsh-*` peer 区间以覆盖宿主 0.2.0(0.2.0 的兼容闸会禁用 peer 区间不覆盖宿主版本的已装插件)。无功能变更。
|
package/README.en.md
CHANGED
|
@@ -4,15 +4,15 @@ English | [中文](README.md)
|
|
|
4
4
|
|
|
5
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.
|
|
6
6
|
|
|
7
|
-
Any dsh agent preset
|
|
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. Authentication is endpoint-key based (`baseUrl` + `apiKey`/`authToken`), 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. There is no OAuth web flow: a container round uses the same credential as a host round, with nothing to authorize inside the container. The Claude Code harness of the [local-agent family](../local-agent/README.md).
|
|
8
8
|
|
|
9
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">
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **
|
|
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
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
|
-
- **
|
|
15
|
+
- **Endpoint credentials, no web authorization** — configure `baseUrl` + `apiKey` (or `authToken`) on the bundle row and the harness is authenticated; the status panel reads authenticated immediately. Each named scope can override the deployment defaults with its own `endpoint-auth.json`. `sessions`/`status`/`logout` and a Settings → 本地 Agent panel complete the family.
|
|
16
16
|
- **Resume a delegation** — pass back the result's `resume` handle to continue the same Claude session, with per-round accounting.
|
|
17
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
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.
|
|
@@ -27,13 +27,13 @@ Prerequisites: a running dsh profile, and the Claude Code CLI (`claude`) on `PAT
|
|
|
27
27
|
dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-claude-code
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
After restarting the web instance, open this package's card under Settings → Plugins and fill in the endpoint and a key in the Authentication block (or preconfigure them on this package's profile row); with neither, the status reads not authenticated. Uninstall:
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
dsh plugin --profile web remove @khorsheed/dsh-local-agent-claude-code
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
The scoped home is kept on uninstall
|
|
36
|
+
The scoped home is kept on uninstall — the `endpoint-auth.json` and the session records live there; delete `$DSH_HOME/local-agent/claude-code` to remove every trace.
|
|
37
37
|
|
|
38
38
|
## Config
|
|
39
39
|
|
|
@@ -41,24 +41,32 @@ Optional fields on the bundle row:
|
|
|
41
41
|
|
|
42
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
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` —
|
|
45
|
-
- `
|
|
44
|
+
- `baseUrl` — the deployment-level endpoint, sent to the child CLI as `ANTHROPIC_BASE_URL` (e.g. a relay such as `https://openproxy.to/claude`, or a self-hosted router); absent means Anthropic's own endpoint. A same-named variable exported in the host shell is NEVER read — the child environment is scrubbed on principle, and routing is decided by the config and the scope file alone. **Editable right on the settings card's Authentication block** — the card saves the default scope's `endpoint-auth.json` (mode 0600, local only, outranking this config row), and no key ever lands in the profile document.
|
|
45
|
+
- `apiKey` — the deployment-level credential, sent as `ANTHROPIC_API_KEY` (the `x-api-key` header). Writable from the settings card (stored in the scope file, never echoed back); in a profile, keep it local and never commit it.
|
|
46
|
+
- `authToken` — the other credential shape, sent as `ANTHROPIC_AUTH_TOKEN` (the `Authorization: Bearer` header) — some relays accept only Bearer. When both keys are set, the CLI's own precedence applies. Also card-editable.
|
|
47
|
+
- `proxyUrl` — an HTTP proxy for the child CLI's own traffic, 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). It is a local traffic forwarder — a different layer from `baseUrl`'s endpoint routing.
|
|
46
48
|
- `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
49
|
- `liveIdleMs` — idle lifetime of a resident runtime before reclaim (default 30 min).
|
|
48
50
|
|
|
49
|
-
> **Migration note** —
|
|
51
|
+
> **Migration note (next release line — breaking)** — the OAuth web flow is removed wholesale: `/claude-code login` no longer exists, and the keychain/`.credentials.json` reconcile is gone with it; the first boot after the upgrade sweeps the scoped home's leftover OAuth artifacts (`.credentials.json` and the `.claude.json` `oauthAccount` marker) — measured, claude 2.1.296 prefers the environment credential over a stored one anyway. Configure `baseUrl` + `apiKey` on the settings card or the profile row BEFORE upgrading (an official console key against the default endpoint works too), or the upgraded instance reads unauthenticated. The old `liveMirrorGranularity: event | token` key is still accepted for existing profiles but ignored.
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
> **Per-scope override** — `<scopedHome>/endpoint-auth.json` (mode 0600) overrides field by field, two layers: **the scope file > the profile's YAML row**, an absent field falling back to the config value. A file naming only `apiKey` is exactly "rotate this scope's key". The settings card's Authentication block edits exactly this file for the default scope (through the family gateway's `endpointAuth`/`configureEndpoint` Remotes — readable and writable with no session present). `/claude-code logout` clears the same file; when the credential comes from the config row alone, logout refuses and names the profile as the place to edit.
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
### Available models (models.json)
|
|
54
56
|
|
|
55
|
-
**
|
|
57
|
+
The settings card's "Available models" block maintains an **ordered model shortlist**: "Add model" checks entries off the endpoint's served catalog (`GET /v1/models`), so the list can only hold what the endpoint actually serves; drag rows to reorder. **The first entry is the default model** — every delegation round starts the CLI with it (`claude -p --model <model>`, the same flag in resident mode), outranked only by a member's session-level override and a delegation's own model. The list persists in the scoped home's `models.json` (each named scope can carry its own — the same file mechanism as the endpoint credential), saves on every edit, and applies to the next delegation round.
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
**Empty = the previous behavior.** With an empty list the model is decided by the pre-list chain: the plugin-config/settings `model` key → the scoped `settings.json`'s `model` → the CLI's built-in default (the plugin never guesses what it is). One-shot (exec) rounds never rewrite the scoped `settings.json`; a resident (live) spawn scratches the 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.
|
|
58
60
|
|
|
59
|
-
The
|
|
61
|
+
The **evaluation snapshot (`effectiveSettings`)** reads the model down the same chain: the list's first entry → the `model` key → the scoped file; the list itself never enters the snapshot (it is a preference, not a fairness knob) — the round's `observedModel` read-back is the evidence. Change the list mid-run and the next round's read-back sees declared ≠ observed and fails the run as misattributed (frozen decision 5).
|
|
60
62
|
|
|
61
|
-
|
|
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
|
+
The model surface now lists **what the endpoint actually serves** (`GET <baseUrl>/v1/models` with the saved credential): the picker shows exactly what the endpoint can run, an invalid credential shows an empty list with the reason — a locally-known CLI vocabulary can never mask a dead key. The directory shares the core cache and refresh subscription; an endpoint or credential change re-probes (a rotation rides a short digest in the cache key, never the key itself), and a transport failure keeps the last good data and retries on schedule. Served effort capabilities become selectable levels. An off-list model id can still be typed by hand. The settings card's "verify endpoint & fetch models" button is the explicit form of the same probe (a session-less Remote, disabled while drafts are unsaved). 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
70
|
|
|
63
71
|
**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
72
|
|
|
@@ -66,20 +74,19 @@ The model surface now also reads Claude's native `initialize` directory, preserv
|
|
|
66
74
|
|
|
67
75
|
**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
76
|
|
|
69
|
-
⚠️ `skip` has no OS-level sandbox — the child can write anywhere the host user can, including outside the workspace — and the
|
|
77
|
+
⚠️ `skip` has no OS-level sandbox — the child can write anywhere the host user can, including outside the workspace — and the endpoint credential is sent to whatever `baseUrl` points at. Prefer `normal` when a delegation needs confinement; point `baseUrl` only at endpoints you trust.
|
|
70
78
|
|
|
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
|
|
79
|
+
**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 — the scope's `endpoint-auth.json` overrides the plugin config field by field; hostname only — key material never enters the snapshot, and the file's name matches the evaluation contract hash's credential denylist, so not even its digest enters a condition fingerprint), 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.
|
|
72
80
|
|
|
73
81
|
## Compatibility
|
|
74
82
|
|
|
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.
|
|
83
|
+
- 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. The settings card renders as a section of the core's「Local Agent Delegation」family card (the core's `plugins.item` entry on the Plugins page, 0.1.7-alpha.1+; on an older core this package falls back to its own `plugins.item` card), mounted through `configForms.whileServed` while the Host serves the `local-agent-claude-code` namespace; 0.1.5 renders no card (the host removed the `settings.plugin.item` seat) — every other surface is unchanged.
|
|
84
|
+
- 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.
|
|
77
85
|
|
|
78
86
|
## Known Limitations
|
|
79
87
|
|
|
80
|
-
- **
|
|
81
|
-
- **
|
|
82
|
-
- **Linux credentials are not isolated** (upstream bug #47661) — claude reads the default `~/.claude/.credentials.json` even when the scoped home has none.
|
|
88
|
+
- **Logout clears only the scope's own file** — a deployment-level credential from the plugin config is unaffected; edit the profile row to change it.
|
|
89
|
+
- **A bad credential costs the retry budget** — a dead key makes the CLI retry up to its limit (measured ≈8 minutes) before the 401 surfaces; the status's `rejected` grade appears when that round settles.
|
|
83
90
|
- **Interactive-session records may be missing** — under a custom `CLAUDE_CONFIG_DIR` the CLI may not write transcripts (upstream behavior).
|
|
84
91
|
- **Headless caveat** — `/claude-code` commands need a Web session; `--profile headless` delegates through a composition that mounts the tool row.
|
|
85
92
|
|
|
@@ -92,22 +99,22 @@ The model surface now also reads Claude's native `initialize` directory, preserv
|
|
|
92
99
|
|
|
93
100
|
**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
101
|
|
|
95
|
-
**
|
|
102
|
+
**The endpoint credential model.** Authentication has exactly one form: `endpoint-auth.ts` merges the scope's `endpoint-auth.json` field by field over the plugin config triple (`baseUrl`/`apiKey`/`authToken`), and the provider, the live driver and the model catalog resolve and inject it (`ANTHROPIC_BASE_URL`/`ANTHROPIC_API_KEY`/`ANTHROPIC_AUTH_TOKEN`) at every spawn — a key rotation takes effect on the next round, no reload. The settings card's credential form reads and writes the default scope's file through the family gateway's `endpointAuth`/`configureEndpoint` Remotes — a session-less channel where keys are neither echoed nor ever written into the profile patch document. Measured: claude 2.1.296 in a brand-new scoped home runs `claude -p` on these variables alone — the CLI scaffolds `.claude.json` itself, and headless mode has no onboarding or key-approval gate; with a stale OAuth grant sitting alongside, the environment credential wins just the same (provisioning still sweeps those leftovers on the first boot after an upgrade). The auth probe is a light file-plus-config check (a non-empty key is authenticated), and the CLI is never spawned just to probe; the `endpoint-auth.json` mtime serves as the credential stamp — a round's 401 grades the credential `rejected`, and rewriting the file (rotating the key) recovers it, one-to-one with the OAuth era's "a fresh login rewrites the marker".
|
|
96
103
|
|
|
97
|
-
**
|
|
104
|
+
**Named scopes.** Each named scope opens another `CLAUDE_CONFIG_DIR` at `<homesRoot>/claude-code@<name>`: its own `endpoint-auth.json`, its own session records, its own credential grade. A scope without a file follows the deployment config — which is exactly how one configuration serves every scope. A scoped delegation runs `claude -p` against that directory and reads back from it; exec-only.
|
|
98
105
|
|
|
99
|
-
**
|
|
106
|
+
**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. The endpoint credential crosses into the unit through the family's container forwarding: the provider's resolved `ANTHROPIC_*` variables ride NAME-ONLY `-e` flags whose values the docker client's own environment carries — the key never appears on an argv or in a process table, so a container round uses the SAME credential as a host round with zero authorization steps inside the unit. An API key neither refreshes nor rotates, so the OAuth era's two-store fork (host and container racing one grant's rotation) does not exist under endpoint credentials. The mount stays read-write because 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. **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
107
|
|
|
101
108
|
```
|
|
102
109
|
src/index.ts harness registration, /claude-code command family, config schema
|
|
110
|
+
src/endpoint-auth.ts the endpoint credential: scope-file ↔ config merge, env construction, stamp and clearing
|
|
103
111
|
src/claude-cli-provider.ts one-shot provider: spawn, stream mirror, turn/token accounting
|
|
104
|
-
src/provision.ts scoped-home provisioning
|
|
105
|
-
src/records.ts
|
|
112
|
+
src/provision.ts scoped-home provisioning (the proxy env block)
|
|
113
|
+
src/records.ts session-record listing and transcript model read-back from the scoped home
|
|
114
|
+
src/tool.ts the `./tool` composition entry: the `subagent_claude_code` delegation tool (preset-granted)
|
|
106
115
|
```
|
|
107
116
|
|
|
108
|
-
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 (
|
|
109
|
-
|
|
110
|
-
**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.
|
|
117
|
+
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.
|
|
111
118
|
|
|
112
119
|
**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.
|
|
113
120
|
|
|
@@ -117,9 +124,9 @@ The bundle patch registers the `claude-code` harness into the family core (`@kho
|
|
|
117
124
|
|
|
118
125
|
**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.
|
|
119
126
|
|
|
120
|
-
**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
|
|
127
|
+
**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 the resolved endpoint credential variables are injected — the global `~/.claude` is never touched and no `auth` verb ever runs.
|
|
121
128
|
|
|
122
|
-
**Config notes.**
|
|
129
|
+
**Config notes.** Every delegation logs the effective endpoint at info level (`subagent-claude: delegating via <endpoint>`), and a failed run's error text names it — diagnose routing from that log and the profile row, not from your current shell's environment. `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.
|
|
123
130
|
|
|
124
131
|
</details>
|
|
125
132
|
|
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:
|
|
6
|
-
packages/local-agent-claude-code/README.md:
|
|
5
|
+
packages/local-agent-claude-code/README.en.md: 812398d9ac14b7cb0d77a584af9c7f510c716c3f
|
|
6
|
+
packages/local-agent-claude-code/README.md: e026c9163886617cc80553db90f76e76ad4246c2
|
package/README.md
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
把编码任务委派给本机的 Claude Code——思考、工具调用与回复实时流回,你的私人 `~/.claude` 分文不动。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
点名本包 `./tool` 组合入口的 agent preset(如开发模式)即可发起委派:子会话全程可见、可中止、可续聊,每轮的账目(实际模型、token 用量、工具调用数)都真实回读。认证是端点凭据制(`baseUrl` + `apiKey`/`authToken`),作用域隔离——插件只管理 `$DSH_HOME/local-agent/claude-code` 这一份目录,你的个人安装与凭据自始至终置身事外。没有 OAuth 网页授权:容器轮与宿主轮用同一套凭据,容器里不需要再授权一次。[local-agent 家族](../local-agent/README.md) 的 Claude Code harness。
|
|
8
8
|
|
|
9
9
|
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/08-local-agent.png" width="640" alt="设置 → 插件 → 插件配置里的 Local Agent 家族卡片,Claude Code 一行的状态点显示授权状态">
|
|
10
10
|
|
|
11
11
|
## 特性
|
|
12
12
|
|
|
13
|
-
-
|
|
13
|
+
- **按会话授予的委派**——`subagent_claude_code` 工具是本包的 `./tool` 组合入口,由点名它的 agent 预设授予该预设的会话,无需逐 preset 变体。
|
|
14
14
|
- **作用域隔离**——每个 Claude 进程都以 `$DSH_HOME/local-agent/claude-code` 为 `CLAUDE_CONFIG_DIR` 运行;你的个人 `~/.claude` 置身事外。
|
|
15
|
-
-
|
|
15
|
+
- **端点凭据,没有网页授权**——在 bundle 行配好 `baseUrl` + `apiKey`(或 `authToken`)即已认证,状态面板直接显示已认证;每个命名 scope 可用自己的 `endpoint-auth.json` 覆盖部署级配置。`sessions`/`status`/`logout` 与 设置 → 本地 Agent 面板构成完整一族。
|
|
16
16
|
- **续聊委派**——把结果自述的 `resume` 句柄传回,即可继续同一个 Claude 会话,并按轮记账。
|
|
17
17
|
- **实时流镜像**——子会话实时镜像 Claude 的思考、工具调用与回复;中止会保留部分转写与真实 token 用量。
|
|
18
18
|
- **模型回读与独立工作目录**——每轮从 stream-json 的 system/init 回读实际模型,写进委派记录;编排器可用 `cwd` 选项给每格独立目录,resume 换目录即拒绝。
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-claude-code
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
重启 web
|
|
30
|
+
重启 web 实例后,打开 设置 → 插件 里本包的卡片,在「认证」区填端点与 key 即可(也可预配在 profile 的本包配置行);都不配则状态为未认证。卸载:
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
dsh plugin --profile web remove @khorsheed/dsh-local-agent-claude-code
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
作用域目录在卸载时刻意保留——`endpoint-auth.json` 与会话记录都在其中;删除 `$DSH_HOME/local-agent/claude-code` 即可清除全部痕迹。
|
|
37
37
|
|
|
38
38
|
## 配置
|
|
39
39
|
|
|
@@ -41,24 +41,28 @@ bundle 行接受这些可选字段:
|
|
|
41
41
|
|
|
42
42
|
- `permissionMode`——`skip`(默认)传 `--dangerously-skip-permissions`,子代理写文件无需审批;`normal` 不传,需审批的动作(如写文件)被拒绝。
|
|
43
43
|
- `model`——每轮委派以它起 CLI(`claude -p --model <模型>`);不写就一个模型参数都不传。见下。
|
|
44
|
-
- `baseUrl
|
|
45
|
-
- `
|
|
44
|
+
- `baseUrl`——部署级端点,为子 CLI 设置 `ANTHROPIC_BASE_URL`(例如 `https://openproxy.to/claude` 这类中转,或自部署路由器);不写即 Anthropic 官方端点。宿主 shell 里 export 的同名变量**从不被读取**——宿主环境对子进程一律清洗,路由只由配置与 scope 文件决定。**也可在设置卡「认证」区直接填**——卡片保存的是默认 scope 的 `endpoint-auth.json`(0600,只存本机,优先于这里的配置行),密钥绝不写进 profile 文件。
|
|
45
|
+
- `apiKey`——部署级凭据,以 `ANTHROPIC_API_KEY`(`x-api-key` 头)发给端点。设置卡可写(落 scope 文件,不回显);写进 profile 时只放本地,绝不入库。
|
|
46
|
+
- `authToken`——部署级凭据的另一形态,以 `ANTHROPIC_AUTH_TOKEN`(`Authorization: Bearer` 头)发出——部分中转只认 Bearer。两个键都配时优先级由 CLI 自己决定。同样可在设置卡填写。
|
|
47
|
+
- `proxyUrl`——子 CLI 自身流量的 HTTP 代理,provision 进作用域 `settings.json` 的 env 块;宿主进程环境不带代理时(例如 supervisor 拉起的实例不继承 shell export)用这项。它是本地流量转发器,与 `baseUrl` 的端点路由是两个层面。
|
|
46
48
|
- `live`——长驻驱动:每成员常驻一个 stream-json 进程(`--input-format stream-json`),按轮发 stdin 消息(runtime 级优雅中断 control interrupt、同形推送流);关闭或通道不可用即回一次性 `claude -p`。
|
|
47
49
|
- `liveIdleMs`——长驻 runtime 空闲回收时限(默认 30 分钟)。
|
|
48
50
|
|
|
49
|
-
>
|
|
51
|
+
> **迁移说明(下一发布线,破坏性变更)**——OAuth 网页授权整体移除:`/claude-code login` 不再存在,钥匙串/`.credentials.json` 协调随之删除;升级后首次启动会清扫作用域目录里残留的 OAuth 文件(`.credentials.json` 与 `.claude.json` 的 `oauthAccount` 标记),实测 claude 2.1.296 在两者并存时本就优先采用环境变量凭据。升级前先在设置卡或 profile 行配好 `baseUrl` + `apiKey`(官方端点的 console key 同样可用),否则升级即未认证。旧 `liveMirrorGranularity: event | token` 配置键继续兼容读取,但不再影响行为。
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
> **每个 scope 的覆盖**——`<作用域目录>/endpoint-auth.json`(0600)按字段覆盖,两层生效顺序:**scope 文件 > profile 的 YAML 行**,缺哪个字段就回落到配置值。一个只写 `apiKey` 的文件等于给这个 scope 换 key。设置卡「认证」区编辑的就是默认 scope 的这份文件(经家族 gateway 的 `endpointAuth`/`configureEndpoint` Remote,会话不在场也能读写)。`/claude-code logout` 清除的也是这个文件;凭据完全来自配置行时 logout 会拒绝并提示去改 profile。
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
### 可用模型(models.json)
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
设置卡「可用模型」区维护一份**有序模型短名单**:点「添加模型」从端点当前供应的目录(`GET /v1/models`)里勾选加入,所以列表里只会有端点真实供应的模型;拖动行调整顺序。**第一个模型就是默认模型**——每轮委派以它起 CLI(`claude -p --model <模型>`,常驻模式同一个旗标),优先级只低于成员会话级覆盖与委派级指定。列表保存在作用域目录的 `models.json`(每个命名 scope 可各有一份,与端点凭据同一套文件机制),编辑即保存,下一轮委派生效。
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
**留空 = 既有行为。** 列表为空时,模型照旧由这些层决定:插件配置或设置的 `model` 键 → 作用域 `settings.json` 的 `model` → CLI 内置默认(本插件从不猜它是什么)。一次性(exec)轮不改写作用域 `settings.json`;常驻(live)spawn 会把生效模型暂写进该文件(`--resume` 重挂认文件不认旗标),绑定空模型的 spawn 前再还原。
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
**评测快照(effectiveSettings)** 的 model 字段同一条解析链:名单第一项 → `model` 键 → 作用域文件;名单进不了快照(它是偏好不是公平性旋钮),回读的 `observedModel` 才是实测证据。run 冻结条件后中途改名单,下一轮的模型回读会发现声明 ≠ 实测并判 misattributed(冻结决策 5)。
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
成员会话里的 composer 模型选择器写入的是**会话级覆盖**——它排在委派模型与名单之前,只存内存、刻意不跨宿主重启、也不写进设置或名单。切换进行中轮次的成员会被拒绝;live 下换到不同模型会退役该成员的常驻 runtime,下一轮以新模型重挂**同一个** CLI 会话(对话不丢),换成相同模型则什么都不发生。成员级模型和推理强度修改使用下文的持久控制面。
|
|
64
|
+
|
|
65
|
+
模型读面来自**端点实际供应的模型**(`GET <baseUrl>/v1/models`,带已保存的凭据)——选择器列出的就是端点能跑的,凭据无效时列表为空并给出原因,绝不会用本地 CLI 的内置词汇表掩盖一个失效的 key。目录共用 core 缓存和刷新订阅;端点或凭据一变即重探(凭据轮换经短哈希进缓存键,密钥本体不进),传输错误保留上次成功数据并按计划重试。端点返回的 effort 能力会带上可选档位。目录外的模型 id 仍可手动输入。设置卡的「验证端点并获取模型」按钮是同一条探测的显式版本(无会话 Remote,未保存修改时禁用)。运行中选择排到下一完整轮次(含工具续跑);core 统一持有当前/待生效配置、撤销与重试,冻结评测成员禁止变更。
|
|
62
66
|
|
|
63
67
|
**委派级的模型优先。** 编排器可以经门面 `DelegationCallOptions.model` 给**某一次委派**点名模型,它排在这个键之前(顺序见家族核心 README 的层序)。首轮请求的值记进委派记录,resume 轮照它重发——resume 不接受 model 参数。常驻模式下,首轮点名的模型作为该成员的起始模型在 runtime spawn 时绑定:已绑定不同模型的 runtime 先退役,同一个 CLI 会话经 `--resume` 在新进程上续起。
|
|
64
68
|
|
|
@@ -66,20 +70,19 @@ bundle 行接受这些可选字段:
|
|
|
66
70
|
|
|
67
71
|
**这不是评测的缺口。** 评测 run 的条件在建立时冻结:run 跑到一半改这个键,下一轮的模型回读会发现声明模型 ≠ 实测模型,run 直接判为 misattributed 而失败(冻结决策 5)。「切了新 run 照新走、进行中的 run 不被悄悄换掉」是设计出来的,不是漏掉的。
|
|
68
72
|
|
|
69
|
-
⚠️ `skip` 没有 OS
|
|
73
|
+
⚠️ `skip` 没有 OS 级沙箱——子进程可以写宿主用户能写的任何地方,包括工作区之外;端点凭据会发送给 `baseUrl` 指向的端点。委派需要限制时改用 `normal`;`baseUrl` 只指向你信任的地址。
|
|
70
74
|
|
|
71
|
-
**评测快照(effectiveSettings)。** 本 harness 向注册表声明一份实时读取的公平性设置快照,供评测条件哈希使用:drive(exec/live)、permissionMode(skip/normal,插件配置)、端点是否固定(按 provider
|
|
75
|
+
**评测快照(effectiveSettings)。** 本 harness 向注册表声明一份实时读取的公平性设置快照,供评测条件哈希使用:drive(exec/live)、permissionMode(skip/normal,插件配置)、端点是否固定(按 provider 的解析顺序——scope 的 `endpoint-auth.json` 逐字段覆盖插件配置,只报主机名;key 材料永远不进快照,文件名命中评测契约哈希的凭据拒绝名单,连摘要都不进条件指纹)、已配置模型(名单第一项 → 插件配置的 `model` 键 → 作用域 `settings.json` 的 `model`;CLI 自身的默认模型由 CLI 决定,绝不猜值,都没有就不给字段)、CLI 版本(`claude --version`,按可执行文件路径+mtime 缓存;探测不到即字段缺位)。`/claude-code status` 与 `LocalAgentStatus` Remote 附带同一份快照。
|
|
72
76
|
|
|
73
77
|
## Compatibility
|
|
74
78
|
|
|
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.
|
|
79
|
+
- npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 公开 API 兼容。生成中的内容走 local-agent 瞬时 Remote 与公开 Conversation 节点;增量耐久恢复检查点负责落盘,最终原生消息保留转写与用量语义。浏览器 P95 验收由 room 协调者提案跟踪。minHost 0.1.5-rc.1——更旧宿主请停留在前一发布线。设置卡渲染为 core「本机 Agent 委派」家族卡的一节(Plugins 页 core 的 `plugins.item` 条目,0.1.7-alpha.1+;旧版 core 上回落为本包自己的 `plugins.item` 卡),`configForms.whileServed` 随宿主服务的 `local-agent-claude-code` 命名空间挂载;0.1.5 线不再呈现卡片(宿主删除了 `settings.plugin.item` 席位),其余表面不变。
|
|
80
|
+
- 源码线(deepseek-harness master):✅(verifiedHost: 0.2.1-rc.1)——host-021 波对 0.2.1-rc.1 树全量 build+test 绿;委派迁移到 `ctx.subagents.startActivation`(受管 activation),run.id 改为每次执行唯一。
|
|
77
81
|
|
|
78
82
|
## 已知限制
|
|
79
83
|
|
|
80
|
-
- **
|
|
81
|
-
-
|
|
82
|
-
- **Linux 凭据不隔离**(上游 bug #47661)——即使作用域目录没有凭据,claude 也会读默认 `~/.claude/.credentials.json`。
|
|
84
|
+
- **logout 只清 scope 自己的文件**——`endpoint-auth.json` 之外,来自插件配置的部署级凭据不受影响;要换它请编辑 profile 行。
|
|
85
|
+
- **凭据错误的代价是重试时长**——一把失效的 key 会让 CLI 在端点前重试到上限(实测约 8 分钟)才报出 401;`/claude-code status` 的 `rejected` 等级在那一轮 settle 后才出现。
|
|
83
86
|
- **交互会话记录可能缺失**——自定义 `CLAUDE_CONFIG_DIR` 下 CLI 可能不写转写(上游行为)。
|
|
84
87
|
- **headless 注意**——`/claude-code` 命令需要 Web 会话;`--profile headless` 可通过挂载工具行的组合委派。
|
|
85
88
|
|
|
@@ -92,22 +95,22 @@ bundle 行接受这些可选字段:
|
|
|
92
95
|
|
|
93
96
|
**工具调用计数。** 每轮 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}}`,与镜像出的工具卡片逐个对得上。
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
**端点凭据模型。** 认证只有一种形态:`endpoint-auth.ts` 把作用域 `endpoint-auth.json` 逐字段合并到插件配置三元组(`baseUrl`/`apiKey`/`authToken`)之上,provider、长驻驱动与模型目录在每次 spawn 前现解现注(`ANTHROPIC_BASE_URL`/`ANTHROPIC_API_KEY`/`ANTHROPIC_AUTH_TOKEN`),换 key 即下一轮生效,无需重载。设置卡的凭据表单经家族 gateway 的 `endpointAuth`/`configureEndpoint` Remote 读写默认 scope 的这份文件——无会话通道,密钥既不回显也绝不落进 profile patch 文档。已实测:claude 2.1.296 在全新作用域目录里仅凭这组环境变量即可跑通 `claude -p`——CLI 自行脚手架 `.claude.json`,headless 模式没有 onboarding 或 key 批准门槛;与陈旧的 OAuth 文件并存时同样优先采用环境变量凭据(provision 仍会在升级后首次启动清扫这些残留)。状态探针是轻量文件+配置检查(有非空 key 即认证),绝不为探测 spawn CLI;`endpoint-auth.json` 的 mtime 充任凭据戳记——一轮 401 把等级打成 `rejected`,重写该文件(换 key)即恢复,与 OAuth 时代「重新登录改写标记」语义一一对应。
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
**命名 scope。** 每个命名 scope 在 `<homesRoot>/claude-code@<名>` 另开一份 `CLAUDE_CONFIG_DIR`:自己的 `endpoint-auth.json`、自己的会话记录、自己的凭据等级。scope 文件缺省即跟随部署级配置——这正是一次配置、全部 scope 可用的来源。带 scope 的委派用该目录跑 `claude -p`、从它回读;只走 exec。
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
**容器内委派。** 编排器可以经门面 `DelegationCallOptions.exec`(`{ container, workdir, env? }`)让本轮跑在一个**已取得的容器**里:argv 变成 `docker exec -w <workdir> [-e NAME…] <container> claude -p …`,其余(stream-json 解析、settle、记录)逐字节不变。`env` 必须给出容器内的 `CLAUDE_CONFIG_DIR`,调用方把宿主作用域目录 rw bind 到那条路径上。端点凭据经家族的容器转发进入单元:provider 解析出的 `ANTHROPIC_*` 变量以**仅名字**的 `-e` 旗标转发,值由 docker 客户端进程环境携带——key 从不上 argv、不进进程表,容器轮因此**与宿主轮用同一套凭据,单元内没有任何授权步骤**。API key 无刷新无轮换,OAuth 时代「宿主与容器双写 refresh token 互杀」的问题在端点凭据下整体不存在。会话记录仍落在挂载目录的 `projects/` 下,评测的模型回读从宿主侧解析它们。容器轮固定走 exec 一次性驱动,且不声明成员桥。**注意作用域 `settings.json` 里的宿主专用项**:实测那里给宿主守护进程用的 `https_proxy` 在容器内指向不存在的地址,本轮当场 `Connection refused`——挂进去的目录要由调用方备好。
|
|
100
103
|
|
|
101
104
|
```
|
|
102
105
|
src/index.ts harness 注册、/claude-code 命令族、config schema
|
|
106
|
+
src/endpoint-auth.ts 端点凭据:scope 文件 ↔ 配置的合并、env 构造、戳记与清除
|
|
103
107
|
src/claude-cli-provider.ts 一次性 provider:spawn、流镜像、turn/token 记账
|
|
104
|
-
src/provision.ts 作用域目录 provisioning
|
|
105
|
-
src/records.ts
|
|
108
|
+
src/provision.ts 作用域目录 provisioning(proxy env 块)
|
|
109
|
+
src/records.ts 作用域目录的会话记录列表与转写模型回读
|
|
110
|
+
src/tool.ts `./tool` 组合入口:`subagent_claude_code` 委派工具(预设点名授予)
|
|
106
111
|
```
|
|
107
112
|
|
|
108
|
-
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`
|
|
109
|
-
|
|
110
|
-
**作用域隔离。** 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 点一致的新者胜规则:它在每次状态读与每次就绪检查时都会跑,那里若是无条件镜像,同样会把容器轮的续期抹掉。
|
|
113
|
+
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` 参数。
|
|
111
114
|
|
|
112
115
|
**会话记录。** `/claude-code sessions` 列出作用域目录下的 `projects/<cwd-slug>/<uuid>.jsonl` 会话文件——即本 agent 的委派,绝不是你的私人会话。slug 是工作区路径的有损编码,所以列出的 `workDir` 取自文件内容(第一条 `user` 事件的 `cwd` 字段),绝不取自目录名。设置面板呈现同一份列表并收窄到当前工作区。
|
|
113
116
|
|
|
@@ -117,9 +120,9 @@ bundle patch 把 `claude-code` harness 注册进家族 core(`@khorsheed/dsh-lo
|
|
|
117
120
|
|
|
118
121
|
**模型体验。** 子会话是委派会话工作区内一个全新的一次性 `claude -p`;父级只提交独立的任务文本,只看到最终回答或精确错误,以及首次委派时的续聊自述——Claude 的评论、工具活动、工作区 diff 不会复制进父级。子会话 token 永不进入父级上下文;子会话为独立的 Claude 上下文与回合付费(缓存复用只取决于作用域安装自身的 provider、模型与历史);父级 KV cache 不受影响。
|
|
119
122
|
|
|
120
|
-
**长驻驱动(`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
|
|
123
|
+
**长驻驱动(`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` 与解析出的端点凭据变量,绝不碰全局 `~/.claude`,绝不执行任何 `auth` 命令。
|
|
121
124
|
|
|
122
|
-
**配置注意。**
|
|
125
|
+
**配置注意。** 每次委派在 info 级别记录有效端点(`subagent-claude: delegating via <endpoint>`),失败运行的报错文本会点名它;排查路由异常时看这份日志与 profile 行,而不是你当前 shell 的环境。`skip` 作为默认是因为一次性 CLI 子代理没有审批面;委派需要被限制时,给宿主自身套沙箱(例如在沙箱化工作区内运行 dsh 宿主)。
|
|
123
126
|
|
|
124
127
|
</details>
|
|
125
128
|
|
package/cordis.patch.yml
CHANGED
|
@@ -8,25 +8,24 @@
|
|
|
8
8
|
# installed explicitly alongside this bundle, or this provider stays pending.
|
|
9
9
|
# No other patch inserts the core row id.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
11
|
+
# Exactly ONE row is inserted: the provider (this bundle's plugin registers the
|
|
12
|
+
# `claude-code` harness — CLAUDE_CONFIG_DIR scoped home, `claude auth login`
|
|
13
|
+
# browser flow, project-file session records). The `subagent_claude_code`
|
|
14
|
+
# delegation tool is no longer a profile-root row: since the family fold it
|
|
15
|
+
# ships as this package's `./tool` composition entry
|
|
16
|
+
# (`name: '@khorsheed/dsh-local-agent-claude-code/tool'` in an agent preset),
|
|
17
|
+
# granted per session — the dev preset's `tool-subagent-claude-code-local` row
|
|
18
|
+
# is the grant point. The family keeps the official model-facing name
|
|
19
|
+
# `subagent_claude_code`: the official preset row of that name ships
|
|
20
|
+
# `disabled: true`, and this patch disables the profile-root row too (below)
|
|
21
|
+
# so a community install can never register two `subagent_claude_code` tools.
|
|
22
|
+
# A user who deliberately re-enables the official row in their own preset
|
|
23
|
+
# layer gets a loud duplicate-name conflict — by design, they must choose one.
|
|
24
|
+
# The family tool keeps its continuation parameter: a later round may pass the
|
|
25
|
+
# dsh child session id returned by the first delegation to continue the same
|
|
26
|
+
# claude conversation.
|
|
26
27
|
#
|
|
27
|
-
# The
|
|
28
|
-
# without per-preset variants. The provider starts no claude process until a
|
|
29
|
-
# tool call or /claude-code login.
|
|
28
|
+
# The provider starts no claude process until a tool call or /claude-code login.
|
|
30
29
|
|
|
31
30
|
- id: tool-subagent-claude-code
|
|
32
31
|
disabled: true
|
|
@@ -34,9 +33,3 @@
|
|
|
34
33
|
- insert:
|
|
35
34
|
- id: local-agent-claude-code
|
|
36
35
|
name: '@khorsheed/dsh-local-agent-claude-code'
|
|
37
|
-
|
|
38
|
-
- id: tool-subagent-claude-code-local
|
|
39
|
-
name: '@khorsheed/dsh-local-agent-tool-subagent'
|
|
40
|
-
config:
|
|
41
|
-
provider: claude-local
|
|
42
|
-
toolName: subagent_claude_code
|