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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,22 @@
1
1
  # 变更记录
2
+ ## 0.2.0(2026-10-26)
3
+
4
+ rc.3 之后到正式版的变化:
5
+
6
+ - **每次 API 调用记自己的用量**(live 路):原来只在轮末把最后一次的用量写在最后一行,中间步骤没有 token 数据;现在每次调用结束就把它自己的用量记上。整轮合计改用上游的累计值,修正多次调用时的少算。
7
+ - **修复冷缓存时输入 token 约多算一倍**:Codex 的 `input_tokens` 已含缓存读写,计算未命中输入时现在把两者都扣掉。
8
+ - **修复子会话步骤乱序、重启后被判损坏**:live 镜像改为写入时才编号、一次只开一个步骤,先开始的先落盘(实时预览照常)。
9
+ - **修复工具发现循环在子会话里看不见调用**:`custom_tool_call` 与动态工具的结果现在如实折叠,不再显示成两段相邻的裸回复。
10
+ - 协作间的桥接调用最长可等 30 分钟(`tool_timeout_sec`),供成员等你回答问题。
11
+ - **README 重写**:改为大白话说明,完整参考移到 `docs/`。
12
+ - **版本号去掉 rc**:社区包不再发 prerelease 版本(docs/publishing.md「版本号规则」),家族各包同版 0.2.0;本节原记为 0.2.0-rc.4。
13
+ - **缓存账目补全与硬化**:`cacheReadTokens` 在上游上报该字段时恒写(0 命中也写 0,与「未上报」可区分);补上此前丢弃的 `cacheWriteTokens`(exec 的 `cache_write_input_tokens` / app-server 的 `cacheWriteInputTokens`);`turn/completed` 的镜像失败不再吞掉收尾(轮不再因折叠异常挂起)。
14
+ - **子会话日志写入 `request/header`**:codex 的流从不携带 model/effort,子会话配置事实原来无处记录。现在每轮以 provider 启动参数为权威来源写入宿主原生 `request/header`(exec 路的 spawn argv、live 路的 `turn/start` 请求参数;只在变化时记录)——Context Lens 的配置卡片由此读出真实值。
15
+ - **成员会话的模型对话框与「可用模型」短名单严格一致**:成员的模型目录原来会混入作用域配置发现的模型与历史记录(kimi 下会列出非本端点服务的配置表模型),与卡片短名单不一致。现在成员对话框的候选即短名单(按名单顺序;名单里端点已不再供应的值保留为裸行,与卡片一致),名单外只保留成员当前绑定的模型(避免既有绑定显示空白);卡片的添加对话框只列端点真实供应的模型,配置/历史附加项不再混入。
2
16
 
3
17
  ## 0.2.0-rc.3(2026-10-22)
4
18
 
5
- (版本号说明:家族的 dsh-headless 包 0.2.0-rc.2 曾被发布后又撤回,npm 永久烧掉该版本号;为守住家族「同一版本线」纪律(check:release-groups),本波家族整体上移 rc.3,内容即 rc.2 计划稿。)
19
+ (版本号说明:家族的 dsh-headless 包 0.2.0-rc.2 版本号已被 npm 发布流水线占用(重复利用被 409 拒绝:「Cannot stage previously published version」——曾发布后被撤回或曾被暂存,外部无法区分,结果相同:该号不可复用);为守住家族「同一版本线」纪律(check:release-groups),本波家族整体上移 rc.3,内容即 rc.2 计划稿。)
6
20
 
7
21
  - **BREAKING:网页登录移除,端点 key 成为唯一认证方式**。部署级凭据为插件配置 `baseUrl` / `apiKey`;每个作用域可再以 `<scopedHome>/endpoint-auth.json`(0600)按字段覆盖——「scope 文件 > profile 配置」。spawn 时注入为自定义 provider 覆盖片段加进程环境变量,`config.toml` 不被改写;key 不带 `baseUrl` 时直连 OpenAI 官方端点。**升级前必须配好端点凭据**,否则升级后读作未认证(README 带迁移说明);残留的 `auth.json` 由 provision 每次启动清扫,`/codex login` 不再存在。
8
22
  - **设置卡新增端点凭据表单与「验证端点并获取模型」按钮**:卡片编辑默认 scope 的 endpoint-auth.json;验证探测 `GET <baseUrl>/v1/models`(兼容带不带 `/v1` 后缀的 baseUrl),不可达 / HTTP 状态 / 形状不符各失败形态折进判定文本。
package/README.en.md CHANGED
@@ -2,158 +2,45 @@
2
2
 
3
3
  English | [中文](README.md)
4
4
 
5
- Hand coding tasks to the Codex CLI installed on your own machine — an endpoint credential or its own login, its own session records, and your `~/.codex` never gets touched.
5
+ Hand coding tasks to the Codex on your machine: watch it work live, get the result back in your session, and keep the conversation going.
6
6
 
7
- dsh agents already get work done, but some tasks you simply want to give to your local Codex CLI: its account, its models, the toolchain you know. This plugin wires Codex in as a dsh local subagent — any agent preset can delegate with one call through the `subagent_codex` tool; delegations run inside a plugin-scoped home (`$DSH_HOME/local-agent/codex`), so your personal config, credentials and sessions are never touched; the process details (reasoning, tool calls, file changes) mirror live into the child session, and the final answer comes back to the parent. Authentication is endpoint-key only (`baseUrl` + `apiKey` — a relay or an official platform key alike): container rounds share the host's credential and nothing is authorized inside the unit. There is no web login and no device code — no OAuth flow exists in this package.
7
+ Some jobs you simply want Codex to do, and that used to mean running it in a terminal and carrying the result back yourself. With this plugin you say "give this to Codex" in a session, and the agent hands the task to Codex on your machine. Its reasoning, tool calls, and file changes show up live in a sub-session, and the result comes back with its real usage and time. Codex runs in its own separate folder; your existing `~/.codex` config, sign-in, and sessions 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 · Codex card on the Settings → plugin configuration page, its header status dot showing the auth state at a glance">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-codex.png" width="560" alt="The expanded Codex section: Codex signed in with its version, endpoint, API key, "Verify endpoint & list models" and the available-models list">
10
10
 
11
- ## Features
11
+ ## What it does
12
12
 
