@0xgeorgemathew/zcode 0.4.4 → 0.5.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/README.md +69 -13
- package/bin/zcode.js +119 -19
- package/lib/bootstrap.js +2 -2
- package/lib/brief.js +69 -0
- package/lib/checks.js +3 -2
- package/lib/commands/doctor.js +3 -0
- package/lib/commands/exec.js +100 -35
- package/lib/commands/plan.js +301 -0
- package/lib/commands/sessions.js +60 -11
- package/lib/contract.js +220 -17
- package/lib/executor.js +141 -34
- package/lib/isolation.js +4 -1
- package/lib/models.js +84 -0
- package/lib/plan.js +424 -0
- package/lib/ports.js +45 -0
- package/lib/store.js +50 -0
- package/lib/taskspec.js +107 -0
- package/lib/ui.js +7 -2
- package/lib/workspace.js +24 -7
- package/package.json +1 -1
- package/skills/README.md +1 -1
- package/skills/claude-code/zcode/SKILL.md +104 -74
- package/skills/codex/zcode/SKILL.md +104 -76
package/README.md
CHANGED
|
@@ -18,8 +18,8 @@ The orchestrator keeps product decisions, design direction, and coordination. zc
|
|
|
18
18
|
<tr><td><b>Checks run by zcode</b></td><td>zcode runs each <code>--check</code> after the work. A failing check sends its output back to the model for up to 2 repair turns.</td></tr>
|
|
19
19
|
<tr><td><b>Status from evidence</b></td><td><code>completed</code> requires passing checks and no edits outside <code>--owns</code>. The model's own report is kept as a claim.</td></tr>
|
|
20
20
|
<tr><td><b>Sessions</b></td><td>Stable ids, <code>--detach</code>, <code>status</code>, <code>wait</code>, <code>resume</code>, <code>cancel</code>. A session whose process died shows as interrupted and can be resumed.</td></tr>
|
|
21
|
-
<tr><td><b>Parallel work</b></td><td>Two sessions with overlapping <code>--owns</code> in one repo are refused with exit 7.</td></tr>
|
|
22
|
-
<tr><td><b>Output</b></td><td>
|
|
21
|
+
<tr><td><b>Parallel work</b></td><td>Two sessions with overlapping <code>--owns</code> in one repo are refused with exit 7. Every session gets its own port, output dir and tmp dir (<code>ZCODE_PORT</code>, <code>ZCODE_OUT</code>, <code>ZCODE_TMP</code>).</td></tr>
|
|
22
|
+
<tr><td><b>Output</b></td><td>With <code>--json</code>, one compact <code>zcode.brief/1</code> result line (<code>--full</code> for the whole <code>zcode.result/1</code> record). Timeouts and usage errors are one line. No colour codes when stdout is not a terminal.</td></tr>
|
|
23
23
|
</table>
|
|
24
24
|
|
|
25
25
|
|
|
@@ -56,13 +56,13 @@ Every command runs on GLM-5.3-Flash at effort `max` unless you pass a flag.
|
|
|
56
56
|
| setting | flag | values | default |
|
|
57
57
|
|---|---|---|---|
|
|
58
58
|
| model | `--model` | `glm-5.3-flash` (aliases `flash`, `5.3-flash`, `53f`), `glm-5.3` (aliases `5.3`, `53`) | `glm-5.3-flash` |
|
|
59
|
-
| effort | `--effort` | `low`, `
|
|
59
|
+
| effort | `--effort` | `low`, `high`, `max` (the engine's levels for both models; `zcode doctor` lists them) | `max` |
|
|
60
60
|
|
|
61
61
|
```sh
|
|
62
62
|
zcode task "..." # GLM-5.3-Flash, effort max
|
|
63
63
|
zcode task --effort high "..." # Flash with less reasoning
|
|
64
64
|
zcode task --model glm-5.3 "..." # GLM-5.3, effort max
|
|
65
|
-
zcode task --model glm-5.3 --effort
|
|
65
|
+
zcode task --model glm-5.3 --effort high "..." # GLM-5.3, effort high
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
A task contract can set `"model"` and `"effort"`. Flags override the contract. A resumed session keeps its original model and effort unless you pass the flags again.
|
|
@@ -96,7 +96,7 @@ not verified
|
|
|
96
96
|
- prose acceptance criteria are confirmed only by the executor's own report
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
That block is the format of the summary printed to stdout. The live feed (session header, phases, tools, subagents, check results) goes to stderr. With `--json` you get the
|
|
99
|
+
That block is the format of the summary printed to stdout. The live feed (session header, phases, tools, subagents, check results) goes to stderr. With `--json` you get the compact brief as one machine-readable result line (see Machine interface).
|
|
100
100
|
|
|
101
101
|
For small work, the objective alone is enough: `zcode task "Fix the typo in the README heading"`.
|
|
102
102
|
|
|
@@ -109,7 +109,7 @@ Flags cover the common fields. For richer handoffs, pass `--task contract.json`,
|
|
|
109
109
|
"objective": "Implement page 2 (Team) per the design spec",
|
|
110
110
|
"scope": ["Team page route and its components"],
|
|
111
111
|
"constraints": ["No new dependencies", "Keep existing routing untouched"],
|
|
112
|
-
"
|
|
112
|
+
"decisions": ["Grid of 3 columns ≥1024px, 1 column below", "Use tokens from src/theme.ts only"],
|
|
113
113
|
"acceptance": ["Every member card shows name, role, avatar"],
|
|
114
114
|
"checks": ["npm run build", "npm test -- team"],
|
|
115
115
|
"files": ["src/theme.ts", "src/pages/home/index.tsx"],
|
|
@@ -122,7 +122,15 @@ Flags cover the common fields. For richer handoffs, pass `--task contract.json`,
|
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
`design`
|
|
125
|
+
`decisions` (flags `--decision "…"`, repeatable, and `--decisions @file`; `design` is an alias) are presented to the model as binding decisions it must implement, not revisit. `@file` entries inside contract `decisions`/`context` lists are inlined (relative to the contract file). If something user-visible or behavioural is left open, the executor does not guess: when it blocks the work it reports `blocked` with a structured question `{"q", "options": [...], "recommend"}` (plain strings still work); when it does not block, it takes the most conservative choice and lists it in the handback `decisions: [{what, why}]`, which the brief carries so the orchestrator can overrule it.
|
|
126
|
+
|
|
127
|
+
A file passed with `--file` and also inlined with `--context @file` reaches the executor once (as context); identical context and acceptance entries are deduped.
|
|
128
|
+
|
|
129
|
+
**Preflight.** `--preflight` runs a read-only turn over the same contract: the executor lists every open question it would otherwise guess and makes no edits; zcode runs no checks. The result is `blocked` with `questions`, or `completed` with summary `no open questions`; any changed file fails it (`preflight_wrote`). The session is recorded as a preflight; `zcode resume <id> "<answers>"` then switches it to execution: the answers plus the full task protocol go to the executor, and checks and visual verification run as usual.
|
|
130
|
+
|
|
131
|
+
**Units.** `--unit "name: objective [owns a,b]"` (repeatable; contract `units: [{name, objective, owns?}]`) makes the executor dispatch each unit to its own subagent inside this one session, in parallel where independent, then integrate and verify the whole. Unit `owns` must lie within the session's `--owns` and be disjoint from each other (exit 2 otherwise). The handback `units: [{name, status, note}]` is listed under `notVerified` as executor claims; zcode's checks still verify the integrated result.
|
|
132
|
+
|
|
133
|
+
**Visual verification.** `--visual "criterion"` (repeatable; contract `visual`), `--viewport WxH` (repeatable, default `390x844` and `1440x900`; contract `viewports`) and `--serve "cmd"` (contract `serve`; must honour `$ZCODE_PORT`, default: the executor reads `package.json`) have the executor serve the app on `$ZCODE_PORT`, capture Playwright screenshots into `$ZCODE_OUT/shots` at each viewport, look at them, fix and re-capture, and report `visual: [{criterion, viewport, met, note, shot}]`. zcode runs no pixel check: results are executor judgement (`notVerified`). Unmet criteria spend the repair budget like failing checks (one combined repair turn when both occur); still unmet afterwards, the result is `partial` with the issue `visual criteria unmet: …`. Without `--visual` nothing changes.
|
|
126
134
|
|
|
127
135
|
### Status: what a result means
|
|
128
136
|
|
|
@@ -149,6 +157,8 @@ zcode task --detach --id page-2 --owns src/pages/team/ --check "npm run build" "
|
|
|
149
157
|
zcode status # all sessions: id, state/status, kind, updated, label
|
|
150
158
|
zcode status page-1 --json # one session: state, phase, activity, recent events, result
|
|
151
159
|
zcode wait page-1 page-2 # block until both finish; print each handback
|
|
160
|
+
zcode wait page-1 page-2 --any # return as soon as the first one finishes
|
|
161
|
+
zcode result page-1 # a finished session's handback, without waiting
|
|
152
162
|
zcode resume page-2 "Use the 2-column variant below 1024px." # answer / follow up / retry
|
|
153
163
|
zcode cancel page-1 # stop the turn and any running check; recorded as cancelled
|
|
154
164
|
```
|
|
@@ -166,6 +176,34 @@ zcode cancel page-1 # stop the turn and any running check; recorded as
|
|
|
166
176
|
- After the work, zcode diffs the workspace and reports each changed file. Changes outside the session's ownership are listed in `scopeViolations`, and for task sessions they make the result `partial`. Files that fall in a concurrent session's ownership are attributed to that session, not counted against this one.
|
|
167
177
|
- For overlapping or risky parallel work, give each session its own git worktree via `--cwd`.
|
|
168
178
|
- `ZAI_CODE_MAX_ACTIVE` (default 8) is a soft machine-wide cap. Sessions beyond it wait in `queued`.
|
|
179
|
+
- **Session environment.** Every session's executor and every check run with `ZCODE_SESSION_ID`, `ZCODE_PORT` (a free TCP port from 41000-48999, reserved per session and unique among live sessions, also recorded in the session), `ZCODE_OUT` (`<cwd>/.zcode/out/<id>`, created) and `ZCODE_TMP` (a per-session scratch dir). The task prompt tells the executor to use them for dev and preview servers, build output dirs and test artifacts, so parallel sessions cannot collide on a port or a `dist/`. Files under `.zcode/` never count as changes or scope violations; add `.zcode/` to the `.gitignore` of repos you run zcode in.
|
|
180
|
+
- **`--touch <path|glob>`** (repeatable; contract key `touch`) allows minimal edits outside `--owns` without declaring ownership. Touched files are listed in `touched` and are not scope violations, so one stray line in a shared stylesheet no longer blocks a session. Starting is refused with exit 7 if a running session owns a matching path; a touch reserves nothing, so it never blocks another session's `--owns`.
|
|
181
|
+
|
|
182
|
+
### Plans: several sessions with dependencies
|
|
183
|
+
|
|
184
|
+
`zcode plan` runs a set of task sessions as one unit, in dependency order, and returns a single summary.
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{ "decisions": ["@decisions.md"], "context": ["@brief.md"], "effort": "high",
|
|
188
|
+
"sessions": [
|
|
189
|
+
{ "id": "tokens", "objective": "Design tokens", "owns": ["src/tokens/**"], "checks": ["npm run lint"] },
|
|
190
|
+
{ "id": "home", "objective": "Home page", "after": ["tokens"], "owns": ["src/home/**"] },
|
|
191
|
+
{ "id": "team", "objective": "Team page", "after": ["tokens"], "owns": ["src/team/**"] }
|
|
192
|
+
] }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Optional top-level keys: `cwd` (relative to the plan file), `decisions`, `context`, `files` (prepended to every session; `@file` entries are read relative to the plan file), `model`, `effort`. Each session has an `id` (its zcode session id), optional `after` (ids) and any task contract keys.
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
zcode plan check plan.json # validate: exit 2, one line per problem
|
|
199
|
+
zcode plan run plan.json --preflight # read-only questions per session (<id>.pre); exit 10 if any
|
|
200
|
+
zcode plan run plan.json --detach # supervise in the background
|
|
201
|
+
zcode plan wait plan --json --timeout-ms 600000 # one line; exit 124 on timeout
|
|
202
|
+
zcode plan status plan # one line per session
|
|
203
|
+
zcode plan resume plan "answers…" # resume failed/partial/blocked sessions, relaunch skipped ones
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
A session starts once everything it is `after` has completed; sessions that can run together must own disjoint paths (`plan check` enforces it). A session that ends without completing marks its downstream `skipped` while the others continue. The summary is `{"type":"plan","name","status","sessions":[{"id","status"}],"briefs":[…]}` with a brief for each finished session that did not complete. Exit codes: 0 completed, 10 blocked, 8 partial or failed, 124 timeout. Plan status is derived from the sessions, so it stays accurate even if the supervisor process dies; `plan resume` restarts it. The plan name is the file's basename (`--id` overrides); ids that already exist as sessions are refused, so re-run through `plan resume`.
|
|
169
207
|
|
|
170
208
|
|
|
171
209
|
|
|
@@ -189,6 +227,18 @@ Inside `zcode task`, subagents are optional. The executor decides when to use th
|
|
|
189
227
|
|
|
190
228
|
`--json`: stdout carries NDJSON events, then exactly one final `{"type":"result",…}` line. With `--quiet`, the result line is the only output. For task sessions, text/reasoning deltas are left out of the events unless you pass `--stream`, so an orchestrator reading the output pays only for lifecycle events.
|
|
191
229
|
|
|
230
|
+
The final line is the compact **brief** (`zcode.brief/1`), sized for an orchestrator that spends tokens on decisions. Keys with empty values are omitted:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{"type":"result","schema":"zcode.brief/1","status":"failed","ok":false,"id":"page-1","label":"home",
|
|
234
|
+
"summary":"…(≤600 chars)…","files":["src/pages/home/index.tsx"],
|
|
235
|
+
"checks":[{"command":"npm run lint","ok":true},{"command":"npm test","ok":false,"exitCode":1,"tail":"…last 1500 chars…"}],
|
|
236
|
+
"questions":[],"issues":["…"],"unresolved":[],"scopeViolations":[],"error":{"kind":"checks_failed"},
|
|
237
|
+
"result":"/…/sessions/page-1/result.json"}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`run` and `agent` briefs also carry `response`, because the text answer is the product there. Passing checks are reduced to `{command, ok:true}`; a failing check carries `exitCode`, `timedOut` and the last 1500 characters of its output as `tail`. `result` is the path to the full record: `result.json` on disk is always the full `zcode.result/1` record, and `--full` prints that same record instead of the brief (on `task`/`run`/`agent`/`resume`/`wait`/`result`):
|
|
241
|
+
|
|
192
242
|
```json
|
|
193
243
|
{"type":"result","schema":"zcode.result/1","ok":true,"status":"completed","id":"page-1","kind":"task",
|
|
194
244
|
"summary":"…","response":"…executor report…","issues":[],"questions":[],"unresolved":[],"notVerified":[],
|
|
@@ -201,7 +251,7 @@ Inside `zcode task`, subagents are optional. The executor decides when to use th
|
|
|
201
251
|
"toolCalls":14,"durationMs":142301,"files":{"session":"…/sessions/page-1","result":"…/result.json","events":"…/events.ndjson"}}
|
|
202
252
|
```
|
|
203
253
|
|
|
204
|
-
`model` and `effort` are engine-reported (from the engine's `model_request_started` event), not echoes. `sessionId` is the engine's session id; `id` is the zcode session id you pass to `status`, `wait`, `resume`, and `cancel`. A run that fails still ends with a parseable result line.
|
|
254
|
+
`model` and `effort` are engine-reported (from the engine's `model_request_started` event), not echoes. `sessionId` is the engine's session id; `id` is the zcode session id you pass to `status`, `wait`, `result`, `resume`, and `cancel`. A run that fails still ends with a parseable result line.
|
|
205
255
|
|
|
206
256
|
| event | meaning |
|
|
207
257
|
|---|---|
|
|
@@ -213,7 +263,7 @@ Inside `zcode task`, subagents are optional. The executor decides when to use th
|
|
|
213
263
|
| `{"type":"mcp","name","status"}` · `{"type":"retry",…}` · `{"type":"model_request_started",…}` | MCP settling, start-up retry, engine model evidence |
|
|
214
264
|
| `{"type":"stream","kind":"text_delta\|reasoning_delta","delta"}` | live text (run/agent; task with `--stream`) |
|
|
215
265
|
|
|
216
|
-
`zcode status <id> --json` returns `{"type":"status", state, status, phase, pid, live, activity, recent:[…], result?}`. `zcode wait --json` prints one
|
|
266
|
+
`zcode status <id> --json` returns `{"type":"status", state, status, phase, pid, live, activity, recent:[…], result?}`. `zcode wait --json` prints one brief per session as each finishes (`--full`: full records; `--any`: stop at the first). When `--timeout-ms` elapses first it prints exactly one line per session still running, `{"type":"pending","id","state","phase","elapsedMs"}` (human mode: `<id> <state> <phase> 12m`), and exits 124. `zcode result <id> [--json] [--full]` prints a finished session's handback without waiting (exit 7 if it is still running). `zcode status` without an id is one line per session. Usage errors are always a single line (`zcode: unknown flag (did you mean …?)` for a mistyped option), never the help text.
|
|
217
267
|
|
|
218
268
|
|
|
219
269
|
|
|
@@ -225,24 +275,30 @@ Inside `zcode task`, subagents are optional. The executor decides when to use th
|
|
|
225
275
|
| `--detach` | run, task, agent, resume | off | background worker; prints the id |
|
|
226
276
|
| `--cwd` | run, task, agent | current dir | the workspace and blast radius |
|
|
227
277
|
| `--owns` | run, task, agent | whole workspace | repeatable path or glob |
|
|
278
|
+
| `--touch` | task | | repeatable; minimal edits outside `--owns`, reported as `touched` |
|
|
228
279
|
| `--allow-overlap` | run, task, agent | off | start despite overlapping ownership |
|
|
229
280
|
| `--task` | task | | JSON contract, markdown brief, or `-` |
|
|
230
|
-
| `--accept`, `--check`, `--file`, `--context` | task | | repeatable; `--context @file` reads a file |
|
|
281
|
+
| `--accept`, `--check`, `--file`, `--context` | task | | repeatable; `--context @file` reads a file (a `--file` also inlined this way is listed once) |
|
|
282
|
+
| `--decision`, `--decisions` | task | | repeatable binding decisions; `--decisions @file` reads a file |
|
|
283
|
+
| `--preflight` | task | off | read-only turn that returns the open questions; `resume <id> "<answers>"` then executes |
|
|
284
|
+
| `--unit` | task | | repeatable `"name: objective [owns a,b]"`; one subagent per unit |
|
|
285
|
+
| `--visual`, `--viewport`, `--serve` | task | viewports `390x844`, `1440x900` | executor screenshots the app with Playwright and judges each criterion |
|
|
231
286
|
| `--max-repairs` | task, resume | `2` | repair turns after failing checks |
|
|
232
287
|
| `--check-timeout-ms` | task, resume | `300000` | per check; checks get `CI=1` when unset |
|
|
233
288
|
| `--model` | all turns | `glm-5.3-flash` | `glm-5.3` plus aliases `5.3`, `53`, `flash`, `5.3-flash`, `53f` |
|
|
234
|
-
| `--effort` | all turns | `max` | `low`, `
|
|
289
|
+
| `--effort` | all turns | `max` | the model's levels: `low`, `high`, `max` |
|
|
235
290
|
| `--timeout-ms` | all turns; wait | run 600000, task 1800000 | the whole invocation incl. checks and repairs |
|
|
236
291
|
| `--session` | run | | 0.3 compat: continue by zcode id or engine `sess_` id |
|
|
237
292
|
| `--profile` | agent | `general-purpose` | subagent profile |
|
|
238
293
|
| `--mcp` / `--mcp-inherit` / `--mcp-list` | run, task, agent | hermetic, plus the desktop's `playwright` | MCP servers (inline JSON or `@file`) |
|
|
239
294
|
| `--no-browser` | run, task, agent | off | Drop the desktop's `playwright` MCP server |
|
|
240
295
|
| `--skills` / `--no-skills` | run, task, agent | desktop skills | add skills; or run with none |
|
|
241
|
-
| `--json`, `--quiet` | all | | see Machine interface |
|
|
296
|
+
| `--json`, `--full`, `--quiet` | all | | see Machine interface |
|
|
297
|
+
| `--any` | wait | off | return when the first listed session finishes |
|
|
242
298
|
| `--thinking` / `--no-thinking` | turns | run on, task off | mirror reasoning to stderr |
|
|
243
299
|
| `--stream`, `--include-reasoning` | turns | off | deltas in task events; full reasoning in the result |
|
|
244
300
|
|
|
245
|
-
Only `glm-5.3` and `glm-5.3-flash` are accepted; anything else exits 2 before any spawn.
|
|
301
|
+
Only `glm-5.3` and `glm-5.3-flash` are accepted; anything else exits 2 before any spawn. The effort levels each model accepts are read from the engine's builtin provider config (`zcode doctor --json` lists them as `models`; a built-in table is the fallback). An unsupported model and effort pair, whether from `--effort`, the task contract, or a resumed session's stored setting, exits 2 with one line naming the supported values, before any engine is spawned.
|
|
246
302
|
|
|
247
303
|
## Exit codes
|
|
248
304
|
|
package/bin/zcode.js
CHANGED
|
@@ -10,8 +10,16 @@ const USAGE = `usage:
|
|
|
10
10
|
zcode agent [options] <instructions…> turn that MUST dispatch a subagent
|
|
11
11
|
zcode resume <id> [message…] continue a session (answer, follow-up, retry)
|
|
12
12
|
zcode status [<id>] [--json] one session in detail, or all sessions
|
|
13
|
-
zcode wait <id…> [--json]
|
|
13
|
+
zcode wait <id…> [--any] [--json] block until sessions finish; print handbacks
|
|
14
|
+
zcode result <id> [--full] [--json] print a finished session's handback (brief by default)
|
|
14
15
|
zcode cancel <id> [--json] stop a running session
|
|
16
|
+
zcode plan check <file> validate a plan file (ids, after, cycles, owns, effort)
|
|
17
|
+
zcode plan run <file> [--preflight] [--detach] [--timeout-ms n] [--id name] [--json]
|
|
18
|
+
run a DAG of task sessions (see "plan" below)
|
|
19
|
+
zcode plan wait|status <name> [--timeout-ms n] [--json]
|
|
20
|
+
one summary line: {type:"plan",name,status,sessions,briefs}
|
|
21
|
+
zcode plan resume <name> [message…] [--session <id>]
|
|
22
|
+
resume failed/partial/blocked sessions, restart supervision
|
|
15
23
|
zcode login read API key from stdin → credentials
|
|
16
24
|
zcode doctor [--json] engine + isolation + key report
|
|
17
25
|
zcode skills [install|uninstall] [claude|codex]
|
|
@@ -32,14 +40,47 @@ task contract (task only):
|
|
|
32
40
|
--task <file|-> JSON contract or markdown brief (- = stdin)
|
|
33
41
|
--accept <text> repeatable acceptance criterion
|
|
34
42
|
--check <command> repeatable shell check zcode runs after the work
|
|
35
|
-
--
|
|
36
|
-
|
|
43
|
+
--touch <path|glob> repeatable: minimal edits allowed outside --owns; reported
|
|
44
|
+
as "touched", not a scope violation. Refused (exit 7) if a
|
|
45
|
+
running session owns a matching path; reserves nothing
|
|
46
|
+
--file <path> repeatable relevant file to read first (a file also given
|
|
47
|
+
as --context @file is inlined once, not listed twice)
|
|
48
|
+
--context <text|@f> repeatable extra context (references, background)
|
|
49
|
+
--decision <text> repeatable binding decision the orchestrator made (the
|
|
50
|
+
executor implements it exactly and must not guess others)
|
|
51
|
+
--decisions <@f|text> binding decisions from a file (same list as --decision)
|
|
52
|
+
--preflight read-only turn: the executor lists the open questions it
|
|
53
|
+
would otherwise guess (blocked + questions, or completed
|
|
54
|
+
"no open questions"); no edits, no checks. Then
|
|
55
|
+
zcode resume <id> "<answers>" executes the task
|
|
56
|
+
--unit "n: goal [owns a,b]" repeatable: one subagent per unit inside this
|
|
57
|
+
session; owns must lie within --owns and be disjoint
|
|
58
|
+
--visual <criterion> repeatable: the executor serves the app on $ZCODE_PORT,
|
|
59
|
+
screenshots it with Playwright and judges each criterion
|
|
60
|
+
--viewport <WxH> repeatable viewport for --visual (default 390x844, 1440x900)
|
|
61
|
+
--serve <cmd> command that serves the app for --visual (honours $ZCODE_PORT)
|
|
37
62
|
--max-repairs <n> repair turns after failing checks (default 2)
|
|
38
63
|
--check-timeout-ms <n> per-check timeout (default 300000)
|
|
39
64
|
|
|
65
|
+
plan (zcode plan run <plan.json>):
|
|
66
|
+
plan.json {cwd?, decisions?, context?, files?, model?, effort?,
|
|
67
|
+
sessions:[{id, after?:[ids], ...task contract keys}]}; the
|
|
68
|
+
top-level decisions/context/files are prepended to every
|
|
69
|
+
session. A session starts when everything it is "after" has
|
|
70
|
+
completed; sessions that can run together must own disjoint
|
|
71
|
+
paths. A session ending non-completed marks its downstream
|
|
72
|
+
"skipped"; the others continue
|
|
73
|
+
--preflight plan run: read-only preflight of every session ("<id>.pre"),
|
|
74
|
+
prints the questions as one brief per session (exit 10 if any)
|
|
75
|
+
--detach plan run/resume: supervise in a background process
|
|
76
|
+
--id <name> plan name (default: the file's basename)
|
|
77
|
+
--session <id> plan resume: only this session
|
|
78
|
+
|
|
40
79
|
execution:
|
|
41
80
|
--model <id> glm-5.3 | glm-5.3-flash (+aliases). default: glm-5.3-flash
|
|
42
|
-
--effort <level> low |
|
|
81
|
+
--effort <level> low | high | max for both models (zcode doctor lists the
|
|
82
|
+
engine's levels per model); an unsupported level exits 2
|
|
83
|
+
before any spawn. default: max
|
|
43
84
|
--timeout-ms <n> whole invocation incl. checks/repairs (default 600000;
|
|
44
85
|
task default 1800000)
|
|
45
86
|
--profile <name> subagent profile (agent command; default general-purpose)
|
|
@@ -51,8 +92,13 @@ execution:
|
|
|
51
92
|
--no-skills empty skill set
|
|
52
93
|
|
|
53
94
|
output:
|
|
54
|
-
--json NDJSON events on stdout; final line
|
|
55
|
-
|
|
95
|
+
--json NDJSON events on stdout; final line is the compact brief
|
|
96
|
+
{"type":"result","schema":"zcode.brief/1",…}
|
|
97
|
+
--full with --json (and result/wait): the full record instead
|
|
98
|
+
(schema zcode.result/1; result.json on disk is always full)
|
|
99
|
+
--any wait: return as soon as the FIRST id finishes
|
|
100
|
+
--quiet no progress: no stderr feed; with --json only the result line;
|
|
101
|
+
usage errors are always a single line
|
|
56
102
|
--thinking / --no-thinking mirror reasoning to stderr (run: on, task: off)
|
|
57
103
|
--stream task --json: include text/reasoning deltas in the events
|
|
58
104
|
--include-reasoning full reasoning text in the result
|
|
@@ -97,9 +143,10 @@ function parseMcpSpec(value) {
|
|
|
97
143
|
return parsed;
|
|
98
144
|
}
|
|
99
145
|
|
|
100
|
-
const COMMANDS = new Set(['run', 'task', 'agent', 'resume', 'status', 'list', 'wait', 'cancel', 'login', 'doctor', 'skills', '__exec']);
|
|
101
|
-
const TASK_ONLY = new Set(['task', 'accept', 'check', 'file', 'context', 'maxRepairs', 'checkTimeoutMs']);
|
|
102
|
-
const
|
|
146
|
+
const COMMANDS = new Set(['run', 'task', 'agent', 'resume', 'status', 'list', 'wait', 'cancel', 'login', 'doctor', 'skills', 'result', 'plan', '__plan', '__exec']);
|
|
147
|
+
const TASK_ONLY = new Set(['task', 'accept', 'check', 'file', 'context', 'touch', 'maxRepairs', 'checkTimeoutMs', 'decisions', 'units', 'visual', 'viewports', 'serve', 'preflight']);
|
|
148
|
+
const FULL_COMMANDS = new Set(['run', 'task', 'agent', 'resume', 'wait', 'result', '__exec']);
|
|
149
|
+
const START_ONLY = new Set(['id', 'label', 'owns', 'touch', 'preflight', 'allowOverlap', 'mcpSpecs', 'mcpInherit', 'noBrowser', 'skillsDirs', 'noSkills', 'profile', 'cwd']);
|
|
103
150
|
|
|
104
151
|
function parseArgv(argv) {
|
|
105
152
|
const out = {
|
|
@@ -125,6 +172,13 @@ function parseArgv(argv) {
|
|
|
125
172
|
file: [],
|
|
126
173
|
context: [],
|
|
127
174
|
owns: [],
|
|
175
|
+
touch: [],
|
|
176
|
+
decisions: [],
|
|
177
|
+
units: [],
|
|
178
|
+
visual: [],
|
|
179
|
+
viewports: [],
|
|
180
|
+
serve: undefined,
|
|
181
|
+
preflight: false,
|
|
128
182
|
allowOverlap: false,
|
|
129
183
|
maxRepairs: undefined,
|
|
130
184
|
checkTimeoutMs: undefined,
|
|
@@ -138,17 +192,21 @@ function parseArgv(argv) {
|
|
|
138
192
|
quiet: false,
|
|
139
193
|
thinking: undefined,
|
|
140
194
|
stream: false,
|
|
195
|
+
full: false,
|
|
196
|
+
any: false,
|
|
141
197
|
},
|
|
142
198
|
};
|
|
143
199
|
const valueFlags = new Map([
|
|
144
200
|
['--model', 'model'], ['--effort', 'effort'], ['--cwd', 'cwd'], ['--profile', 'profile'],
|
|
145
201
|
['--timeout-ms', 'timeoutMs'], ['-p', 'prompt'], ['--prompt', 'prompt'], ['--session', 'session'],
|
|
146
202
|
['--id', 'id'], ['--label', 'label'], ['--task', 'task'], ['--max-repairs', 'maxRepairs'],
|
|
147
|
-
['--check-timeout-ms', 'checkTimeoutMs'],
|
|
203
|
+
['--check-timeout-ms', 'checkTimeoutMs'], ['--serve', 'serve'],
|
|
148
204
|
]);
|
|
149
205
|
const listFlags = new Map([
|
|
150
206
|
['--mcp', 'mcpSpecs'], ['--skills', 'skillsDirs'], ['--accept', 'accept'], ['--check', 'check'],
|
|
151
|
-
['--file', 'file'], ['--context', 'context'], ['--owns', 'owns'],
|
|
207
|
+
['--file', 'file'], ['--context', 'context'], ['--owns', 'owns'], ['--touch', 'touch'],
|
|
208
|
+
['--decision', 'decisions'], ['--decisions', 'decisions'], ['--unit', 'units'], ['--visual', 'visual'],
|
|
209
|
+
['--viewport', 'viewports'],
|
|
152
210
|
]);
|
|
153
211
|
const boolFlags = new Map([
|
|
154
212
|
['--json', ['json', true]], ['--fresh', ['fresh', true]], ['--mcp-inherit', ['mcpInherit', true]],
|
|
@@ -156,6 +214,7 @@ function parseArgv(argv) {
|
|
|
156
214
|
['--include-reasoning', ['includeReasoning', true]], ['--quiet', ['quiet', true]],
|
|
157
215
|
['--detach', ['detach', true]], ['--allow-overlap', ['allowOverlap', true]],
|
|
158
216
|
['--thinking', ['thinking', true]], ['--no-thinking', ['thinking', false]], ['--stream', ['stream', true]],
|
|
217
|
+
['--full', ['full', true]], ['--any', ['any', true]], ['--preflight', ['preflight', true]],
|
|
159
218
|
]);
|
|
160
219
|
const numeric = (flag, value, { min = 1 } = {}) => {
|
|
161
220
|
const n = Number(value);
|
|
@@ -168,7 +227,7 @@ function parseArgv(argv) {
|
|
|
168
227
|
if (valueFlags.has(arg) || listFlags.has(arg)) {
|
|
169
228
|
const key = valueFlags.get(arg) || listFlags.get(arg);
|
|
170
229
|
const value = argv[++i];
|
|
171
|
-
if (value === undefined) fail(2, `option ${arg} requires a value
|
|
230
|
+
if (value === undefined) fail(2, `option ${arg} requires a value.`);
|
|
172
231
|
out.given.add(key);
|
|
173
232
|
if (key === 'timeoutMs' || key === 'checkTimeoutMs') out.flags[key] = numeric(arg, value);
|
|
174
233
|
else if (key === 'maxRepairs') out.flags[key] = numeric(arg, value, { min: 0 });
|
|
@@ -176,8 +235,6 @@ function parseArgv(argv) {
|
|
|
176
235
|
else if (key === 'skillsDirs') {
|
|
177
236
|
if (!isDirectory(value)) fail(2, `--skills requires an existing directory, got "${value}".`);
|
|
178
237
|
out.flags.skillsDirs.push(value);
|
|
179
|
-
} else if (key === 'context') {
|
|
180
|
-
out.flags.context.push(value.startsWith('@') ? readFileOrFail(value.slice(1), '--context') : value);
|
|
181
238
|
} else if (listFlags.has(arg)) out.flags[key].push(value);
|
|
182
239
|
else if (!value.trim()) fail(2, `${arg} requires a non-empty value.`);
|
|
183
240
|
else out.flags[key] = value.trim();
|
|
@@ -201,7 +258,10 @@ function parseArgv(argv) {
|
|
|
201
258
|
out.prompts.push(...argv.slice(i + 1));
|
|
202
259
|
break;
|
|
203
260
|
}
|
|
204
|
-
if (arg.startsWith('--') && arg.length > 2)
|
|
261
|
+
if (arg.startsWith('--') && arg.length > 2) {
|
|
262
|
+
const hint = suggestFlag(arg, [...valueFlags.keys(), ...listFlags.keys(), ...boolFlags.keys()]);
|
|
263
|
+
fail(2, `unknown flag ${arg} (${hint ? `did you mean ${hint}?` : 'see zcode --help'})`);
|
|
264
|
+
}
|
|
205
265
|
if (out.prompts.length === 0 && out.command === 'run' && !out.commandSet && COMMANDS.has(arg)) {
|
|
206
266
|
out.command = arg === 'list' ? 'status' : arg;
|
|
207
267
|
out.commandSet = true;
|
|
@@ -214,17 +274,25 @@ function parseArgv(argv) {
|
|
|
214
274
|
if (out.flags.prompt !== undefined) out.prompts.unshift(out.flags.prompt);
|
|
215
275
|
if (cmd !== 'task') {
|
|
216
276
|
for (const key of TASK_ONLY) {
|
|
277
|
+
if (cmd === 'plan' && key === 'preflight') continue;
|
|
217
278
|
if (out.given.has(key) && !(cmd === 'resume' && (key === 'maxRepairs' || key === 'checkTimeoutMs'))) {
|
|
218
279
|
fail(2, `--${flagName(key)} is only valid with "zcode task".`);
|
|
219
280
|
}
|
|
220
281
|
}
|
|
221
282
|
}
|
|
283
|
+
if (cmd === 'plan' || cmd === '__plan') {
|
|
284
|
+
for (const key of ['label', 'owns', 'touch', 'allowOverlap', 'model', 'effort', 'profile']) {
|
|
285
|
+
if (out.given.has(key)) fail(2, `--${flagName(key)} is not valid with "zcode plan"; set it in the plan file.`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
222
288
|
if (cmd === 'resume' || cmd === '__exec') {
|
|
223
289
|
for (const key of START_ONLY) {
|
|
224
290
|
if (out.given.has(key)) fail(2, `--${flagName(key)} cannot change on resume; it is fixed when the session starts.`);
|
|
225
291
|
}
|
|
226
292
|
}
|
|
227
|
-
if (out.given.has('
|
|
293
|
+
if (out.given.has('any') && cmd !== 'wait') fail(2, '--any is only valid with "zcode wait".');
|
|
294
|
+
if (out.given.has('full') && !FULL_COMMANDS.has(cmd)) fail(2, `--full is not valid with "zcode ${cmd}".`);
|
|
295
|
+
if (out.given.has('session') && cmd !== 'run' && cmd !== 'plan') fail(2, '--session is only valid with "zcode run"; use "zcode resume <id>".');
|
|
228
296
|
if (out.given.has('id') && !require('../lib/store.js').validId(out.flags.id)) {
|
|
229
297
|
fail(2, `--id "${out.flags.id}" must be 1-64 chars of letters, digits, ".", "_" or "-", starting with a letter or digit.`);
|
|
230
298
|
}
|
|
@@ -239,8 +307,34 @@ function parseArgv(argv) {
|
|
|
239
307
|
return out;
|
|
240
308
|
}
|
|
241
309
|
|
|
310
|
+
// Closest known flag by edit distance (<= 2), or null.
|
|
311
|
+
function suggestFlag(arg, known) {
|
|
312
|
+
const dist = (a, b) => {
|
|
313
|
+
const row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
314
|
+
for (let i = 1; i <= a.length; i++) {
|
|
315
|
+
let prev = row[0];
|
|
316
|
+
row[0] = i;
|
|
317
|
+
for (let j = 1; j <= b.length; j++) {
|
|
318
|
+
const tmp = row[j];
|
|
319
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
320
|
+
prev = tmp;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return row[b.length];
|
|
324
|
+
};
|
|
325
|
+
let best = null;
|
|
326
|
+
let bestD = Infinity;
|
|
327
|
+
for (const k of known) {
|
|
328
|
+
if (!k.startsWith('--')) continue;
|
|
329
|
+
const d = dist(arg, k);
|
|
330
|
+
if (d < bestD) { best = k; bestD = d; }
|
|
331
|
+
}
|
|
332
|
+
return best && bestD <= 2 ? best : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
242
335
|
function flagName(key) {
|
|
243
|
-
return key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^mcp-specs$/, 'mcp').replace(/^skills-dirs$/, 'skills')
|
|
336
|
+
return key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^mcp-specs$/, 'mcp').replace(/^skills-dirs$/, 'skills')
|
|
337
|
+
.replace(/^(unit|viewport)s$/, '$1');
|
|
244
338
|
}
|
|
245
339
|
|
|
246
340
|
function isDirectory(p) {
|
|
@@ -261,8 +355,12 @@ async function main() {
|
|
|
261
355
|
case 'resume':
|
|
262
356
|
case '__exec':
|
|
263
357
|
return require('../lib/commands/exec.js')(parsed);
|
|
358
|
+
case 'plan':
|
|
359
|
+
case '__plan':
|
|
360
|
+
return require('../lib/commands/plan.js')(parsed);
|
|
264
361
|
case 'status':
|
|
265
362
|
case 'wait':
|
|
363
|
+
case 'result':
|
|
266
364
|
case 'cancel':
|
|
267
365
|
return require('../lib/commands/sessions.js')(parsed);
|
|
268
366
|
case 'login':
|
|
@@ -272,10 +370,13 @@ async function main() {
|
|
|
272
370
|
case 'skills':
|
|
273
371
|
return require('../lib/commands/skills.js')(parsed);
|
|
274
372
|
default:
|
|
275
|
-
return fail(2, `unknown command "${parsed.command}"
|
|
373
|
+
return fail(2, `unknown command "${parsed.command}" (see zcode --help).`);
|
|
276
374
|
}
|
|
277
375
|
}
|
|
278
376
|
|
|
377
|
+
// Exported before main() runs: the plan supervisor reuses parseArgv.
|
|
378
|
+
module.exports = { parseArgv, USAGE };
|
|
379
|
+
|
|
279
380
|
if (require.main === module) {
|
|
280
381
|
main().catch((err) => {
|
|
281
382
|
process.stderr.write(`zcode: internal error: ${err && err.stack ? err.stack : err}\n`);
|
|
@@ -283,4 +384,3 @@ if (require.main === module) {
|
|
|
283
384
|
});
|
|
284
385
|
}
|
|
285
386
|
|
|
286
|
-
module.exports = { parseArgv, USAGE };
|
package/lib/bootstrap.js
CHANGED
|
@@ -55,11 +55,11 @@ function resolveRuntime(env = process.env) {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function makeClient(runtime, { home, cwd }) {
|
|
58
|
+
function makeClient(runtime, { home, cwd, sessionEnv = {} }) {
|
|
59
59
|
return new AppServerClient({
|
|
60
60
|
nodeBin: runtime.nodeBin,
|
|
61
61
|
enginePath: runtime.enginePath,
|
|
62
|
-
env: engineEnv({ home, builtinProviderConfig: runtime.builtinConfig }),
|
|
62
|
+
env: engineEnv({ home, builtinProviderConfig: runtime.builtinConfig, sessionEnv }),
|
|
63
63
|
cwd,
|
|
64
64
|
scrub: runtime.scrub,
|
|
65
65
|
});
|
package/lib/brief.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The compact handback: what an orchestrator reads by default (`--json`,
|
|
3
|
+
// `zcode result`, `zcode wait --json`). A projection of the full result
|
|
4
|
+
// record (schema zcode.result/1, still what result.json holds on disk and
|
|
5
|
+
// what `--full` prints): decision-relevant fields only, empty keys omitted,
|
|
6
|
+
// long text bounded, passing checks reduced to {command, ok:true}, and a
|
|
7
|
+
// pointer (`result`) to the full record for anything else.
|
|
8
|
+
const BRIEF_SCHEMA = 'zcode.brief/1';
|
|
9
|
+
const SUMMARY_MAX = 600;
|
|
10
|
+
const ERROR_MAX = 500;
|
|
11
|
+
const TAIL_MAX = 1500;
|
|
12
|
+
|
|
13
|
+
function clip(text, max) {
|
|
14
|
+
const s = String(text ?? '');
|
|
15
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const empty = (v) =>
|
|
19
|
+
v === undefined || v === null || v === '' || (Array.isArray(v) && v.length === 0);
|
|
20
|
+
|
|
21
|
+
function briefCheck(c) {
|
|
22
|
+
if (c.ok) return { command: c.command, ok: true };
|
|
23
|
+
const out = { command: c.command, ok: false, exitCode: c.exitCode ?? null };
|
|
24
|
+
if (c.timedOut) out.timedOut = true;
|
|
25
|
+
out.tail = String(c.output || '').slice(-TAIL_MAX);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ALWAYS = new Set(['type', 'schema', 'status', 'ok', 'id']);
|
|
30
|
+
|
|
31
|
+
function buildBrief(result) {
|
|
32
|
+
// checks_failed restates the failing `checks` entries below: kind only.
|
|
33
|
+
const error = result.error
|
|
34
|
+
? result.error.kind === 'checks_failed'
|
|
35
|
+
? { kind: 'checks_failed' }
|
|
36
|
+
: { kind: result.error.kind, message: clip(result.error.message, ERROR_MAX) }
|
|
37
|
+
: null;
|
|
38
|
+
// The failure message usually repeats as an issue; say it once.
|
|
39
|
+
const issues = (result.issues || []).filter((i) => !(error && i === result.error.message));
|
|
40
|
+
const raw = {
|
|
41
|
+
type: 'result',
|
|
42
|
+
schema: BRIEF_SCHEMA,
|
|
43
|
+
status: result.status,
|
|
44
|
+
ok: Boolean(result.ok),
|
|
45
|
+
id: result.id,
|
|
46
|
+
label: result.label,
|
|
47
|
+
preflight: result.preflight,
|
|
48
|
+
summary: clip(result.summary, SUMMARY_MAX),
|
|
49
|
+
// run/agent: the answer is the product. task: the handback is.
|
|
50
|
+
response: result.kind === 'task' ? undefined : result.response,
|
|
51
|
+
files: ((result.changes && result.changes.files) || []).map((f) => f.path),
|
|
52
|
+
checks: (result.checks || []).map(briefCheck),
|
|
53
|
+
questions: result.questions,
|
|
54
|
+
issues,
|
|
55
|
+
unresolved: result.unresolved,
|
|
56
|
+
scopeViolations: result.scopeViolations,
|
|
57
|
+
touched: result.touched,
|
|
58
|
+
decisions: result.decisions,
|
|
59
|
+
visual: result.visual,
|
|
60
|
+
units: result.units,
|
|
61
|
+
error,
|
|
62
|
+
result: result.files && result.files.result,
|
|
63
|
+
};
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [k, v] of Object.entries(raw)) if (ALWAYS.has(k) || !empty(v)) out[k] = v;
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { buildBrief, BRIEF_SCHEMA };
|
package/lib/checks.js
CHANGED
|
@@ -22,6 +22,7 @@ function killGroup(child) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function runCheck(command, { cwd, timeoutMs = 300000, signal, env = process.env } = {}) {
|
|
25
|
+
env = env || process.env;
|
|
25
26
|
const started = Date.now();
|
|
26
27
|
return new Promise((resolve) => {
|
|
27
28
|
let output = '';
|
|
@@ -80,12 +81,12 @@ function runCheck(command, { cwd, timeoutMs = 300000, signal, env = process.env
|
|
|
80
81
|
|
|
81
82
|
// Runs every check in order (later checks still run when an earlier one
|
|
82
83
|
// fails, so the handback carries complete evidence); stops on cancel.
|
|
83
|
-
async function runChecks(commands, { cwd, timeoutMs, signal, onEvent = () => {} } = {}) {
|
|
84
|
+
async function runChecks(commands, { cwd, timeoutMs, signal, env, onEvent = () => {} } = {}) {
|
|
84
85
|
const results = [];
|
|
85
86
|
for (const command of commands) {
|
|
86
87
|
if (signal && signal.aborted) break;
|
|
87
88
|
onEvent({ type: 'check', phase: 'started', command });
|
|
88
|
-
const r = await runCheck(command, { cwd, timeoutMs, signal });
|
|
89
|
+
const r = await runCheck(command, { cwd, timeoutMs, signal, env });
|
|
89
90
|
results.push(r);
|
|
90
91
|
onEvent({
|
|
91
92
|
type: 'check',
|
package/lib/commands/doctor.js
CHANGED
|
@@ -8,6 +8,7 @@ const { sha256File, engineVersion } = require('../engine.js');
|
|
|
8
8
|
const { resolveRuntime, ConfigError } = require('../bootstrap.js');
|
|
9
9
|
const { buildEngineHome, engineEnv, rootDir, liveDir, effectiveMcpServers } = require('../isolation.js');
|
|
10
10
|
const store = require('../store.js');
|
|
11
|
+
const { modelLevels } = require('../models.js');
|
|
11
12
|
|
|
12
13
|
module.exports = async function doctor(parsed) {
|
|
13
14
|
const flags = parsed.flags;
|
|
@@ -51,6 +52,7 @@ module.exports = async function doctor(parsed) {
|
|
|
51
52
|
version,
|
|
52
53
|
builtinProviderConfig: builtinConfig,
|
|
53
54
|
},
|
|
55
|
+
models: modelLevels({ builtinConfig }),
|
|
54
56
|
isolation: {
|
|
55
57
|
root,
|
|
56
58
|
home: built.home,
|
|
@@ -83,6 +85,7 @@ module.exports = async function doctor(parsed) {
|
|
|
83
85
|
` sha256 ${report.engine.sha256}`,
|
|
84
86
|
` version ${version ?? 'unknown'}`,
|
|
85
87
|
` builtin provider config ${builtinConfig}`,
|
|
88
|
+
` models ${Object.entries(report.models).map(([id, l]) => `${id} (effort ${l.join('|')})`).join(', ')}`,
|
|
86
89
|
` isolation root ${root} (one engine HOME per session; config mode: ${built.mode})`,
|
|
87
90
|
` skills inherited ${built.skillsCopied}, mcp servers available with --mcp-inherit ${report.isolation.mcpServersInherited}`,
|
|
88
91
|
` sessions ${report.sessions.total} (${report.sessions.running} running, ${report.sessions.interrupted} interrupted)`,
|