@khorsheed/dsh-local-agent-codex 0.1.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +136 -0
- package/README.i18n.yaml +6 -0
- package/README.md +136 -0
- package/cordis.patch.yml +42 -0
- package/lib/client.js +1066 -0
- package/lib/index.js +3550 -0
- package/lib/invariant.js +31 -0
- package/lib/types/client/SettingsCard.d.ts +76 -0
- package/lib/types/client/SettingsCard.js +200 -0
- package/lib/types/client/icons.d.ts +18 -0
- package/lib/types/client/icons.js +2 -0
- package/lib/types/client/index.d.ts +43 -0
- package/lib/types/client/index.js +88 -0
- package/lib/types/client/locales.d.ts +64 -0
- package/lib/types/client/locales.js +55 -0
- package/lib/types/codex-cli-provider.d.ts +347 -0
- package/lib/types/codex-cli-provider.js +1225 -0
- package/lib/types/index.d.ts +70 -0
- package/lib/types/index.js +272 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +33 -0
- package/lib/types/live-driver.d.ts +229 -0
- package/lib/types/live-driver.js +1279 -0
- package/lib/types/live-switch.d.ts +70 -0
- package/lib/types/live-switch.js +90 -0
- package/lib/types/model-broker.d.ts +102 -0
- package/lib/types/model-broker.js +187 -0
- package/lib/types/model-catalog.d.ts +44 -0
- package/lib/types/model-catalog.js +220 -0
- package/lib/types/provision.d.ts +80 -0
- package/lib/types/provision.js +213 -0
- package/lib/types/records.d.ts +120 -0
- package/lib/types/records.js +505 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +106 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# `@khorsheed/dsh-local-agent-codex`
|
|
2
|
+
|
|
3
|
+
English | [中文](README.md)
|
|
4
|
+
|
|
5
|
+
**Live output migration.** Live runs always consume incremental output. The old `liveMirrorGranularity: event | token` key is accepted for existing profiles but ignored; changing it never changes a running process. Exec remains available for evaluation. Final provider items remain authoritative, including tool history and usage.
|
|
6
|
+
|
|
7
|
+
Delegate coding tasks from any dsh agent preset to your locally installed Codex CLI. Delegations run under a plugin-scoped home, so your personal `~/.codex` — config, credentials, sessions — is never touched.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Delegate from any preset** — the `subagent_codex` tool mounts at the profile root; no per-preset setup.
|
|
12
|
+
- **Scoped-home isolation** — all Codex state lives in `$DSH_HOME/local-agent/codex`, separate from your `~/.codex`.
|
|
13
|
+
- **In-session login** — device-code `/codex login`, with auth status and sign-out under Settings → 本地 Agent.
|
|
14
|
+
- **Resume a thread** — pass `resume="<childSessionId>"` to continue the same codex thread in the same dsh child session.
|
|
15
|
+
- **Custom endpoint** — route Codex's LLM requests through your own router via a scoped `config.toml` provider.
|
|
16
|
+
- **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.
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Prerequisites: a dsh profile and the Codex CLI (`codex`) on `PATH` — the plugin neither installs it nor logs in for you.
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
dsh plugin --profile web add @khorsheed/dsh-local-agent
|
|
23
|
+
dsh plugin --profile web add @khorsheed/dsh-local-agent-codex
|
|
24
|
+
# restart the profile, then run /codex login once from a session
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both packages must be named explicitly — `dsh plugin add` reconciles only direct dependencies.
|
|
28
|
+
|
|
29
|
+
Uninstall:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web remove @khorsheed/dsh-local-agent-codex
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The scoped home (`$DSH_HOME/local-agent/codex`) is kept so a reinstall needs no fresh login; delete it to remove every trace.
|
|
36
|
+
|
|
37
|
+
## Config
|
|
38
|
+
|
|
39
|
+
Optional, in the profile patch layer:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
- id: local-agent-codex
|
|
43
|
+
config:
|
|
44
|
+
sandbox: workspace-write # codex exec policy: read-only | workspace-write | danger-full-access
|
|
45
|
+
model: gpt-5.2 # optional: every delegation round starts the CLI with it; absent passes no model flag at all (below)
|
|
46
|
+
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
|
|
47
|
+
liveIdleMs: 1800000 # idle lifetime of a resident runtime before reclaim (default 30 min)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Default model (`model`)
|
|
51
|
+
|
|
52
|
+
**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.
|
|
53
|
+
|
|
54
|
+
**Set = every delegation round starts the CLI with it.** A fresh round and a resume round are treated alike:
|
|
55
|
+
|
|
56
|
+
| Drive | How it is passed |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 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) |
|
|
59
|
+
| 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 |
|
|
60
|
+
|
|
61
|
+
The scoped `config.toml` is never rewritten: `-m` overrules it per round and the file stays exactly as you edited it.
|
|
62
|
+
|
|
63
|
+
The settings card’s "Default model" writes the provider setting for subsequent rounds. Its shared picker displays the scoped model directory, discovery source and completeness, plus an explicit model-ID input when needed. Clearing the selection follows the effective configuration/default chain. Saving does not interrupt an active round and requires no reload. If the shared picker is unavailable, the card retains its text-input fallback. Per-member model and effort changes use the durable controls described below.
|
|
64
|
+
|
|
65
|
+
The rich directory read now preserves native display labels, hidden candidates and reasoning options, traverses pagination, and uses the core cache and refresh subscription. Its context follows the member's scope and cwd. Failed refreshes retain the last successful snapshot as stale; configuration and history suggestions retain their source labels. 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.
|
|
66
|
+
|
|
67
|
+
**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 > 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.
|
|
68
|
+
|
|
69
|
+
**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`.
|
|
70
|
+
|
|
71
|
+
**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.
|
|
72
|
+
|
|
73
|
+
## Custom endpoint
|
|
74
|
+
|
|
75
|
+
Route Codex's LLM requests through your own endpoint by adding 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):
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[model_providers.dsh-router]
|
|
79
|
+
name = "dsh-router"
|
|
80
|
+
base_url = "https://your-router.example/v1"
|
|
81
|
+
model_provider = "dsh-router"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The last line selects the provider for delegations; keep the rest of the file intact.
|
|
85
|
+
|
|
86
|
+
⚠️ **Credential exposure**: the scoped `auth.json` token is sent to whatever endpoint serves the request — only use endpoints you control or trust. Each delegation logs the effective endpoint at info level.
|
|
87
|
+
|
|
88
|
+
**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 (read from the scoped config's provider, hostname only), the configured model (the plugin config's `model` key first — it overrules the file on every argv — then the scoped config's top-level `model`; absent when neither 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.
|
|
89
|
+
|
|
90
|
+
## Compatibility
|
|
91
|
+
|
|
92
|
+
- 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; suffix checkpoints provide recovery, and native final messages retain transcript and usage semantics. Browser P95 acceptance is tracked separately in the room coordinator proposal. Older hosts stay on the previous release line.
|
|
93
|
+
- source line (deepseek-harness master): ✅ (verifiedHost: 0.1.5-rc.1)
|
|
94
|
+
|
|
95
|
+
## Known Limitations
|
|
96
|
+
|
|
97
|
+
- **Login needs one browser step** — device-code only; no API-key path.
|
|
98
|
+
- **`codex exec` is non-interactive** — actions the sandbox policy would approve are denied, not prompted; see the `sandbox` config.
|
|
99
|
+
- **Headless caveat** — `/codex` commands need a Web session; headless can still delegate via a composition mounting the tool row.
|
|
100
|
+
|
|
101
|
+
## How it works
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary>Internals (click to expand)</summary>
|
|
105
|
+
|
|
106
|
+
**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.
|
|
107
|
+
|
|
108
|
+
**Bundle composition.** The patch registers the `codex` harness (scoped `CODEX_HOME`, device-code login, rollout-file session records) and mounts `subagent_codex` at the profile root; the `codex-local` one-shot provider spawns `codex exec` under that home. The settings section ships with the family core's `./client` half; the core itself comes from `@khorsheed/dsh-local-agent`, declared as a dependency.
|
|
109
|
+
|
|
110
|
+
**Login and credentials.** `/codex login` shows the device-code URL in-session and polls in the background; credentials land in the scoped home on authorization. First start writes a minimal `config.toml` pinning `cli_auth_credentials_store = "file"` — Codex's default `auto` would resolve to the macOS keychain, leaking credentials outside the scoped home and defeating this package's `auth.json` presence check; an existing config is left untouched. `/codex logout` deletes the scoped `auth.json`, so a later login authorizes a fresh account.
|
|
111
|
+
|
|
112
|
+
**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 settings section narrows the list to records whose `workDir` matches the session cwd.
|
|
113
|
+
|
|
114
|
+
**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.
|
|
115
|
+
|
|
116
|
+
**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}`.
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
**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.
|
|
121
|
+
|
|
122
|
+
**Resume.** The family tool (`@khorsheed/dsh-local-agent-tool-subagent`) 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.
|
|
123
|
+
|
|
124
|
+
**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.
|
|
125
|
+
|
|
126
|
+
**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.
|
|
127
|
+
|
|
128
|
+
**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.
|
|
129
|
+
|
|
130
|
+
**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.
|
|
131
|
+
|
|
132
|
+
</details>
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
Part of the [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo (`packages/local-agent-codex`). Issues and contributions welcome there.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record: the git blob hash of each side as of the
|
|
2
|
+
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
|
+
# editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/local-agent-codex/README.en.md
|
|
5
|
+
packages/local-agent-codex/README.en.md: 9e97dee795a54e41d13515d895365a7347751c2f
|
|
6
|
+
packages/local-agent-codex/README.md: a75ad178e28915254eb7ab2693a52ef7451cccfc
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# `@khorsheed/dsh-local-agent-codex`
|
|
2
|
+
|
|
3
|
+
[English](README.en.md) | 中文
|
|
4
|
+
|
|
5
|
+
**实时输出迁移。** live 轮次统一消费增量输出。旧 `liveMirrorGranularity: event | token` 配置继续兼容读取,但不再影响行为,也不会改变运行中的进程。评测继续保留 exec。最终内容仍以 provider 完成项为准,包括工具记录和用量。
|
|
6
|
+
|
|
7
|
+
把编码任务从任意 dsh agent preset 委派给你本地安装的 Codex CLI。委派在插件隔离的作用域目录下运行,你个人的 `~/.codex`——config、凭据、会话——完全不被触碰。
|
|
8
|
+
|
|
9
|
+
## 特性
|
|
10
|
+
|
|
11
|
+
- **任意 preset 皆可委派**——`subagent_codex` 工具挂在 profile 根,无需逐 preset 配置。
|
|
12
|
+
- **作用域目录隔离**——Codex 的全部状态留在 `$DSH_HOME/local-agent/codex`,与你的 `~/.codex` 互不干扰。
|
|
13
|
+
- **会话内登录**——device-code `/codex login`,设置 → 本地 Agent 显示认证状态并提供退出登录按钮。
|
|
14
|
+
- **线程续聊**——传入 `resume="<childSessionId>"` 在同一个 dsh 子会话里继续同一个 codex 线程。
|
|
15
|
+
- **自定义端点**——经作用域 `config.toml` 的自定义 provider 把 Codex 的 LLM 请求路由到你自己的路由端点。
|
|
16
|
+
- **回读与独立工作目录**——每轮从本轮 rollout 回读实际模型、CLI 版本与用量写进委派记录;定位按 threadId + cwd + 时间窗,并发跑也读的是自己那一轮。编排器可用 `cwd` 选项给每格独立目录,resume 换目录即拒绝。
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
前置依赖:一个可运行的 dsh profile 和 `PATH` 上的 Codex CLI(`codex`)——插件不安装它、也不替你登录。
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
dsh plugin --profile web add @khorsheed/dsh-local-agent
|
|
23
|
+
dsh plugin --profile web add @khorsheed/dsh-local-agent-codex
|
|
24
|
+
# restart the profile, then run /codex login once from a session
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
两个包必须同时点名——`dsh plugin add` 只调和**直接**依赖。
|
|
28
|
+
|
|
29
|
+
卸载:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web remove @khorsheed/dsh-local-agent-codex
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
作用域目录(`$DSH_HOME/local-agent/codex`)会刻意保留,重装无需重新登录;删除它即可清除全部痕迹。
|
|
36
|
+
|
|
37
|
+
## 配置
|
|
38
|
+
|
|
39
|
+
可选,写在 profile patch 层:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
- id: local-agent-codex
|
|
43
|
+
config:
|
|
44
|
+
sandbox: workspace-write # codex exec 策略:read-only | workspace-write | danger-full-access
|
|
45
|
+
model: gpt-5.2 # 可选:每轮委派以它起 CLI;不写就一个模型参数都不传(见下)
|
|
46
|
+
live: false # 长驻驱动:每成员常驻一个 codex app-server 进程,按轮发 turn(runtime 级优雅中断、事件推送镜像);关闭或通道不可用即回一次性 exec
|
|
47
|
+
liveIdleMs: 1800000 # 长驻 runtime 空闲回收时限(默认 30 分钟)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 默认模型(`model`)
|
|
51
|
+
|
|
52
|
+
**不写 = 今天的表现。**没有这个键时,本插件在 argv 上一个模型参数都不加:跑哪个模型由作用域 `config.toml` 的顶层 `model` 决定,它也没有时由 codex 自己的默认决定。
|
|
53
|
+
|
|
54
|
+
**写了 = 每轮委派以它起 CLI。**新起一轮与续接(resume)同等对待:
|
|
55
|
+
|
|
56
|
+
| 驱动 | 传法 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 一次性(默认) | `codex exec -m <model> …`;`-m` 是 `codex exec` 自己的选项,因此排在 `resume` 子命令之前(`codex exec resume` 不认自己的 `-m`) |
|
|
59
|
+
| 常驻(`live: true`) | `codex app-server -c model="<model>" --stdio`;app-server 没有 `-m`,用 CLI 为此提供的进程级 `-c` 覆盖 |
|
|
60
|
+
|
|
61
|
+
作用域 `config.toml` 不会被改写——`-m` 每轮覆盖它,文件仍是你编辑的样子。
|
|
62
|
+
|
|
63
|
+
设置卡「默认模型」修改后续轮次使用的 provider 配置。共享选择器展示当前作用域的模型目录、发现来源和完整性,并保留按需填写模型 ID 的入口。清空选择后跟随有效配置及默认值链。保存不会打断当前轮次,也无需重载;共享选择器不可用时,卡片保留文本输入兜底。成员级模型和推理强度修改使用下文的持久控制面。
|
|
64
|
+
|
|
65
|
+
丰富目录读面现已保留原生显示名、隐藏候选和推理选项,遍历分页,并接入 core 缓存与刷新订阅。查询上下文跟随成员的 scope 和 cwd。刷新失败保留最后成功快照并标为过期;配置项和历史建议保留来源标识。共用模型菜单和成员 effort 控制已接入。运行中选择排到下一完整轮次(含工具续跑);core 统一持有当前/待生效配置、撤销与重试,冻结评测成员禁止变更。原生目录候选不等于账号权限证明。
|
|
66
|
+
|
|
67
|
+
**委派级的模型优先。**编排器可以经门面 `DelegationCallOptions.model` 给**某一次委派**点名模型,它排在这个键之前(固定顺序:会话覆盖 > 委派记录 > 本键 > 作用域 config > CLI 内置)。首轮请求的值记进委派记录,resume 轮照它重发——resume 不接受 model 参数。常驻模式下带模型的轮次不再被拒绝:它成为该成员的**起始模型**,在 app-server 起进程时绑定;若该成员的常驻 runtime 绑定的是另一个模型,先回收重生(同一 codex 线程经 thread/resume 续上),再以所点模型起新一轮。
|
|
68
|
+
|
|
69
|
+
**成员级切换(作曲器模型选择器)。**成员会话里可以按会话切换模型:一个内存中的会话级覆盖,优先级最高,宿主重启即失。切换在有轮次进行中时被拒绝;空闲时若成员有常驻 runtime 且绑定模型不同,切换即回收该 runtime——下一轮以新模型重生,CLI 会话(rollout)本身延续。一次性(exec)驱动下没有常驻进程,覆盖直接决定下一轮的 `-m`。
|
|
70
|
+
|
|
71
|
+
**这不是评测的缺口。**评测 run 的条件在建立时冻结:run 跑到一半改这个键,下一轮的模型回读会发现声明模型 ≠ 实测模型,run 直接判为 misattributed 而失败(冻结决策 5)。换句话说「切了新 run 照新走、进行中的 run 不被悄悄换掉」是设计出来的,不是漏掉的。
|
|
72
|
+
|
|
73
|
+
## 自定义端点
|
|
74
|
+
|
|
75
|
+
把 Codex 的 LLM 请求路由到你自己的端点:在作用域 `$DSH_HOME/local-agent/codex/config.toml` 里**新增自定义 provider**(内置 provider 不可覆盖、`OPENAI_BASE_URL` 不生效、预置逻辑从不覆盖已存在的 config):
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[model_providers.dsh-router]
|
|
79
|
+
name = "dsh-router"
|
|
80
|
+
base_url = "https://your-router.example/v1"
|
|
81
|
+
model_provider = "dsh-router"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
最后一行为委派选择该 provider;文件其余内容必须保留。
|
|
85
|
+
|
|
86
|
+
⚠️ **凭据暴露**:作用域 `auth.json` token 会发送给处理请求的端点——只使用你控制或信任的端点。每次委派在 info 级别记录生效的端点。
|
|
87
|
+
|
|
88
|
+
**评测快照(effectiveSettings)。** 本 harness 向注册表声明一份实时读取的公平性设置快照,供评测条件哈希使用:drive(exec/live)、sandbox 策略(插件配置)、推理强度(读作用域 config 的顶层 `model_reasoning_effort`)、端点是否固定(读作用域 config 的自定义 provider,只报主机名)、已配置模型(先看插件配置的 `model` 键——它每轮覆盖文件;没有才读作用域 config 的顶层 `model`;都没有就不给字段)、CLI 版本(`codex --version`,按可执行文件路径+mtime 缓存;探测不到即字段缺位)。`/codex status` 与 `LocalAgentStatus` Remote 附带同一份快照。
|
|
89
|
+
|
|
90
|
+
## Compatibility
|
|
91
|
+
|
|
92
|
+
- npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 公开 API 兼容。生成中的内容走 local-agent 瞬时 Remote 与公开 Conversation 节点;后缀检查点负责恢复,最终原生消息保留转写与用量语义。浏览器 P95 另在 room 协调者提案中验收。更旧宿主留在前一发布线。
|
|
93
|
+
- 源码线(deepseek-harness master):✅(verifiedHost: 0.1.5-rc.1)
|
|
94
|
+
|
|
95
|
+
## 已知限制
|
|
96
|
+
|
|
97
|
+
- **登录需要一次浏览器授权**——仅 device-code,没有 API key 路径。
|
|
98
|
+
- **`codex exec` 非交互运行**——沙箱策略本需审批的动作会被拒绝而非弹提示;见 `sandbox` 配置。
|
|
99
|
+
- **headless 注意**——`/codex` 命令需要 Web 会话;headless 仍可通过挂载工具行的组合进行委派。
|
|
100
|
+
|
|
101
|
+
## 实现原理
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary>内部结构(点击展开)</summary>
|
|
105
|
+
|
|
106
|
+
**命名 scope。** `/codex login --scope <名>` 会在 `<homesRoot>/codex@<名>` 里另开一份作用域目录:目录建立时自动跑同一份 `config.toml` provision(把凭据存储钉在文件而非 macOS keychain),因此该 scope 的登录落在它自己的 `auth.json` 里。带 scope 的委派用它跑 `codex exec`、把 rollout 写进它、从它回读;只走 exec——常驻 app-server 绑的是缺省作用域目录。
|
|
107
|
+
|
|
108
|
+
**bundle 组成。** patch 注册 `codex` harness(`CODEX_HOME` 作用域目录、device-code 登录、rollout 文件会话记录),并把 `subagent_codex` 工具挂到 profile 根;`codex-local` 一次性 provider 在该作用域目录下 spawn `codex exec`。设置分区随家族 core 的 `./client` 半提供;core 本身来自声明为依赖的 `@khorsheed/dsh-local-agent`。
|
|
109
|
+
|
|
110
|
+
**登录与凭据。** `/codex login` 在会话中显示 device-code URL 并在后台轮询;用户授权后凭据写入作用域目录。首次启动写入一份最小 `config.toml`,固定 `cli_auth_credentials_store = "file"`——Codex 默认的 `auto` 会解析到 macOS keychain,把凭据泄漏到作用域目录之外并使本包的 `auth.json` 存在性检查失效;已存在的 config 保持不动。`/codex logout` 删除作用域 `auth.json`,之后重新登录即可换账号。
|
|
111
|
+
|
|
112
|
+
**会话记录。** `/codex sessions` 列出作用域目录的 `sessions/YYYY/MM/DD/rollout-*.jsonl` 文件——仅本插件委派产生的会话,绝不含你的私人会话。设置分区把列表收窄到 `workDir` 与当前会话 cwd 一致的记录。
|
|
113
|
+
|
|
114
|
+
**回读与 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`)。取不到即缺位,绝不猜测。
|
|
115
|
+
|
|
116
|
+
**工具调用计数。** 每轮 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}`。
|
|
117
|
+
|
|
118
|
+
定位与扫描两处都按并发校准过。`turn_context` 是 codex 在**回合开始**时写的,所以一轮里之后产生的事件一多就会把它挤出文件尾——只扫尾部会读回 null(一次真实评测跑就是这么丢的:38 KB 的 smoke 轮读得到,100–500 KB 的正式格读不到),因此尾部扫不到就再做一次有界的整文件读。定位则在 threadId 之外把**本轮 cwd** 也算进去:并发委派会让多个格的文件落进同一个时间窗,`session_meta.payload.cwd` 才是区分它们的字段;窗口里有候选却没有一个对得上本轮目录时,宁可什么都不报也不报邻居那一轮(窗口里只有一个候选除外——那是路径写法差异,不是歧义)。编排器还可以经门面 `DelegationCallOptions.cwd` 给本轮指定工作目录(记录进 `cwd` 字段);resume 轮解析出的目录若与首轮记录不一致,进程启动前即 fail loud——CLI 会话延续的是首轮所在目录的上下文。
|
|
119
|
+
|
|
120
|
+
**容器内委派。** 编排器可以经门面 `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`。
|
|
121
|
+
|
|
122
|
+
**续聊(resume)。** 家族工具(`@khorsheed/dsh-local-agent-tool-subagent`)在官方 `description`/`prompt` 子集上增加可选 `resume` 参数。首次委派的结果文本自述句柄(`追问请带 resume="<childSessionId>"`);后续轮次传回它即在**同一个** dsh 子会话里继续**同一个** codex 线程(`codex exec --json resume <thread_id>`),按轮记账。句柄只从 `resume` 参数读取,localAgent registry 仅对记录该委派的同一 parent 会话与 provider 解析——伪造句柄在任何 CLI 进程启动前就被拒绝。
|
|
123
|
+
|
|
124
|
+
**事件流镜像。** 子代理会话按顺序镜像 `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,并保留已收到的事件与用量。
|
|
125
|
+
|
|
126
|
+
**模型体验。** 每次委派都是委派 Session 工作区内一个全新的一次性 `codex exec` 进程。父级只提交任务文本、只看到最终回答或精确错误——Codex 的评论、工具活动、工作区 diff 不会跨进父级会话,子会话 token 也永不进入父级上下文。
|
|
127
|
+
|
|
128
|
+
**长驻驱动(`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。
|
|
129
|
+
|
|
130
|
+
**委派记账。** 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` 都没写出的极端情况仍保持无用量,不猜测。
|
|
131
|
+
|
|
132
|
+
</details>
|
|
133
|
+
|
|
134
|
+
## 开发
|
|
135
|
+
|
|
136
|
+
隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo(`packages/local-agent-codex`)。问题与贡献请移步该仓库。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# The dsh-local-agent-codex bundle patch: delegate dsh agents to the local
|
|
2
|
+
# Codex CLI under a scoped CODEX_HOME, and surface its session records. The
|
|
3
|
+
# family core row (`local-agent`, shared scoped-homes root) lives in the
|
|
4
|
+
# framework bundle @khorsheed/dsh-local-agent's own patch. This bundle declares
|
|
5
|
+
# that package as an npm dependency, which makes the core's module resolvable —
|
|
6
|
+
# it does NOT mount the core row: `dsh plugin add` reconciles only the
|
|
7
|
+
# profile's direct dependencies, so the core must be installed explicitly
|
|
8
|
+
# alongside this bundle, or this provider stays pending. No other patch inserts
|
|
9
|
+
# the core row id.
|
|
10
|
+
#
|
|
11
|
+
# Two rows are inserted:
|
|
12
|
+
# - local-agent-codex: this bundle's plugin registers the `codex` harness
|
|
13
|
+
# (CODEX_HOME scoped home, `codex login --device-auth`, rollout-file
|
|
14
|
+
# session records).
|
|
15
|
+
# - tool-subagent-codex-local: the family-owned delegation tool (schema
|
|
16
|
+
# description/prompt plus optional resume) over the `codex-local`
|
|
17
|
+
# provider. The toolName is `subagent_codex` — the family takes the
|
|
18
|
+
# model-facing name: the official preset row of that name ships
|
|
19
|
+
# `disabled: true`, and this patch disables it too (below) so a community
|
|
20
|
+
# install can never register two `subagent_codex` tools. A user who
|
|
21
|
+
# deliberately re-enables the official row in their own preset layer gets
|
|
22
|
+
# a loud duplicate-name conflict — by design, they must choose one. The
|
|
23
|
+
# family tool adds continuation: a later round may pass the dsh child
|
|
24
|
+
# session id returned by the first delegation to continue the same codex
|
|
25
|
+
# thread.
|
|
26
|
+
#
|
|
27
|
+
# The tool mounts at the profile root, so every agent preset can delegate
|
|
28
|
+
# without per-preset variants. The provider starts no codex process until a
|
|
29
|
+
# tool call or /codex login.
|
|
30
|
+
|
|
31
|
+
- id: tool-subagent-codex
|
|
32
|
+
disabled: true
|
|
33
|
+
|
|
34
|
+
- insert:
|
|
35
|
+
- id: local-agent-codex
|
|
36
|
+
name: '@khorsheed/dsh-local-agent-codex'
|
|
37
|
+
|
|
38
|
+
- id: tool-subagent-codex-local
|
|
39
|
+
name: '@khorsheed/dsh-local-agent-tool-subagent'
|
|
40
|
+
config:
|
|
41
|
+
provider: codex-local
|
|
42
|
+
toolName: subagent_codex
|