13
- - **Per-session delegation grant** — the `subagent_codex` tool is this package's `./tool` composition entry, granted to the sessions of an agent preset that names it (e.g. dev mode); no per-preset setup.
14
- - **Scoped-home isolation** — all Codex state (config, credentials, rollout session records) lives in `$DSH_HOME/local-agent/codex`, separate from your `~/.codex`.
15
- - **Endpoint credential (the only auth mode)** — fill `baseUrl` + `apiKey` on the card's Authentication section (saved as the default scope's `endpoint-auth.json`, mode 0600, local only) or preconfigure the profile row, and the scope is authenticated; every named scope may override the deployment pair field by field with its own `endpoint-auth.json`. Every round routes at the endpoint through a custom provider (`wire_api=responses`, verified end-to-end against codex-cli 0.163.0 through a relay), the key riding the process environment. The host shell's environment is never read; there is no web login.
16
- - **Resident mode (live)** — one resident `codex app-server` process per member, one turn per round: output streams into the child session in real time, cancel does not kill the process, and a crash resumes the same thread; off — or a channel that cannot come up — means the one-shot `codex exec` path, hot-switched without a reload.
17
- - **Settings card** — a section of the core's「Local Agent Delegation」family card on the Plugins page (on an older core, this package's own Plugins detail-page card): sign-in, the available-models shortlist (ordered, the first entry is the default; empty = the legacy chain), and the resident switch; a save applies on the next round.
18
- - **Resume a thread** — pass `resume="<childSessionId>"` to continue the same codex thread in the same dsh child session, even across host restarts.
19
- - **Custom endpoint** — configure the endpoint credential on the card or the profile row to route through your relay; a hand-edited custom provider in the scoped `config.toml` stays authoritative too (never overwritten).
20
- - **Read-back and per-cell working directory** — every settled round reads the model, CLI version and usage back out of its own rollout into the delegation record; the file is located by thread id, cwd and time window, so concurrent runs each read their own round. Orchestrators pass a `cwd` per cell, and a resume in a different directory is rejected.
21
-
22
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-codex-card.png" width="640" alt="the expanded Codex settings card: auth status with sign-in and sign-out, the default model, and the resident-mode switch">
13
+ - **Hand it off in one sentence**: tell the agent which part goes to Codex; it writes up the brief, and the result comes back to your session.
14
+ - **See the work as it happens**: reasoning, commands, and changed files appear live in the sub-session, and you can stop it any time.
15
+ - **Keep talking, even after a restart**: have the agent follow up, or open the sub-session and message Codex yourself; it's the same Codex thread, and it picks up again after DSH restarts.
16
+ - **Paste a key and go**: enter the endpoint and API key on the settings card (an official platform key or a relay key), press "Save credential", then "Verify endpoint & list models" to check it connects.
17
+ - **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.
18
+ - **Resident mode is faster**: turn on "Resident mode (live)" and the Codex process stays up, streaming output into the sub-session; stopping doesn't kill it, and after a crash it resumes the same thread.
23
19
 
24
20
  ## Install
25
21
 
26
- Prerequisites: a working dsh profile and the Codex CLI (`codex`) on `PATH` — the plugin neither installs it nor logs in for you.
27
-
28
- ```sh
29
- dsh plugin --profile web add @khorsheed/dsh-local-agent
30
- dsh plugin --profile web add @khorsheed/dsh-local-agent-codex
31
- ```
32
-
33
- Both packages must be named explicitly — `dsh plugin add` reconciles only direct dependencies, so the family core is never mounted by a transitive dependency. Restart the web instance to activate, then fill the endpoint and key in the Codex section's Authentication block of the「Local Agent Delegation」family card (the Plugins page) — or preconfigure the profile row; with neither, the status reads unauthenticated.
34
-
35
- Uninstall:
22
+ First install the Codex CLI yourself (`codex` runs in your terminal); the plugin doesn't install it. Then in DSH, open Plugins → Add plugin, enter this package name, and press Install:
36
23
 
37
- ```sh
38
- dsh plugin --profile web remove @khorsheed/dsh-local-agent-codex
39
24
  ```
40
-
41
- The scoped home (`$DSH_HOME/local-agent/codex`) is kept on purpose, so a reinstall needs no fresh login; delete it to remove every trace.
42
-
43
- ## Config
44
-
45
- Optional, in the profile patch layer:
46
-
47
- ```yaml
48
- - id: local-agent-codex
49
- config:
50
- sandbox: workspace-write # codex exec policy: read-only | workspace-write | danger-full-access
51
- baseUrl: https://your-relay.example/openai/v1 # optional: deployment-level endpoint (an OpenAI-compatible /v1 root); absent leaves the scope file or the web login to decide
52
- apiKey: sk-... # optional: deployment-level endpoint key (Bearer); keep it local, never commit it
53
- model: gpt-5.2 # optional: every delegation round starts the CLI with it; absent passes no model flag at all (below)
54
- live: false # live driver: one resident codex app-server process per member, one turn per round (runtime-level graceful interrupt, push-mode mirroring); off — or a channel that cannot come up — means the one-shot exec path
55
- liveIdleMs: 1800000 # idle lifetime of a resident runtime before reclaim (default 30 min)
25
+ @khorsheed/dsh-bundle-local-agent
56
26
  ```
57
27
 
58
- ### Endpoint credential (`baseUrl` + `apiKey`)
59
-
60
- **A configured key = every round routes at the endpoint.** When the scope's resolution (the scope's own `endpoint-auth.json` merged field by field over the profile row) carries a non-empty key, every delegation injects a custom provider on the argv (`-c model_provider=… -c model_providers.…={base_url, env_key=OPENAI_API_KEY, wire_api=responses}`) and the key travels in the process environment — `config.toml` is never rewritten, `auth.json` never consulted, the web login never participates. The one-shot exec, the resident app-server, and the model-catalog probes all ride the same credential. A key without a `baseUrl` targets OpenAI's own endpoint (an official platform key needs no relay — the built-in provider reads the same variable). Clearing the key (save the field blank on the card) reads unauthenticated.
61
-
62
- **The card's Authentication section edits the default scope's `endpoint-auth.json`** (mode 0600, local only; through the family gateway's `endpointAuth`/`configureEndpoint` Remotes, so no session is needed) — keys never enter the profile document, and the file's name matches the evaluation contract hash's credential denylist, so it never enters a condition fingerprint either. The "Verify endpoint & list models" button next to it runs a token-free probe against the SAVED endpoint (`GET <baseUrl>/v1/models`, Bearer header) and answers with reachability plus the served model list. The file also accepts a hand-written `"wireApi": "chat"` (the card never edits it and preserves it on save) for relays that only speak chat/completions.
63
-
64
- > **A bad key costs retry time** — a dead key makes the CLI retry against the endpoint for minutes before reporting a 401; `/codex status` shows the `rejected` grade only after that round settles. Re-saving the key (rewriting endpoint-auth.json) is the recovery.
65
-
66
- > **Migration note (next release line, breaking)** — the web login is removed outright: `/codex login` no longer exists, and the device-code/browser grant with it; the first boot after the upgrade sweeps the scoped home's leftover `auth.json`, so a stale grant cannot keep authenticating while the status reads unauthenticated. Configure `baseUrl` + `apiKey` on the card or the profile row BEFORE upgrading (an official platform key works as well as a relay key), or the upgraded instance reads unauthenticated.
67
- >
68
- > **Logout clears only the scope's own file** — `/codex logout` removes the scope's `endpoint-auth.json`; when the credential comes entirely from the profile row, logout refuses and names the profile as the place to edit.
69
-
70
- **Legacy key migration.** Live runs always consume incremental output: the old `liveMirrorGranularity: event | token` key is accepted for existing profiles but ignored, and changing it never changes a running process. Exec remains available for evaluation; final provider items remain authoritative, including tool history and usage.
71
-
72
- ### Default model (`model`)
73
-
74
- **Absent = today's behavior.** Without this key the plugin adds no model flag to the argv at all: the scoped `config.toml`'s top-level `model` decides which model runs, and codex's own default decides when that is missing too.
75
-
76
- **Set = every delegation round starts the CLI with it.** A fresh round and a resume round are treated alike:
77
-
78
- | Drive | How it is passed |
79
- |---|---|
80
- | one-shot (default) | `codex exec -m <model> …` — `-m` is an option of `codex exec` itself, so it precedes the `resume` subcommand (`codex exec resume` declares no `-m` of its own) |
81
- | resident (`live: true`) | `codex app-server -c model="<model>" --stdio` — the app-server has no `-m`, so the model rides the process-level `-c` override the CLI documents for this |
82
-
83
- The scoped `config.toml` is never rewritten: `-m` overrules it per round and the file stays exactly as you edited it.
84
-
85
- The settings card's「Available models」section maintains an **ordered model shortlist**: "Add model" checks entries off the endpoint's served catalog, 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 (`codex exec -m` one-shot, `-c model=…` on the resident app-server), outranked only by member session overrides and delegation-level picks. The list persists in the scoped home's `models.json` (each named scope may carry its own, the same file mechanism as the endpoint credential), every edit saves immediately and applies to the next round. **Empty = the legacy chain** (the `model` key below → the scoped `config.toml` → the CLI built-in). Per-member model and effort changes use the durable controls described below.
86
-
87
- The rich directory's vocabulary comes from the endpoint itself (`GET <baseUrl>/v1/models` — the same probe the "Verify endpoint & list models" button runs): a local registry cannot prove what the endpoint serves, and a dead key can no longer hide behind the CLI's local list. The native app-server `model/list` probe is demoted to metadata enrichment, running concurrently: it alone carries the display names, hidden candidates, reasoning-effort options and the `isDefault` compiled default, so a served id that matches the registry carries those over, while an id only the endpoint serves stands as a plain entry; a native failure warns once and the endpoint data is a complete answer on its own. With no credential, a rejected credential, an unexpected HTTP status, or a non-`/v1/models`-shaped reply, the directory loads empty with the reason; a transport failure throws, so the cache keeps the last good snapshot marked stale and retries (the openproxy relay once 500'd and recovered on its own — exactly the case this semantics is for). The probe context follows the member's scope and cwd. 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. A native catalog candidate is not proof of account access.
88
-
89
- **A delegation's own model outranks this key.** An orchestrator may name the model for ONE delegation through the facade's `DelegationCallOptions.model` (the fixed order: session override > delegation record > the shortlist's first entry > this key > scoped config > CLI built-in). The first round's request is recorded and every resume round re-requests it — `resume` takes no model of its own. In live mode such a round is no longer refused: the model becomes the member's **start model**, bound at the app-server spawn; a resident runtime bound to a different model is retired first (the same codex thread resumes via thread/resume) so the round respawns onto the asked-for model.
90
-
91
- **Member-level switching (the composer model picker).** A member session can switch its model per session: an in-memory session-level override, the highest-priority layer, deliberately lost on a host restart. A switch is refused while a round is in flight; when idle and the member has a resident runtime bound to a different model, the switch retires that runtime — the next round respawns onto the new model while the CLI session (rollout) itself carries over. On the one-shot (exec) drive there is no resident process, so the override simply decides the next round's `-m`.
28
+ Codex 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 Codex section, and fill in the endpoint and API key.
92
29
 
93
- **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.
30
+ Handing work to Codex 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 Codex". Not seeing Codex delegation in other modes is expected.
94
31
 
95
- ## Custom endpoint
32
+ ## Known limitations
96
33
 
97
- The endpoint credential (the card or the profile row) is the only credentialed route. You may still add a custom provider to the scoped `$DSH_HOME/local-agent/codex/config.toml` (built-in providers can't be overridden, `OPENAI_BASE_URL` is ignored, and provisioning never overwrites an existing config) — but its `env_key` reads the CHILD's environment, the host shell's same-named variables are scrubbed, and only the endpoint credential's key rides along, so a hand-written provider is useful in practice only for endpoints that need no auth (e.g. a local router):
98
-
99
- ```toml
100
- [model_providers.dsh-router]
101
- name = "dsh-router"
102
- base_url = "https://your-router.example/v1"
103
- model_provider = "dsh-router"
104
- ```
105
-
106
- The last line selects the provider for delegations; keep the rest of the file intact. An endpoint credential with a key wins over the file's `model_provider` selection (the argv's provider overrides it).
107
-
108
- ⚠️ **Credential exposure**: the endpoint key is sent to whatever endpoint serves the request — only use endpoints you control or trust. Each delegation logs the effective endpoint at info level.
109
-
110
- **Evaluation snapshot (`effectiveSettings`).** The harness declares a live-read snapshot of its fairness-relevant settings for the evaluation condition hash: drive (exec/live), the sandbox policy (plugin config), the reasoning effort (the scoped config's top-level `model_reasoning_effort`), whether a custom endpoint is pinned (the endpoint credential first — the scope's `endpoint-auth.json` merged field by field over the plugin config; with no key, the scoped config's custom provider answers; hostname only, key material never enters the snapshot), the configured model (the scope's `models.json` first entry first, then the plugin config's `model` key — it overrules the file on every argv — then the scoped config's top-level `model`; absent when none names one — never guessed), and the CLI version (`codex --version`, cached against the executable's path + mtime; absent when the CLI cannot be asked). `/codex status` and the `LocalAgentStatus` Remote attach the same snapshot.
34
+ - Only an endpoint plus API key is supported; signing in with a ChatGPT subscription is not.
35
+ - Nobody is there to approve Codex's requests: actions that would need your OK are refused outright rather than asking you.
36
+ - The `/codex` commands only work in web sessions.
111
37
 
112
38
  ## Compatibility
113
39
 
114
- - 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 checkpoints provide recovery, and native final messages retain their transcript and usage semantics. End-to-end latency acceptance is tracked by the room coordinator proposal. minHost 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-codex` namespace; 0.1.5 renders no card (the host removed the `settings.plugin.item` seat) — every other surface is unchanged.
115
- - 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.
116
-
117
- ## Known Limitations
118
-
119
- - **No web login** — the endpoint credential is the only auth path (the card or the profile row; an official platform key works as well as a relay key). The ChatGPT-subscription OAuth path is not part of this package.
120
- - **`codex exec` is non-interactive** — actions the sandbox policy would approve are denied, not prompted; in resident mode approval-shaped requests are likewise auto-declined unattended (cancel/decline). See the `sandbox` config.
121
- - **Named scopes are exec-only** — the resident app-server binds the default scoped home, so a scoped delegation never enters live mode.
122
- - **Headless caveat** — `/codex` commands need a Web session; headless can still delegate via a composition mounting the tool row.
123
-
124
- ## How it works
125
-
126
- <details>
127
- <summary>Internals (click to expand)</summary>
128
-
129
- **Named scopes.** `/codex login --scope <name>` opens a second scoped home at `<homesRoot>/codex@<name>`: the directory is provisioned with the same `config.toml` that pins credential storage to a file rather than the macOS keychain, so that scope's login lands in its own `auth.json`. A scoped delegation runs `codex exec` there, writes its rollout there and reads it back from there — exec-only, because the resident app-server is bound to the default scoped home.
130
-
131
- **Bundle composition.** The patch registers the `codex` harness (scoped `CODEX_HOME`, PTY login, rollout-file session records) — exactly one provider row; since the family fold the `subagent_codex` tool is this package's `./tool` composition entry, granted per session by an agent preset that names it (the dev preset's `tool-subagent-codex-local` row), no longer mounted at the profile root. The family keeps the official model-facing name `subagent_codex`: the official preset row of that name already ships `disabled: true`, and the patch disables the profile-root row once more, so a community install can never register two `subagent_codex` tools; a user who deliberately re-enables the official row in their own preset layer gets a loud duplicate-name conflict and must choose one, by design. The `codex-local` provider spawns `codex exec` under that home (the one-shot drive when live is off). The browser half ships with this package's `./client`: the Codex settings card composes the family core's shared `ProviderAuthBlock`, rendering as a section of the core's「Local Agent Delegation」family card — mounted through `configForms.whileServed` while the Host serves the `local-agent-codex` namespace, one section for a standalone install, a family bundle, or a mode pack alike (on an older core, this package's own `plugins.item` card instead); card writes ride the bound settingsScope and hot-apply without a reload. The core itself comes from `@khorsheed/dsh-local-agent`, declared as a dependency.
132
-
133
- **Credentials.** Endpoint-key only, no login flow: the scope's `endpoint-auth.json` (mode 0600) merges field by field over the profile row's `baseUrl`/`apiKey`, and when the resolution carries a key, every round injects a custom provider on the argv (`wire_api=responses`, the key in the process environment — verified against codex-cli 0.163.0 on a fresh scope with no login file at all, straight through a relay). First start writes a minimal `config.toml` pinning `cli_auth_credentials_store = "file"` — Codex's default `auto` would resolve to the macOS keychain, so a hand-run `codex login` inside the scoped home would leak the grant outside it; an existing config is left untouched. Every boot also sweeps the web-login era's leftover `auth.json` — this package no longer reads it, and keeping it would let a stale grant authenticate while the status reads unauthenticated. `/codex logout` deletes the scoped `endpoint-auth.json`, so a later configuration rotates the key.
134
-
135
- **Session records.** `/codex sessions` lists the scoped home's `sessions/YYYY/MM/DD/rollout-*.jsonl` files — sessions this plugin's delegations created, never your personal ones. The records entry in the session header (provided by the family core's client) narrows the same list to records whose `workDir` matches the session cwd.
136
-
137
- **Read-back and the cwd override.** After every settled round the provider reads three facts back out of that round's own rollout file, reports them through the `settled` run-progress event, and merges them into `delegations.jsonl`: the model (`turn_context.payload.model` — codex 0.144.0's exec stream carries no model at all, so the rollout is the only authority; filtered to this round's time window, so a resumed thread's earlier models are never misread), the CLI version (`session_meta.payload.cli_version` — written by the codex build that actually served the round, a stronger answer than probing the executable afterwards), and the token usage a non-completed round never streamed (the last `token_count`). Absence is recorded, never guessed.
138
-
139
- **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 comes only from the `item.completed` branches the stream fold ALREADY walks — no new parse path: `command_execution`, `web_search_call`, `file_change`, and `function_call` each count once, and `function_call_output` is a result, not a second call. `byName` keys are **codex's own item types** (`command_execution`), not the display names the mirrored cards carry (`Bash`) — the accounting records what the CLI calls the thing. One accounting per round, never cumulative; a round that called nothing carries no field at all (absent is not zero). Measured: one "list the directory and count the files" round reads back `{count: 1, byName: {command_execution: 1}}`, and a two-command round reads back `{count: 2}`.
140
-
141
- Both the locate and the scan are calibrated for concurrency. codex writes `turn_context` when the TURN STARTS, so a round that then produces enough events pushes it past the end of any tail-only scan — which read back null in a real evaluation run (the 38 KB smoke round read fine, the 100–500 KB cells did not), so a tail miss is followed by a bounded full-file read. The locate takes the round's **cwd** alongside the thread id: concurrent delegations put several cells' files inside one time window, and `session_meta.payload.cwd` is what tells them apart. A window holding candidates but none in this round's directory reports nothing rather than a neighbour's round — except when the window holds exactly one candidate, where the mismatch is path skew rather than ambiguity. 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.
142
-
143
- **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> codex exec …` and everything else (stream parse, settle, rollout readback, recording) is byte-for-byte unchanged. `env` must name the in-container `CODEX_HOME`, and it should be the read-write bind mount of the host scoped home — the rollout readback reads the host copy. A container round always takes the exec one-shot (the resident app-server is a host process) and declares no member bridge (a host unix socket cannot reach into the container). Measured: one "answer 2+2" round in an `eval-env:pinned` unit settles `completed` with output `4`, and `observedModel` reads back as `gpt-5.6-sol` from the rollout the container wrote into the host scoped home.
144
-
145
- **Resume.** The family tool (this package's `./tool` composition entry, sharing the family core's `./tool-factory` implementation) adds an optional `resume` parameter to the official `description`/`prompt` subset. A fresh delegation's result self-describes its handle (`追问请带 resume="<childSessionId>"`); passing it back continues the same codex thread (`codex exec --json resume <thread_id>`) in the same dsh child session, with per-round accounting. The handle is read only from the `resume` parameter and resolved by the `localAgent` registry only for the recording parent session and provider — a forged handle is rejected before any CLI process starts.
146
-
147
- **Event-stream mirror.** The subagent session mirrors the `codex exec --json` stream in order: `reasoning` → reasoning blocks, `agent_message` → reply text, `command_execution`/`web_search_call`/`function_call_output` → tool lines (`[工具 Bash] <command> → output`), `file_change` → an `ApplyPatch` card (kind and path per change), `function_call` (e.g. the multi-agent `wait`) → a card under its own name with the `function_call_output` merged in; the final `agent_message` is the run output and the round's usage rides the last mirrored assistant message. Resumed rounds append their own turn without duplication. Aborting settles the tool result immediately while keeping the events and usage already received.
148
-
149
- **Model experience.** Each delegation is a fresh one-shot `codex exec` in the delegating session's workspace. The parent submits only the task text and sees only the final answer or the exact error — Codex commentary, tool activity, and workspace diffs never cross into the parent session, and child tokens never enter the parent's context.
150
-
151
- **Live driver (`live: true`).** Replaces the per-round spawn: the member's first delegation brings up one resident `codex app-server --stdio` process (same scoped home, same `-c` member-bridge declaration), creates a persisted thread (`thread/start` with `ephemeral: false`), and every later round is a `turn/start` to that living runtime; `item/completed` events fold into the child session as they arrive (same `CodexTranscriptLine` fold and append core as exec, the last line held back until `turn/completed` to carry usage), and `cancel` lands as `turn/interrupt` — the process survives and the thread stays continuable. Approval-shaped server→client requests are auto-answered unattended (cancel/decline, matching exec behavior). Runtimes are reclaimed after an idle timeout (the app-server wire has no shutdown method: stdin EOF → SIGTERM ladder); after a crash the next round re-spawns and `thread/resume`s the on-disk thread; a handshake failure trips a cooldown breaker and falls back to exec per round.
152
-
153
- **Accounting.** The provider opens `turn/start` at spawn and closes `turn/end` at settle — including on `error`/`aborted` — so `subagentTiming` duration equals real CLI runtime; the final assistant message carries token usage parsed from the event stream. Codex's `input_tokens` includes cache hits, so the uncached bucket is `input_tokens − cached_input_tokens`, `cached_input_tokens` maps to cache read, and there is no cache-write concept — the `tokenUsage` projection never double-counts cache hits. Resumed rounds repeat this under their own turn number. **Usage fallback for non-completed terminal states (aborted/error).** An interrupted or failed round never sees `turn.completed`, so its event stream carries no usage — yet codex has already written the round's real token spend to the rollout file under the scoped home. The exec mirror then recovers the LAST `token_count` of THIS run's rollout file and attaches it as the round's usage: the file is located by thread id (the `session_meta` head id), falling back to the spawn-time window when the stream was truncated before `thread.started`; the caliber is byte-identical to `turn.completed` (`input − cached` and the rest, via the shared `usageFromCodex`). A hard kill that wrote no `token_count` at all still leaves the round without usage — the fallback never guesses.
154
-
155
- </details>
40
+ Needs DSH 0.1.5-rc.1 or later; verified on 0.2.1-rc.1.
156
41
 
157
- ## Development
42
+ ## For developers
158
43
 
159
- Part of the [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo (`packages/local-agent-codex`). Issues and contributions welcome there.
44
+ - Config keys (sandbox, default model, resident mode), where the endpoint credential is stored, custom endpoints, read-back, and internals are in the [full reference](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/local-agent-codex/docs/reference.en.md).
45
+ - To install it alone, name the core too: `dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-codex`; the delegation tool is granted by a mode and won't appear unless a mode names it.
46
+ - Part of [dsh-plugins](https://github.com/Khorsheed/dsh-plugins); open issues and contributions there. Changes: [CHANGELOG.md](CHANGELOG.md).
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/local-agent-codex/README.en.md
5
- packages/local-agent-codex/README.en.md: 3c6d3b8d99fb9689ab77216dad33308e80432f10
6
- packages/local-agent-codex/README.md: 868a96be66c115ee92f4ba3cce80d3efc3d50858
5
+ packages/local-agent-codex/README.en.md: 86803cc98209985a1bce01c21dde37ff24c58b20
6
+ packages/local-agent-codex/README.md: baa5511e5dba84d3f1e3abe05d57680202718cfb
package/README.md CHANGED
@@ -2,158 +2,45 @@
2
2
 
3
3
  [English](README.en.md) | 中文
4
4
 
5
- 把编码任务甩给你本机装好的 Codex CLI 跑——端点凭据或它自己的登录、自己的会话记录,你的 `~/.codex` 一根手指都不碰。
5
+ 把编码任务交给你电脑上的 Codex:过程实时看得见,跑完结果回到当前会话,还能接着聊。
6
6
 
7
- dsh 的 agent 已经能干活,但有些任务你就是想交给本机的 Codex CLI:它的账号、它的模型、你熟悉的工具链。这个插件把 Codex 接成 dsh 的本地子代理——任意 agent preset 都能经 `subagent_codex` 工具一句话委派;委派跑在插件隔离的作用域目录(`$DSH_HOME/local-agent/codex`)里,你的个人 config、凭据、会话完全不被触碰;过程细节(推理、工具调用、文件改动)实时镜像在子会话里,最终回答回到父会话。认证是端点凭据制(`baseUrl` + `apiKey`,中转/官方平台 key 均可):容器轮与宿主轮用同一套凭据,容器里不需要再授权一次。没有网页登录、没有设备码,OAuth 流程在本包不存在。
7
+ 有些活你就是想让 Codex 来干,以前得自己开终端跑、再把结果搬回来。装上之后,你在会话里说一句「这个交给 Codex」,agent 就把任务交给本机的 Codex;它的推理、工具调用和改了哪些文件实时出现在子会话里,跑完把结果带回来,附上真实的用量和耗时。Codex 在自己单独的目录里运行,你电脑上原来的 `~/.codex` 配置、登录和会话一点不碰。
8
8
 
9
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/08-local-agent.png" width="640" alt="设置 → 插件配置页里的 Local Agent · Codex 卡片,卡头状态点一眼可见授权状态">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-codex.png" width="560" alt="展开的 Codex 一节:已登录的 Codex 和版本号,端点地址、API Key、「验证端点并获取模型」和可用模型名单">
10
10
 
11
- ## 特性
11
+ ## 能做什么
12
12
 
13
- - **按会话授予的委派**——`subagent_codex` 工具是本包的 `./tool` 组合入口,由点名它的 agent 预设(如开发模式)授予该预设的会话,无需逐 preset 配置。
14
- - **作用域目录隔离**——Codex 的全部状态(config、凭据、rollout 会话记录)留在 `$DSH_HOME/local-agent/codex`,与你的 `~/.codex` 互不干扰。
15
- - **端点凭据(唯一的认证方式)**——设置卡「认证」区填 `baseUrl` + `apiKey`(保存进默认 scope 的 `endpoint-auth.json`,0600,只存本机)或预配在 profile 配置行即已认证;每个命名 scope 可用自己的 `endpoint-auth.json` 按字段覆盖部署级配置。每轮委派以自定义 provider(`wire_api=responses`,实测 codex-cli 0.163.0 经中转直通)路由到端点,key 经进程环境传递。宿主 shell 的环境变量从不被读取;没有网页登录。
16
- - **常驻模式(live)**——每成员常驻一个 `codex app-server` 进程,按轮发 turn:输出实时流入子会话、取消不杀进程、崩溃后自动续上原线程;关闭或通道起不来即回一次性 `codex exec`,热切换无需重载。
17
- - **设置卡片**——core 的「本机 Agent 委派」家族卡里的一节(Plugins 页;旧版 core 上回落为本包自己的 Plugins 详情页卡):登录授权、可用模型(有序短名单,第一个为默认;留空 = 跟随旧链)、常驻开关,保存即于下一轮生效。
18
- - **线程续聊**——传入 `resume="<childSessionId>"` 在同一个 dsh 子会话里继续同一个 codex 线程,宿主重启后也续得上。
19
- - **自定义端点**——设置卡或 profile 行配好端点凭据即路由到你的中转;也可手改作用域 `config.toml` 的自定义 provider(文件保持权威,不被覆盖)。
20
- - **回读与独立工作目录**——每轮从本轮 rollout 回读实际模型、CLI 版本与用量写进委派记录;定位按 threadId + cwd + 时间窗,并发跑也读的是自己那一轮。编排器可用 `cwd` 选项给每格独立目录,resume 换目录即拒绝。
21
-
22
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-codex-card.png" width="640" alt="展开的 Codex 设置卡:认证状态与登录/退出、默认模型、常驻模式开关">
13
+ - **一句话交出去**:告诉 agent 哪段活给 Codex,它会自己写好要求交过去,结果回到当前会话。
14
+ - **过程看得见**:推理、执行的命令和改动的文件实时出现在子会话里,可以随时停下。
15
+ - **接着聊,重启也续得上**:让 agent 追问,或者打开子会话直接发消息,继续的是同一个 Codex 线程;DSH 重启之后也接得上。
16
+ - **填好 key 就能用**:在设置卡片里填端点地址和 API Key(官方平台的 key 或中转的 key 都行),点「保存凭据」,再点「验证端点并获取模型」确认能连上。
17
+ - **自己挑模型**:在「可用模型」里勾选、拖动排序,排第一的就是默认模型;子会话里也能临时换模型和推理强度。
18
+ - **常驻模式更快**:打开「常驻模式(live)」,Codex 进程常驻,输出实时流进子会话;停下不杀进程,崩溃后自动续上原来的线程。
23
19
 
24
20
  ## 安装
25
21
 
26
- 前置依赖:一个可运行的 dsh profile 和 `PATH` 上的 Codex CLI(`codex`)——插件不安装它、也不替你登录。
27
-
28
- ```sh
29
- dsh plugin --profile web add @khorsheed/dsh-local-agent
30
- dsh plugin --profile web add @khorsheed/dsh-local-agent-codex
31
- ```
32
-
33
- 两个包必须同时点名——`dsh plugin add` 只调和**直接**依赖,家族 core 不会被传递依赖自动挂载。重启 web 实例后生效,然后在「本机 Agent 委派」家族卡(Plugins 页)的 Codex 一节「认证」区填端点与 key 即可(也可预配在 profile 的本包配置行);都不配则状态为未认证。
34
-
35
- 卸载:
36
-
37
- ```sh
38
- dsh plugin --profile web remove @khorsheed/dsh-local-agent-codex
39
- ```
40
-
41
- 作用域目录(`$DSH_HOME/local-agent/codex`)会刻意保留,重装无需重新登录;删除它即可清除全部痕迹。
42
-
43
- ## 配置
22
+ 先在电脑上装好 Codex CLI(命令行里能运行 `codex`),插件不替你装。然后在 DSH 的「插件 → 添加插件」里输入下面的包名,点安装:
44
23
 
45
- 可选,写在 profile patch 层:
46
-
47
- ```yaml
48
- - id: local-agent-codex
49
- config:
50
- sandbox: workspace-write # codex exec 策略:read-only | workspace-write | danger-full-access
51
- baseUrl: https://your-relay.example/openai/v1 # 可选:部署级端点(OpenAI 兼容 /v1 根);不配则由 scope 文件或网页登录决定
52
- apiKey: sk-... # 可选:部署级端点 key(Bearer);只放本地,绝不入库
53
- model: gpt-5.2 # 可选:每轮委派以它起 CLI;不写就一个模型参数都不传(见下)
54
- live: false # 常驻驱动:每成员常驻一个 codex app-server 进程,按轮发 turn(运行时级优雅中断、事件推送镜像);关闭或通道不可用即回一次性 exec
55
- liveIdleMs: 1800000 # 常驻 runtime 空闲回收时限(默认 30 分钟)
56
24
  ```
57
-
58
- ### 端点凭据(`baseUrl` + `apiKey`)
59
-
60
- **配了 key = 每轮走端点。**scope 的解析结果(`<作用域目录>/endpoint-auth.json` 按字段覆盖 profile 配置行)带着非空 key 时,每轮委派在 argv 上注入一个自定义 provider(`-c model_provider=… -c model_providers.…={base_url, env_key=OPENAI_API_KEY, wire_api=responses}`),key 经进程环境传递——不写 `config.toml`、不读 `auth.json`,网页登录完全不参与;一次性 exec、常驻 app-server、模型目录探测同一套凭据。key 不带 `baseUrl` 时用 OpenAI 官方端点(官方平台 key 不需要中转,内置 provider 读同一个环境变量)。清掉 key(设置卡留空保存)即未认证。
61
-
62
- **设置卡「认证」区编辑的就是默认 scope 的 `endpoint-auth.json`**(0600,只存本机;经家族 gateway 的 `endpointAuth`/`configureEndpoint` Remote,会话不在场也能读写),密钥绝不写进 profile 文件,文件名也命中评测契约哈希的凭据拒绝名单、不进条件指纹。旁边的「验证端点并获取模型」按钮对**已保存**的端点跑一次无 token 探测(`GET <baseUrl>/v1/models`,Bearer 头),返回可达性与端点供应的模型列表。文件里还可手写 `"wireApi": "chat"`(卡片不编辑它,保存时保留)适配只支持 chat/completions 的中转。
63
-
64
- > **凭据错误的代价是重试时长**——一把失效的 key 会让 CLI 在端点前重试数分钟才报出 401;`/codex status` 的 `rejected` 等级在那一轮 settle 后才出现。重存 key(重写 endpoint-auth.json)即恢复。
65
-
66
- > **迁移说明(下一发布线,破坏性变更)**——网页登录整体移除:`/codex login` 不再存在,设备码/浏览器授权随之删除;升级后首次启动会清扫作用域目录里残留的 `auth.json`,避免一份陈旧授权在状态显示未认证时仍悄悄可用。升级前先在设置卡或 profile 行配好 `baseUrl` + `apiKey`(官方平台 key 同样可用),否则升级即未认证。
67
- >
68
- > **logout 只清 scope 自己的文件**——`/codex logout` 删除 scope 的 `endpoint-auth.json`;凭据完全来自 profile 配置行时 logout 拒绝并提示去改 profile。
69
-
70
- **旧键迁移。** live 轮次统一消费增量输出:旧 `liveMirrorGranularity: event | token` 配置继续兼容读取,但不再影响行为,也不会改变运行中的进程。评测继续保留 exec 驱动;最终内容仍以 provider 完成项为准,包括工具记录和用量。
71
-
72
- ### 默认模型(`model`)
73
-
74
- **不写 = 今天的表现。**没有这个键时,本插件在 argv 上一个模型参数都不加:跑哪个模型由作用域 `config.toml` 的顶层 `model` 决定,它也没有时由 codex 自己的默认决定。
75
-
76
- **写了 = 每轮委派以它起 CLI。**新起一轮与续接(resume)同等对待:
77
-
78
- | 驱动 | 传法 |
79
- |---|---|
80
- | 一次性(默认) | `codex exec -m <model> …`;`-m` 是 `codex exec` 自己的选项,因此排在 `resume` 子命令之前(`codex exec resume` 不认自己的 `-m`) |
81
- | 常驻(`live: true`) | `codex app-server -c model="<model>" --stdio`;app-server 没有 `-m`,用 CLI 为此提供的进程级 `-c` 覆盖 |
82
-
83
- 作用域 `config.toml` 不会被改写——`-m` 每轮覆盖它,文件仍是你编辑的样子。
84
-
85
- 设置卡「可用模型」区维护一份**有序模型短名单**:点「添加模型」从端点当前供应的目录里勾选加入,列表里只会有端点真实供应的模型;拖动行调整顺序。**第一个模型就是默认模型**——每轮委派以它起 CLI(一次性 `codex exec -m`,常驻 app-server 的 `-c model=…`),优先级只低于成员会话级覆盖与委派级指定。列表保存在作用域目录的 `models.json`(每个命名 scope 可各有一份,与端点凭据同一套文件机制),编辑即保存,下一轮委派生效。**留空 = 回退旧链**(下面的 `model` 键 → 作用域 `config.toml` → CLI 内置)。成员级模型和推理强度修改使用下文的持久控制面。
86
-
87
- 丰富目录的词汇来自端点本身(`GET <baseUrl>/v1/models`,与「验证端点并获取模型」按钮同一次探测)——本地注册表证明不了端点供应什么,失效的 key 再也不能躲在 CLI 本地列表后面。原生 app-server 的 `model/list` 探测降级为元数据补全,与端点探测并发:它独占显示名、隐藏候选、推理档位与 `isDefault` 编译默认,端点供应的 id 命中注册表即带走这些元数据,只被端点供应的 id 以裸条目列出;原生探测失败只警告一次,端点数据独自成完整答案。端点无凭据、拒绝凭据、状态异常或答非 `/v1/models` 形态时目录带原因空载;传输故障抛错,缓存保留最后一次成功快照并标过期重试(实测 openproxy 短暂 500 后自愈,正是这条语义)。查询上下文跟随成员的 scope 和 cwd。共用模型菜单和成员 effort 控制已接入。运行中选择排到下一完整轮次(含工具续跑);core 统一持有当前/待生效配置、撤销与重试,冻结评测成员禁止变更。原生目录候选不等于账号权限证明。
88
-
89
- **委派级的模型优先。**编排器可以经门面 `DelegationCallOptions.model` 给**某一次委派**点名模型,它排在这个键之前(固定顺序:会话覆盖 > 委派记录 > 可用模型首项 > 本键 > 作用域 config > CLI 内置)。首轮请求的值记进委派记录,resume 轮照它重发——resume 不接受 model 参数。常驻模式下带模型的轮次不再被拒绝:它成为该成员的**起始模型**,在 app-server 起进程时绑定;若该成员的常驻 runtime 绑定的是另一个模型,先回收重生(同一 codex 线程经 thread/resume 续上),再以所点模型起新一轮。
90
-
91
- **成员级切换(作曲器模型选择器)。**成员会话里可以按会话切换模型:一个内存中的会话级覆盖,优先级最高,宿主重启即失。切换在有轮次进行中时被拒绝;空闲时若成员有常驻 runtime 且绑定模型不同,切换即回收该 runtime——下一轮以新模型重生,CLI 会话(rollout)本身延续。一次性(exec)驱动下没有常驻进程,覆盖直接决定下一轮的 `-m`。
92
-
93
- **这不是评测的缺口。**评测 run 的条件在建立时冻结:run 跑到一半改这个键,下一轮的模型回读会发现声明模型 ≠ 实测模型,run 直接判为 misattributed 而失败(冻结决策 5)。换句话说「切了新 run 照新走、进行中的 run 不被悄悄换掉」是设计出来的,不是漏掉的。
94
-
95
- ## 自定义端点
96
-
97
- 端点凭据(设置卡/profile 行)是唯一带凭据的路线。仍可在作用域 `$DSH_HOME/local-agent/codex/config.toml` 里**新增自定义 provider**(内置 provider 不可覆盖、`OPENAI_BASE_URL` 不生效、预置逻辑从不覆盖已存在的 config),但它的 `env_key` 读的是子进程环境——宿主 shell 的同名变量会被清洗,只有端点凭据的 key 会随行,所以手工 provider 实际只对**不需要认证**的端点(如本地路由器)有用:
98
-
99
- ```toml
100
- [model_providers.dsh-router]
101
- name = "dsh-router"
102
- base_url = "https://your-router.example/v1"
103
- model_provider = "dsh-router"
25
+ @khorsheed/dsh-bundle-local-agent
104
26
  ```
105
27
 
106
- 最后一行为委派选择该 provider;文件其余内容必须保留。端点凭据带 key 时以它为准(argv 上的 provider 覆盖文件的 `model_provider` 选择)。
107
-
108
- ⚠️ **凭据暴露**:端点 key 会发送给处理请求的端点——只使用你控制或信任的端点。每次委派在 info 级别记录生效的端点。
28
+ Codex 会跟着「多Agent协作」套件一起装好(各家本机 agent 共用同一个底座,单装用不起来);如果你主要写代码,直接装开发套件 `@khorsheed/dsh-dev-suite` 也包含它。装完重启 DSH,到「插件」页的「本机 Agent 委派」卡片里,展开 Codex 一节填好端点和 API Key。
109
29
 
110
- **评测快照(effectiveSettings)。** 本 harness 向注册表声明一份实时读取的公平性设置快照,供评测条件哈希使用:drive(exec/live)、sandbox 策略(插件配置)、推理强度(读作用域 config 的顶层 `model_reasoning_effort`)、端点是否固定(端点凭据优先——scope 的 `endpoint-auth.json` 逐字段覆盖插件配置;没有 key 才读作用域 config 的自定义 provider;只报主机名,key 材料永远不进快照)、已配置模型(先看作用域 `models.json` 的首项,没有才看插件配置的 `model` 键——它每轮覆盖文件;再没有才读作用域 config 的顶层 `model`;都没有就不给字段)、CLI 版本(`codex --version`,按可执行文件路径+mtime 缓存;探测不到即字段缺位)。`/codex status` 与 `LocalAgentStatus` Remote 附带同一份快照。
111
-
112
- ## Compatibility
113
-
114
- - npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 公开 API 兼容。生成中的内容走 local-agent 瞬时 Remote 与公开 Conversation 节点;增量耐久检查点负责恢复,最终原生消息保留转写与用量语义。端到端延迟验收由 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-codex` 命名空间挂载;0.1.5 线不再呈现卡片(宿主删除了 `settings.plugin.item` 席位),其余表面不变。
115
- - 源码线(deepseek-harness master):✅(verifiedHost: 0.2.1-rc.1)——host-021 波对 0.2.1-rc.1 树全量 build+test 绿;委派迁移到 `ctx.subagents.startActivation`(受管 activation),run.id 改为每次执行唯一。
30
+ 把活交给 Codex 要在带委派的模式里进行,比如「开发模式」:开一个新会话、选这个模式,对 agent 说「把这个交给 Codex」。在其它模式里看不到 Codex 委派是正常的。
116
31
 
117
32
  ## 已知限制
118
33
 
119
- - **没有网页登录**——认证只有端点凭据一条路(设置卡/profile 行,官方平台 key 与中转 key 均可);ChatGPT 订阅的 OAuth 路径不在本包。
120
- - **`codex exec` 非交互运行**——沙箱策略本需审批的动作会被拒绝而非弹提示;常驻模式下审批类请求同样按无人值守策略自动谢绝(cancel/decline)。见 `sandbox` 配置。
121
- - **命名 scope 只走一次性 exec**——常驻 app-server 绑定缺省作用域目录,带 scope 的委派不进 live。
122
- - **headless 注意**——`/codex` 命令需要 Web 会话;headless 仍可通过挂载工具行的组合进行委派。
123
-
124
- ## 实现原理
125
-
126
- <details>
127
- <summary>内部结构(点击展开)</summary>
128
-
129
- **命名 scope。** `/codex login --scope <名>` 会在 `<homesRoot>/codex@<名>` 里另开一份作用域目录:目录建立时自动跑同一份 `config.toml` provision(把凭据存储钉在文件而非 macOS keychain),因此该 scope 的登录落在它自己的 `auth.json` 里。带 scope 的委派用它跑 `codex exec`、把 rollout 写进它、从它回读;只走 exec——常驻 app-server 绑的是缺省作用域目录。
34
+ - 只支持端点和 API Key,不支持用 ChatGPT 订阅登录。
35
+ - Codex 没人在旁边批准:本来要你点头的操作会被直接拒掉,不会弹出来问你。
36
+ - `/codex` 这组命令只能在网页会话里用。
130
37
 
131
- **bundle 组成。** patch 注册 `codex` harness(`CODEX_HOME` 作用域目录、PTY 登录、rollout 文件会话记录)——恰好一行 provider 行;`subagent_codex` 工具自家族折叠起是本包的 `./tool` 组合入口,由点名它的 agent 预设按会话授予(dev 预设的 `tool-subagent-codex-local` 行),不再挂 profile 根。家族沿用官方模型名 `subagent_codex`:官方 preset 的同名行本就 `disabled: true`,patch 对 profile 根的同名行再禁一次,社区安装永远不会注册出两个 `subagent_codex`;刻意在自有 preset 层重新启用官方行的用户会得到响亮的重名冲突,按设计必须二选一。`codex-local` provider 在该作用域目录下 spawn `codex exec`(live 未启用时的一次性驱动)。浏览器半部分随本包 `./client` 提供:Codex 设置卡组合家族 core 的共享 `ProviderAuthBlock`,渲染为 core「本机 Agent 委派」家族卡的一节——宿主服务 `local-agent-codex` 命名空间时经 `configForms.whileServed` 挂载,独立安装、家族 bundle、模式包同一节(旧版 core 上回落为本包自己的 `plugins.item` 卡);卡片写入经绑定的 settingsScope 热生效,不用重载。core 本身来自声明为依赖的 `@khorsheed/dsh-local-agent`。
132
-
133
- **凭据。** 端点凭据制,没有登录流程:scope 的 `endpoint-auth.json`(0600)按字段覆盖 profile 配置行的 `baseUrl`/`apiKey`,解析结果带 key 时每轮在 argv 注入自定义 provider(`wire_api=responses`,key 经进程环境传递,实测 codex-cli 0.163.0 全新 scope 下无需任何登录文件直通中转)。首次启动写入一份最小 `config.toml`,固定 `cli_auth_credentials_store = "file"`——Codex 默认的 `auto` 会解析到 macOS keychain,在作用域目录里手跑 `codex login` 会把凭据泄漏到目录之外;已存在的 config 保持不动。每次启动还会清扫网页登录时代残留的 `auth.json`——本包不再读取它,留着只会让一份陈旧授权在状态显示未认证时仍悄悄可用。`/codex logout` 删除作用域 `endpoint-auth.json`,之后重新配置即可换 key。
134
-
135
- **会话记录。** `/codex sessions` 列出作用域目录的 `sessions/YYYY/MM/DD/rollout-*.jsonl` 文件——仅本插件委派产生的会话,绝不含你的私人会话。会话头部的记录入口(家族 core 的客户端提供)把同一份列表收窄到 `workDir` 与当前会话 cwd 一致的记录。
136
-
137
- **回读与 cwd 覆盖。** 每轮 settle 后,provider 从本轮自己的 rollout 文件里回读三件事,随 `settled` 进度事件上报并合并进 `delegations.jsonl`:模型(`turn_context.payload.model`——codex 0.144.0 的 exec 流事件根本不带 model,rollout 是唯一权威;按本轮时间窗过滤,resume 线程里先前轮次的模型不会被误读)、CLI 版本(`session_meta.payload.cli_version`——服务本轮的那个 codex build 自己写下的,比事后探测可执行文件更准)、以及非正常结束时流里缺失的 token 用量(最后一条 `token_count`)。取不到即缺位,绝不猜测。
138
-
139
- **工具调用计数。** 每轮 settle 时,provider 顺带数出本轮的工具调用,随 `settled` 进度事件上报(`toolCalls: { count, byName }`)。计数只来自流解析**已经走过**的 `item.completed` 分支,不新增任何解析路径:`command_execution`、`web_search_call`、`file_change`、`function_call` 各记一次,`function_call_output` 是结果、不计。`byName` 的键是 **codex 自己的 item 类型**(`command_execution`),不是镜像卡片上的显示名(`Bash`)——记的是 CLI 怎么叫这件事。本轮一份,绝不累计;一次都没调用就整个字段缺位(缺席 ≠ 0)。实测:一轮「列目录并统计文件数」回读 `{count: 1, byName: {command_execution: 1}}`,两条命令的一轮回读 `{count: 2}`。
140
-
141
- 定位与扫描两处都按并发校准过。`turn_context` 是 codex 在**回合开始**时写的,所以一轮里之后产生的事件一多就会把它挤出文件尾——只扫尾部会读回 null(一次真实评测跑就是这么丢的:38 KB 的 smoke 轮读得到,100–500 KB 的正式格读不到),因此尾部扫不到就再做一次有界的整文件读。定位则在 threadId 之外把**本轮 cwd** 也算进去:并发委派会让多个格的文件落进同一个时间窗,`session_meta.payload.cwd` 才是区分它们的字段;窗口里有候选却没有一个对得上本轮目录时,宁可什么都不报也不报邻居那一轮(窗口里只有一个候选除外——那是路径写法差异,不是歧义)。编排器还可以经门面 `DelegationCallOptions.cwd` 给本轮指定工作目录(记录进 `cwd` 字段);resume 轮解析出的目录若与首轮记录不一致,进程启动前即 fail loud——CLI 会话延续的是首轮所在目录的上下文。
142
-
143
- **容器内委派。** 编排器可以经门面 `DelegationCallOptions.exec`(`{ container, workdir, env? }`)让本轮跑在一个**已取得的容器**里:argv 变成 `docker exec -w <workdir> [-e NAME…] <container> codex exec …`,其余(流解析、settle、rollout 回读、记录)逐字节不变。`env` 必须给出容器内的 `CODEX_HOME`,且它应当是宿主作用域目录的 rw bind 挂载点——rollout 回读读的是宿主那份文件。容器轮固定走 exec 一次性驱动(长驻 app-server 是宿主进程),且不声明成员桥(宿主 unix socket 进不去容器)。实测:`eval-env:pinned` 单元里一次「回答 2+2」settle 为 `completed`,输出 `4`,`observedModel` 从容器写进宿主作用域目录的 rollout 里回读为 `gpt-5.6-sol`。
144
-
145
- **续聊(resume)。** 家族工具(本包的 `./tool` 组合入口,实现共享自家族 core 的 `./tool-factory`)在官方 `description`/`prompt` 子集上增加可选 `resume` 参数。首次委派的结果文本自述句柄(`追问请带 resume="<childSessionId>"`);后续轮次传回它即在**同一个** dsh 子会话里继续**同一个** codex 线程(`codex exec --json resume <thread_id>`),按轮记账。句柄只从 `resume` 参数读取,localAgent registry 仅对记录该委派的同一 parent 会话与 provider 解析——伪造句柄在任何 CLI 进程启动前就被拒绝。
146
-
147
- **事件流镜像。** 子代理会话按顺序镜像 `codex exec --json` 事件流:`reasoning` → 推理块、`agent_message` → 回复文本、`command_execution`/`web_search_call`/`function_call_output` → 工具行(`[工具 Bash] <command> → output`),`file_change` → `ApplyPatch` 卡(逐 change 列出 kind 与路径),`function_call`(如 multi-agent 的 `wait`)→ 同名工具卡、结果由 `function_call_output` 并入;最终 `agent_message` 作为运行输出,当轮用量挂在最后一条镜像的 assistant 消息上。续聊轮各自追加自己的 turn,不会重复。中止会让工具结果立即 settle,并保留已收到的事件与用量。
148
-
149
- **模型体验。** 每次委派都是委派 Session 工作区内一个全新的一次性 `codex exec` 进程。父级只提交任务文本、只看到最终回答或精确错误——Codex 的评论、工具活动、工作区 diff 不会跨进父级会话,子会话 token 也永不进入父级上下文。
150
-
151
- **长驻驱动(`live: true`)。** 替代每轮 spawn:成员首轮委派拉起一个常驻 `codex app-server --stdio` 进程(同 scoped home、同 `-c` 成员桥声明),创建持久线程(`thread/start`,`ephemeral: false`),之后每轮 = 向活着的 runtime 发 `turn/start`;`item/completed` 事件即时折进子会话(与 exec 共享同一 `CodexTranscriptLine` 折叠与 append 核,最后一行留置到 `turn/completed` 以挂用量),`cancel` 落地为 `turn/interrupt`——进程不死、线程可续。审批类 server→client 请求按无人值守策略自动应答(cancel/decline,与 exec 行为一致)。runtime 空闲超时回收(app-server 无 shutdown 方法:stdin EOF → SIGTERM 阶梯),崩溃后下一轮自动重连并 `thread/resume` 盘上线程;握手失败进冷却熔断,逐轮回退 exec。
152
-
153
- **委派记账。** provider 在 spawn 时开 `turn/start`、settle 时关 `turn/end`——失败或取消(`error`/`aborted`)同样关闭——`subagentTiming` 的时长等于实际 CLI 运行时长;最终 assistant 消息携带从事件流解析的 token 用量。codex 的 `input_tokens` 含缓存命中,所以未缓存桶取 `input_tokens − cached_input_tokens`、`cached_input_tokens` 映射缓存读取、没有缓存写入概念——`tokenUsage` 不会双重计数缓存命中。每个续聊轮在各自递增的轮次号下重复这套记账。**非 completed 终态(aborted/error)的用量回落。** 被中止/失败的轮次永远收不到 `turn.completed`,事件流里没有用量——但 codex 已把本轮真实 token 消耗写进了 scoped home 的 rollout 文件。此时 exec 镜像改读**本次 run 的 rollout 文件末条 `token_count`** 挂用量:文件按线程 id(`session_meta` 头)定位,流在 `thread.started` 之前就被截断时按 spawn 时间窗回落;口径与 `turn.completed` 完全一致(`input − cached` 等桶,共享 `usageFromCodex`)。硬杀到连 `token_count` 都没写出的极端情况仍保持无用量,不猜测。
38
+ ## Compatibility
154
39
 
155
- </details>
40
+ 需要 DSH 0.1.5-rc.1 或更新版本,已在 0.2.1-rc.1 上验证。
156
41
 
157
- ## 开发
42
+ ## 给开发者
158
43
 
159
- 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo(`packages/local-agent-codex`)。问题与贡献请移步该仓库。
44
+ - 配置项(沙箱、默认模型、常驻模式)、端点凭据的存放、自定义端点、回读与内部结构,都在[完整参考](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/local-agent-codex/docs/reference.md)里。
45
+ - 单独安装要连同底座一起点名:`dsh plugin --profile web add @khorsheed/dsh-local-agent @khorsheed/dsh-local-agent-codex`;委派工具由模式授予,没有模式点名时不会出现。
46
+ - 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins),问题与贡献请到该仓库;变更见 [CHANGELOG.md](CHANGELOG.md)。