wicked-crew-api-types 0.37.0 → 0.39.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.
Files changed (2) hide show
  1. package/index.d.ts +765 -9
  2. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -147,12 +147,69 @@ export type RoutingInfo =
147
147
  * `false` (or the whole object absent — a daemon before 0.7.33) ⇒ delivery follows verify
148
148
  * UNATTENDED; a composer must say so instead of promising a gate, and must not send
149
149
  * `deliverGate` (an older daemon's launch schema rejects it).
150
+ * - `revisesPr` (api-types 0.38.0, additive) — the daemon accepts `LaunchRunBody.revisesPr`
151
+ * (crew#550). Absent or `false` ⇒ a composer must NOT send it (an older daemon's launch
152
+ * schema rejects it with a 400) and says "this daemon cannot revise a PR" instead.
153
+ *
154
+ * The object is the named {@link HealthCapabilities} since 0.38.0 — the same shape 0.37.0 declared
155
+ * inline, so nothing a 0.37.0 client compiled against moves.
150
156
  */
151
157
  export interface HealthResponse {
152
158
  status: string;
153
159
  version: string;
154
160
  ping: string;
155
- capabilities?: { deliverGate: boolean };
161
+ capabilities?: HealthCapabilities;
162
+ /**
163
+ * Blockers the daemon reports while it still SERVES (additive; wicked-core#411 / wicked-crew#497).
164
+ * One kind today — `state-home.unregistered`: the daemon's state home holds an entry the worker
165
+ * Read fence cannot classify, so every governed launch would be refused and `POST /runs` answers
166
+ * **409** `state_home_unregistered` until it is gone (`GET /diagnostics.stateHome` has the
167
+ * detail). ABSENT when there is nothing to say, and on a daemon before this field. A composer
168
+ * shows it as the blocker it is; `status` stays `ok` — the daemon is up, it refuses to launch.
169
+ */
170
+ warnings?: HealthWarning[];
171
+ /**
172
+ * The BASE skill posture for the NEXT launch (crew#554; additive) — what a composer's confirm
173
+ * line renders as `discipline skill: <name> gen N` (or `MISSING — runs will be refused at
174
+ * intake` under `baseSkillPolicy: 'require'`, `MISSING — runs proceed without it` under
175
+ * `'warn'`). `null` when the setting is off or the skills seam is disabled; absent on a daemon
176
+ * before this field. Cached by the daemon — reading it costs no I/O.
177
+ */
178
+ baseSkill?: BaseSkillPosture | null;
179
+ }
180
+
181
+ /**
182
+ * `GET /health.capabilities` (api-types 0.38.0 — named; 0.37.0 declared the same shape inline on
183
+ * {@link HealthResponse}). What the running daemon + its engine addon can actually do; every key
184
+ * is a promise the deployment keeps, so a client reads it before offering the behaviour.
185
+ */
186
+ export interface HealthCapabilities {
187
+ /** See {@link HealthResponse} — the engine's deliver gate exists (wicked-core-ts ≥ 0.7.24). */
188
+ deliverGate: boolean;
189
+ /**
190
+ * `LaunchRunBody.revisesPr` is accepted (crew#550; crew ≥ 0.7.36). ABSENT on a daemon before
191
+ * the field — read as `false`: do not send `revisesPr` to such a daemon.
192
+ */
193
+ revisesPr?: boolean;
194
+ /**
195
+ * `LaunchRunBody.chatId` is accepted (crew#619; crew ≥ 0.7.39). ABSENT on a daemon before
196
+ * the field — read as `false`: do not send `chatId` to such a daemon.
197
+ */
198
+ chatIdOnLaunch?: boolean;
199
+ /**
200
+ * `InteractiveDocCreateRequest.clisJson` (and the same field on demo/video and testing bodies)
201
+ * is accepted (crew#631; crew ≥ 0.7.40). ABSENT on a daemon before the field — omit `clisJson`
202
+ * on such a daemon.
203
+ */
204
+ seatChipOnCreate?: boolean;
205
+ }
206
+
207
+ /** One `GET /health.warnings[]` entry (additive; wicked-core#411 / wicked-crew#497). */
208
+ export interface HealthWarning {
209
+ /** `state-home.unregistered` today; open for later kinds. */
210
+ kind: string;
211
+ severity: 'warning' | 'error';
212
+ message: string;
156
213
  }
157
214
 
158
215
  /** A run (`AgentSession`). */
@@ -308,6 +365,23 @@ export interface AgentSession {
308
365
  * now; a joined `AgentSession.cost` is a follow-up once that store exists.
309
366
  */
310
367
  created_at?: number;
368
+ /**
369
+ * When this run reached a TERMINAL status — unix SECONDS (api-types 0.38.0; crew#496 / studio#230).
370
+ * Mirrors {@link created_at}: daemon-derived at DTO assembly on BOTH `GET /runs` and
371
+ * `GET /runs/:id` from a `run.ended` audit entry the daemon records when it sees the run's
372
+ * terminal frame (`sessionCompleted` | `sessionFailed` | `runCancelled`), joined like
373
+ * {@link retry_of} / {@link guidance} and hydrated from the same trail scan at boot. Together with
374
+ * {@link created_at} ("started" ≡ "created" — the ONE launch instant) it gives a run its own clock;
375
+ * a skin prefers these over the moment it happened to attach.
376
+ *
377
+ * ABSENT — never `null`, never fabricated — when the daemon has no `run.ended` entry: a run
378
+ * still live, a run that terminalled before this field existed or before THIS daemon booted
379
+ * (no re-emission at boot), a lost trail, or the one crash window between the engine's status
380
+ * write and the daemon's synchronous audit record. Read with `typeof === 'number'`; a duration
381
+ * or a "finished N minutes ago" MUST be omitted for an undated run, never derived from now.
382
+ * Distinct from {@link finished_at} (engine-side unix MILLIS, retention bookkeeping).
383
+ */
384
+ ended_at?: number;
311
385
  /**
312
386
  * The interactive DOCUMENT this run answered (wave 6, F-4R2-006 root fix; api-types 0.36.0) —
313
387
  * daemon-joined at DTO assembly on `GET /runs` and `GET /runs/:id` from the interactive seams'
@@ -336,6 +410,37 @@ export interface AgentSession {
336
410
  /** Wave 6 (F-7R2-006): the seats BENCHED for this run — never convened, never a failover or
337
411
  * judge target. Absent (never `[]`) when none / on an older engine. */
338
412
  benched_seats?: BenchedSeat[];
413
+ /** crew#661: the declared skills this run's phases ran WITHOUT (the published skills snapshot did not
414
+ * hold them when the launching subsystem armed) — the run proceeded DEGRADED, and says so here.
415
+ * ABSENT (never `[]`) when the run launched fully armed, and on a daemon before this field. */
416
+ skill_gaps?: RunSkillGap[];
417
+ /**
418
+ * The surface that triggered this launch (crew#632; crew ≥ 0.7.40): `'studio'` (web UI),
419
+ * `'cli'` (wicked-crew CLI), or `'api'` (programmatic). ABSENT when the launch did not include
420
+ * the field (an older daemon or a caller that omitted it).
421
+ */
422
+ channel?: 'studio' | 'cli' | 'api';
423
+ /**
424
+ * An opaque caller-supplied identifier for the user or system that triggered the launch
425
+ * (crew#632; crew ≥ 0.7.40). ABSENT when the launch did not name one. Use `AgentSession.actor`
426
+ * (the authenticated crew actor) for the identity of WHO launched; `launch_actor` is an
427
+ * ADDITIONAL annotation the caller may attach (e.g. a CI job name or a studio tab id).
428
+ */
429
+ launch_actor?: string;
430
+ /**
431
+ * The number of warm seats in the chat this run was promoted from (crew#641/#642, item 5).
432
+ * ABSENT — never `null` — when the run was not promoted from a chat (`chatId` absent at launch)
433
+ * or on a daemon predating this field. A value of `1` means the intent was authored by a single
434
+ * model that could not disagree with itself.
435
+ */
436
+ chat_seat_count?: number;
437
+ /**
438
+ * Whether the chat this run was promoted from was graph-grounded at launch (crew#641/#642,
439
+ * item 5). `true` = at least one repo graph was bound and held entities; `false` = ungrounded.
440
+ * ABSENT — never `null` — when the run was not promoted from a chat or on a daemon predating
441
+ * this field.
442
+ */
443
+ chat_grounded?: boolean;
339
444
  }
340
445
 
341
446
  /** An ordered unit of work within a run (`WorkUnit`). */
@@ -372,6 +477,14 @@ export interface WorkUnit {
372
477
  * before the field existed, and on an older engine.
373
478
  */
374
479
  executes_code?: boolean;
480
+ /**
481
+ * The BASE skill this unit follows (wicked-core#468; additive, skip-if-none on the wire): the
482
+ * run's role-keyed discipline skill, copied onto every AGENT unit at plan time (never a Tool
483
+ * unit). The unit header renders it with the unit's `role` as `discipline: <name> §<role> gen N`,
484
+ * `N` from the unit's `skillsSnapshotHanded.gen` frame. Absent on runs without one, on units
485
+ * planned before the field existed, and on an older engine.
486
+ */
487
+ base_skill_ref?: string | null;
375
488
  }
376
489
 
377
490
  /** A run plus its ordered units (`SessionView`) — the shape `GET /runs` returns. */
@@ -438,6 +551,14 @@ export interface OnboardRef {
438
551
  * the message — no operator hand-edit involved.
439
552
  */
440
553
  export interface SeatHealth {
554
+ /**
555
+ * `inactive` is NO LONGER PRODUCED from crew 0.7.36 (api-types 0.38.0 doc; DES-L3 R5): the daemon
556
+ * stopped flipping a seat on `stepFailed{workerError}`, on quota/401/timeout phrases and on
557
+ * repeated ACP fallback — seat standing is the engine's per-run ballot bench
558
+ * (`AgentSession.benched_seats`) plus the seat's own auth refusal (`RosterSeat.auth`). The token
559
+ * stays in the union so a daemon before 0.7.36 still parses; a reader treats it exactly as
560
+ * before (a seat-level error, recovery by the probe or a restart).
561
+ */
441
562
  status: 'active' | 'inactive';
442
563
  /** Bounded excerpt of the last seat-level error; present while `inactive`. */
443
564
  message?: string;
@@ -522,12 +643,18 @@ export interface RosterSeat {
522
643
  * `councilSeatFailed` evidence (`non_zero_exit` / `timed_out`, the derivative `benched` kind
523
644
  * excluded), folded over a bounded window (`window_ms`) and cleared by the seat's next ok unit
524
645
  * output. `council_eligible` is false while it is present.
646
+ *
647
+ * @deprecated ABSENT from crew 0.7.36 (DES-L3 R5b): the daemon-wide council-failure ledger is
648
+ * retired — the engine benches a dead seat per run at its own ballot threshold and the run DTO
649
+ * carries the bench (`AgentSession.benched_seats`). Removed from this contract one minor later;
650
+ * a reader must tolerate absence today.
525
651
  */
526
652
  council_bench?: RosterSeatCouncilBench;
527
653
  [k: string]: unknown;
528
654
  }
529
655
 
530
- /** `RosterSeat.council_bench` (api-types 0.35.0). */
656
+ /** `RosterSeat.council_bench` (api-types 0.35.0).
657
+ * @deprecated ABSENT from crew 0.7.36 (see {@link RosterSeat.council_bench}); removed one minor later. */
531
658
  export interface RosterSeatCouncilBench {
532
659
  /** Primary ballot failures inside the window. */
533
660
  failures: number;
@@ -628,6 +755,26 @@ export interface GateInfo {
628
755
  export interface GateDecision {
629
756
  approve: boolean;
630
757
  amend?: string;
758
+ /**
759
+ * The decision ARM (api-types 0.38.0, additive; DES-L1 PR-1B / crew PR-2, crew ≥ 0.7.36 on
760
+ * wicked-core-ts ≥ 0.7.27). Absent = today's two-arm mapping: `approve: true` approves (retry
761
+ * the same unit, `amend` steers it), `approve: false` rejects (cancels the run, keeps a dirty
762
+ * worktree). `'request_changes'` — an ESCALATION gate only: rewind to the most recent creator
763
+ * phase at or before the gated unit, hand it the evaluator's findings (+ `amend` as the note)
764
+ * and re-dispatch it (`unitReworkAmended{scope: 'request_changes'}` → `resumed` →
765
+ * `unitDispatched{attempt: last + 1}`); requires `approve: false` — a disagreement between
766
+ * `action` and `approve` answers 400, a gate with no creator phase before it 409. An older
767
+ * daemon's strict schema rejects the key with a 400 — omit it against such servers.
768
+ */
769
+ action?: 'approve' | 'request_changes' | 'reject';
770
+ /**
771
+ * Where an approve's `amend` lands (api-types 0.38.0, additive; same release as `action`).
772
+ * Absent = `'cursor'` (today: the gated unit's description). `'creator'` — the first creator
773
+ * phase at or after the cursor receives it (`unitReworkAmended{scope: 'creator'}`) while the
774
+ * cursor dispatches unamended: the intake steer that must reach the fix phase, not triage.
775
+ * Identical text already present on the target is not appended twice.
776
+ */
777
+ amendScope?: 'cursor' | 'creator';
631
778
  }
632
779
 
633
780
  /**
@@ -646,6 +793,12 @@ export interface ReassignRequest {
646
793
  * fields cover the frames the daemon and the studio inspect; the index
647
794
  * signature keeps the shape additive-safe so new variants pass through
648
795
  * untouched (DES-STUDIO-001 §2.1, §5.1).
796
+ *
797
+ * Exact per-frame shapes for consumers that narrow on `type`: the cockpit
798
+ * {@link InsightEvent} union, the gate-evidence {@link GateEvidenceEvent}
799
+ * union, and — typed in api-types 0.38.0 as the engine already emits them —
800
+ * the 11-key {@link GateEscalatedEvent}, {@link SandboxPostureEvent} and
801
+ * {@link WorktreeRetainedEvent}.
649
802
  */
650
803
  export interface CoreEvent {
651
804
  type: string;
@@ -772,6 +925,9 @@ export interface CoreEvent {
772
925
  degradedReason?: string | null;
773
926
  degraded_reason?: string | null;
774
927
  seatConstraint?: string | null;
928
+ /** `unitDistributed` (wicked-core#461): the evaluator ≠ creator fallback as a field — see
929
+ * `UnitDistributedEvent.distinctnessFallback`. */
930
+ distinctnessFallback?: 'creator_seat' | 'same_cli_instance' | null;
775
931
  /** `gateEvaluated` (wave 6, F-7R2-005): `true` when NOTHING gated the unit — render as UNGATED. */
776
932
  ungated?: boolean;
777
933
  /** `gateEvaluated` (wave 6): why, when `ungated`. */
@@ -946,8 +1102,20 @@ export interface RecordedEvent extends CoreEvent {
946
1102
  * is required and spans every frame of the run; that one is optional and scoped to a stream. The
947
1103
  * name is inherited from the wire and narrowed here rather than renamed, because the wire is what
948
1104
  * `runEvents` returns.
1105
+ *
1106
+ * Across a daemon RESTART the counter is re-seeded from the trail's highest `seq` + 1, so `seq`
1107
+ * stays strictly increasing over the whole trail — but two records of one run may be separated
1108
+ * by an engine that lost every in-flight frame in between (api-types 0.38.0; crew#513).
949
1109
  */
950
1110
  seq: number;
1111
+ /**
1112
+ * Stamped `true` on exactly ONE record per restart: the first frame a fresh engine writes for a
1113
+ * run whose trail already existed (wicked-core `event_log.rs`; api-types 0.38.0, crew#513). Every
1114
+ * other record omits the key — never `false`. A timeline reads it as a "daemon restarted here"
1115
+ * boundary: frames the previous process never wrote are not coming, and the run's `ended_at` may
1116
+ * be absent for that reason.
1117
+ */
1118
+ daemonRestarted?: true;
951
1119
  }
952
1120
 
953
1121
  /**
@@ -968,6 +1136,20 @@ export interface UnitDispatchedEvent {
968
1136
  session: string;
969
1137
  ord: number;
970
1138
  attempt: number;
1139
+ /**
1140
+ * The BASE skill directive this dispatch carries (wicked-core#468; additive): the run's
1141
+ * role-keyed discipline skill and the `§<role>` section the unit was told to follow. Emitted
1142
+ * unconditionally by an engine that has it — `null` when the run declares none; ABSENT on an
1143
+ * older engine. The generation it is handed from is the same unit's `skillsSnapshotHanded.gen`
1144
+ * (the handoff is where the generation is known truthfully). A run page renders
1145
+ * `discipline: <name> §<role> gen N`.
1146
+ *
1147
+ * `handed` (api-types 0.38.0, additive; wicked-core#479 — typed AHEAD of its producer, which
1148
+ * lands in the wave-3 core-ts): `true` when the admitted snapshot holds the base skill and the
1149
+ * carrier delivered it (the skill form is not `Unloaded`); `false` when the run only NAMED it.
1150
+ * ABSENT ⇒ UNKNOWN (an engine before the field) — render `gen ?`, never "not handed".
1151
+ */
1152
+ baseSkill?: { name: string; role: 'creator' | 'evaluator' | 'neutral'; handed?: boolean } | null;
971
1153
  }
972
1154
 
973
1155
  /** §3 B3 — token/cost burn for one unit run. `costUsd` is `null` when no cost is known. */
@@ -1082,6 +1264,16 @@ export interface GateEvaluatedEvent {
1082
1264
  * validator gated it) — `"no eligible judge seat distinct from creator \`claude\` (roster: …;
1083
1265
  * benched: …)"`. `null` when a judge ran or none was wanted; absent on an older engine. */
1084
1266
  judgeSkippedReason?: string | null;
1267
+ /**
1268
+ * The evaluator's own `VERDICT:` token (api-types 0.38.0, additive; DES-L1 PR-1A, wicked-core-ts
1269
+ * ≥ 0.7.27): after trimming leading decoration, the LAST line whose first token is `VERDICT`
1270
+ * decides — `"PASS"`, `"FAIL"`, or whatever token the seat wrote (`"CONDITIONAL"` is not PASS).
1271
+ * Parsed for an Evaluator-role agent unit only; `null` for creator / neutral / tool units and
1272
+ * when no such line exists (then `denial.source` is `evaluator_verdict` and the gate escalates
1273
+ * `verdict_not_pass`). ABSENT on an engine before the field — a reader treats absent as "not
1274
+ * parsed", never as PASS.
1275
+ */
1276
+ evaluatorVerdict?: string | null;
1085
1277
  }
1086
1278
 
1087
1279
  /** Foundation wave: session started with enriched context. */
@@ -1234,15 +1426,88 @@ export interface ValidationPinAttachedEvent {
1234
1426
  criterion: string;
1235
1427
  }
1236
1428
 
1237
- /** P2 — a HumanConfirmIf gate escalated to human review. */
1429
+ /**
1430
+ * P2 — a HumanConfirmIf gate escalated to human review. Since wicked-core#464 every fold DENIAL
1431
+ * parks the run here too (followed by `awaitingHuman{gateKind: 'escalation'}`), and `condition`
1432
+ * names the CLASS: `evaluator_mutated_worktree` (the worktree guard) · `boundary_deny` (input
1433
+ * governance refused a tool call) · `dead_seat` (the seat is unusable — signed out / not installed /
1434
+ * benched) · `floor_failed` (a deterministic floor: repo checks, pinned validator, substance,
1435
+ * deliverables) · `verdict_not_pass` (the layer-2 judge, a worker failure, or — wicked-core-ts ≥
1436
+ * 0.7.27 — the evaluator's own `VERDICT` line), or a def-declared `human_confirm_if` condition
1437
+ * (`defGate: true`). `verdictSummary` is the denial reason.
1438
+ *
1439
+ * The seven additive keys (api-types 0.38.0 — the engine has emitted all eleven fields, twelve JSON
1440
+ * keys with `type`, since wicked-core#464; `event.rs::GateEscalated`) carry what a decision arm
1441
+ * needs without re-reading the unit. ABSENT on a daemon whose engine predates them; when present
1442
+ * they are ALWAYS all present.
1443
+ */
1238
1444
  export interface GateEscalatedEvent {
1239
1445
  type: 'gateEscalated';
1240
1446
  session: string;
1241
1447
  ord: number;
1242
1448
  condition: string;
1243
1449
  verdictSummary: string;
1450
+ /** The attempt whose output — persisted as the unit's REJECTED transcript — this gate reviews. */
1451
+ attempt?: number;
1452
+ /**
1453
+ * The raw denial LAYER token (`gateEvaluated.denial.source`): `worktree_guard` ·
1454
+ * `input_governance` · `dead_seat` · `repo_checks` · `repo_checks_timeout` · `pinned_validator`
1455
+ * · `substance` · `deliverables` · `agent_validator` · `worker_failure` · `evaluator_verdict`
1456
+ * (0.7.27) · `governance`. `""` (EMPTY) = a hook veto whose source identity was folded away
1457
+ * (`actor.rs` `boundary_deny` arm with no structured denial) — a reader keys copy on
1458
+ * `(condition, denialSource)` and treats `""` beside `boundary_deny` as input governance.
1459
+ */
1460
+ denialSource?: string;
1461
+ /** `true` when the unit's OWN def declared this escalation (`human_confirm_if`); `false` = engine-authored. */
1462
+ defGate?: boolean;
1463
+ /** The phase produced output text — the precondition of an "accept the captured output" arm. */
1464
+ outputCaptured?: boolean;
1465
+ /**
1466
+ * The worktree guard's restore outcome, mirroring {@link WorktreeRestoredEvent} — meaningful for
1467
+ * `evaluator_mutated_worktree` only; `false` / `[]` / `null` for every other class.
1468
+ */
1469
+ restored?: boolean;
1470
+ /** The paths the restore discarded (`git status --porcelain` status + path), as {@link WorktreeRestoredEvent.discarded}. */
1471
+ discarded?: { status: string; path: string }[];
1472
+ /** The `refs/wicked/suggestions/<run>/<ord>/<attempt>` ref holding the discarded changes; `null` when none. */
1473
+ suggestionRef?: string | null;
1244
1474
  }
1245
1475
 
1476
+ /**
1477
+ * F-E2E-039 (api-types 0.38.0; emitted since wicked-core `pipeline.rs` armed it): the containment
1478
+ * POSTURE a unit's worker actually ran under, once per dispatch, so a skin can say it instead of
1479
+ * implying an OS sandbox that was never armed. `os` — the registry seat record arms an OS write
1480
+ * boundary (macOS `sandbox-exec` / Linux `bwrap`); `advisory` — the record arms none, so
1481
+ * containment is the worktree guard plus the command-text fences, which a shell can evade.
1482
+ * `reason` is the engine's sentence naming the deciding record/flag.
1483
+ */
1484
+ export type SandboxPostureEvent = {
1485
+ type: 'sandboxPosture';
1486
+ session: string;
1487
+ ord: number;
1488
+ /** The registry seat key. */
1489
+ cli: string;
1490
+ posture: 'os' | 'advisory';
1491
+ reason: string;
1492
+ };
1493
+
1494
+ /**
1495
+ * F-RC1-064 / F-E2E-028 (api-types 0.38.0; emitted since wicked-core#456): a TERMINAL run's
1496
+ * worktree was KEPT because it holds uncommitted work the run branch does not carry — named on the
1497
+ * wire so a skin can point the operator at the path. Cancel and the terminal reap apply the same
1498
+ * rule; the retention window (`WICKED_COMPLETED_WORKTREE_KEEP_DAYS`) then reaps it clean-only. A
1499
+ * clean tree is reaped and emits nothing. Session-level (no `ord`). Both new frames are `type`
1500
+ * aliases on purpose — see {@link EvaluatorMutatedWorktreeEvent}: an alias satisfies `CoreEvent`'s
1501
+ * index signature and relays through the CoreEvent-typed broadcast seams unchanged.
1502
+ */
1503
+ export type WorktreeRetainedEvent = {
1504
+ type: 'worktreeRetained';
1505
+ session: string;
1506
+ /** The kept worktree's absolute path (the engine's; a skin never resolves it). */
1507
+ path: string;
1508
+ reason: string;
1509
+ };
1510
+
1246
1511
  /** P2 — a tool-executor command was dispatched (non-agent unit). */
1247
1512
  export interface ToolExecutorDispatchedEvent {
1248
1513
  type: 'toolExecutorDispatched';
@@ -1349,6 +1614,51 @@ export interface RepoCheckRun {
1349
1614
  /** The last 4 KiB of each stream — the evidence, verbatim. */
1350
1615
  stdoutTail: string;
1351
1616
  stderrTail: string;
1617
+ /**
1618
+ * The baseline-diff floor's evidence (api-types 0.38.0 — the seven keys the engine has emitted
1619
+ * since wicked-core#469 / #476). PRESENT ON EVERY CHECK from that engine on: the one producer of
1620
+ * this camelCase form, `event.rs::check_run_json`, writes all seven every time — `0` / `null` / `[]`
1621
+ * when there is nothing to say (the Rust `Option` → `null` rule; the struct's own
1622
+ * `skip_serializing_if` governs the persisted record, not this frame). ABSENT only on an older
1623
+ * engine — the `?:` is that one case, so read with `== null`, never `=== undefined`. `boundS` — the
1624
+ * EFFECTIVE wall-clock bound the check ran under, seconds (base × host-load factor; a bare number,
1625
+ * `0` when unknown); `boundNote` — how it was derived, for the operator (`null` when none).
1626
+ */
1627
+ boundS?: number;
1628
+ boundNote?: string | null;
1629
+ /**
1630
+ * Failure identifiers streamed off the runner's output (`test a::b ... FAILED`, ` FAIL file >
1631
+ * name`, `path: error TS1234: …`) — what the baseline diff compares. `[]` when the runner's format
1632
+ * is not one the scanner knows (the diff then compares exit codes).
1633
+ */
1634
+ failureIds?: string[];
1635
+ /**
1636
+ * Set when the check FAILED and the same check was run on the base (`base`): `regression` (head
1637
+ * failures absent on the base — denies) or `pre_existing_in_sandbox` (equal failure sets — never
1638
+ * this change's doing); `floor_env_mismatch` was emitted by engines before wicked-core-ts 0.7.27
1639
+ * and is retired. An OPEN string — new tokens may follow; `null` when the check passed,
1640
+ * timed out, could not run, or no base comparison was possible (the check then denies as before).
1641
+ */
1642
+ classification?: string | null;
1643
+ /** Head failures that ALSO fail on the base (never this change's doing); `[]` when none. */
1644
+ preExisting?: string[];
1645
+ /** Head failures ABSENT on the base — the regressions that deny; `[]` when none. */
1646
+ regressions?: string[];
1647
+ /** The same check run on the run BASE, when the floor ran it; `null` otherwise. */
1648
+ base?: RepoCheckBaseRun | null;
1649
+ }
1650
+
1651
+ /**
1652
+ * `RepoCheckRun.base` — the same check on the run base (api-types 0.38.0; wicked-core
1653
+ * `repo_checks.rs::BaseRun`, serialised by `event.rs::base_run_json`). Typed as an OPEN object this
1654
+ * release; the engine writes all four keys every time: `head` (the base commit), `cached` (read back
1655
+ * from this run's cache), `run` (a nested check run — `null` when the base could not be run) and
1656
+ * `error` (why not: export failed, the base declares no such check, its install failed, the repo
1657
+ * opted out — the head check then denies fail-closed; `null` when it ran). The fields are named in
1658
+ * the next minor (DES-L2 2C); read them through `unknown` narrowing until then.
1659
+ */
1660
+ export interface RepoCheckBaseRun {
1661
+ [k: string]: unknown;
1352
1662
  }
1353
1663
 
1354
1664
  /** wicked-core F-039 — the engine ran the repository's OWN checks in the worktree for the def's
@@ -1620,6 +1930,15 @@ export interface UnitReworkAmendedEvent {
1620
1930
  amendment: string;
1621
1931
  /** The unit's description after the amendment was injected. */
1622
1932
  updatedDescription: string;
1933
+ /**
1934
+ * WHICH unit received the text and WHY (api-types 0.38.0, additive; DES-L1 PR-1B, wicked-core-ts ≥
1935
+ * 0.7.27): `'cursor'` — the gated unit, today's approve-with-steer (`GateDecision.amendScope`
1936
+ * absent or `'cursor'`); `'creator'` — the first creator phase at/after the cursor
1937
+ * (`amendScope: 'creator'`); `'request_changes'` — the creator phase the gate rewound to, and
1938
+ * `amendment` then carries the evaluator's full findings + the operator's note. ABSENT on an
1939
+ * engine before the field — read as `'cursor'`.
1940
+ */
1941
+ scope?: 'cursor' | 'creator' | 'request_changes';
1623
1942
  }
1624
1943
 
1625
1944
  /** P2 — a worker's ApplyStepResult arrived and output is ready to be gated. Fires before GateDecided.
@@ -1706,6 +2025,26 @@ export interface UnitDistributedEvent {
1706
2025
  /** WHY the candidate seats were narrowed BEFORE the council voted (core#401): a non-portable
1707
2026
  * skill constrained the unit to a claude seat. `null` when every roster seat was a candidate. */
1708
2027
  seatConstraint: string | null;
2028
+ /**
2029
+ * The evaluator ≠ creator DISCLOSURE as a field (wicked-core#461, crew#556): `'creator_seat'` when a
2030
+ * review/test unit STAYS on a seat that built what it checks because no eligible seat distinct from
2031
+ * the builders admits it — a single-eligible-seat roster, or a bench that emptied the pool
2032
+ * (`degradedReason` then says which; on a bench-free single-seat roster it stays `null`, so this
2033
+ * field is the ONLY disclosure). `null` otherwise. The fallback seat is always a still-eligible one:
2034
+ * a benched or dead seat is never the fallback.
2035
+ *
2036
+ * OPTIONAL on this side of the wire deliberately: the engine emits the key unconditionally (`null`,
2037
+ * never absent — the `seatConstraint` rule) from the wicked-core release carrying #461 on, but an
2038
+ * older engine does not send it at all, and this contract must read frames from both. Consumers
2039
+ * guard with `== null`, never `=== undefined`.
2040
+ *
2041
+ * `'same_cli_instance'` (wicked-core#591/#595): the unit IS on a seat distinct from every builder
2042
+ * seat, but that seat runs the SAME cli as a builder (`claude#2` grading `claude#1`) — instance
2043
+ * distinctness removes the creator's context, not the model's blind spots, so it is a degraded
2044
+ * gate and disclosed. `'creator_seat'` dominates when both would apply. Consumers must treat an
2045
+ * unrecognised value as a disclosure too, never as "distinct".
2046
+ */
2047
+ distinctnessFallback?: 'creator_seat' | 'same_cli_instance' | null;
1709
2048
  /** @deprecated api-types 0.36.0 — the engine emits `routingMethod`; removed in 0.37. */
1710
2049
  routing_method?: 'council' | 'degraded' | 'evaluator_distinct' | 'tool';
1711
2050
  /** @deprecated api-types 0.36.0 — the engine emits `agreementPct`; removed in 0.37. */
@@ -2078,6 +2417,11 @@ export interface SkillBaselineRecord {
2078
2417
  * the portable form names that skill (`wicked-garden-<x>`) instead;
2079
2418
  * - `requires-harness:claude` — the author declared `metadata.requires-harness: claude` in the
2080
2419
  * frontmatter: the skill genuinely needs the Claude harness (not an authoring defect).
2420
+ * - `claude-dispatch` (api-types 0.38.0; crew ≥ 0.7.36, DES-L6 D-21) — the body CALLS a Claude
2421
+ * Code dispatch primitive (`Task(`, `Skill(`, `context: fork`, `AskUserQuestion`, `TaskCreate`)
2422
+ * that no other seat has: a pi/opencode/codex worker cannot follow it. The portable form is the
2423
+ * plain-prose Hand-off paragraph. Reported by the publisher; garden's lint fails the same
2424
+ * skill (12.38.0). An older daemon never emits the token.
2081
2425
  */
2082
2426
  export type SkillPortabilityReason =
2083
2427
  | 'plugin-root'
@@ -2085,7 +2429,8 @@ export type SkillPortabilityReason =
2085
2429
  | 'cwd-script'
2086
2430
  | 'relative-link'
2087
2431
  | 'cross-skill-path'
2088
- | 'requires-harness:claude';
2432
+ | 'requires-harness:claude'
2433
+ | 'claude-dispatch';
2089
2434
 
2090
2435
  /** Per-reason portability of a skill (F-079, api-types 0.34.0) — reported beside `portable`. */
2091
2436
  export interface SkillPortability {
@@ -2384,6 +2729,17 @@ export interface SkillMutationResult extends SkillAnalyzeResult {
2384
2729
  * (`snapshot: null`), not a 409. */
2385
2730
  export interface SkillPublishResult extends SkillAnalyzeResult {
2386
2731
  snapshot: { gen: number; path: string; contentHash: string; skills: number } | null;
2732
+ /** The BASE skill posture AFTER this publish (crew#554): `present: false` with a `finding` is the
2733
+ * "published a snapshot without the discipline skill" warning. Absent on a daemon before it. */
2734
+ baseSkill?: BaseSkillPosture | null;
2735
+ /**
2736
+ * `true` when the publish found NOTHING to publish (api-types 0.38.0; crew#547 / DES-L6, crew ≥
2737
+ * 0.7.36): the effective tree's `contentHash` equals the current generation's, so no new
2738
+ * `snapshots/<gen>/` was minted, `current` did not move and `snapshot` is the CURRENT generation
2739
+ * (its `gen` unchanged). ABSENT (never `false`) on a publish that staged a generation, and on a
2740
+ * daemon before the field — a skin shows "already published as gen N", not a new generation.
2741
+ */
2742
+ unchanged?: true;
2387
2743
  }
2388
2744
 
2389
2745
  /** `POST /skills/refresh-baseline` 200 body — the three-way merge per FILE (baseline_old /
@@ -2405,6 +2761,9 @@ export interface SkillRefreshResult extends SkillAnalyzeResult {
2405
2761
  removed: string[];
2406
2762
  /** Skills flagged `conflict` by this refresh. */
2407
2763
  conflicts: string[];
2764
+ /** The BASE skill posture AFTER this refresh (crew#554): a refresh moves the CATALOG, not the handed
2765
+ * generation, so `inCatalog: true, present: false` reads "publish to hand it". Absent on a daemon before it. */
2766
+ baseSkill?: BaseSkillPosture | null;
2408
2767
  }
2409
2768
 
2410
2769
  /** The 409 body of a `/skills` mutation whose `expectedRevision` is stale — a CAS conflict, the
@@ -3038,6 +3397,27 @@ export interface LaunchRunBody {
3038
3397
  * older daemon's launch schema rejects the field with a 400 — omit it against such servers.
3039
3398
  */
3040
3399
  deliverGate?: 'human' | 'auto';
3400
+ /**
3401
+ * REVISE an existing pull request instead of opening a new one (api-types 0.38.0, additive;
3402
+ * crew#550 / DES-L9, crew ≥ 0.7.36 on wicked-core-ts ≥ 0.7.27): the number of an OPEN same-repo PR.
3403
+ * The daemon resolves its head branch (`gh pr view` in the registered clone), the engine bases the
3404
+ * run's worktree on that head, and the deliver phase pushes exactly the run's commits to that
3405
+ * branch and comments the run record on the PR — no second PR, no rebase. Needs `repoRef` and
3406
+ * `workflow` (400 without), a resolved `deliver: 'pr'` (409 otherwise; an explicit
3407
+ * `deliver: 'none'` is a 400), and the PR must be OPEN and not from a fork (409 naming why).
3408
+ * Send it ONLY when `GET /health.capabilities.revisesPr === true` — an older daemon's strict
3409
+ * launch schema rejects the key with a 400.
3410
+ */
3411
+ revisesPr?: number;
3412
+ /**
3413
+ * Promote-from-chat linkage (crew#619; api-types 0.39.0, additive): the id of the chat this
3414
+ * run was promoted from. When present, the daemon retains that chat's conversation transcript
3415
+ * on disk for the run's lifetime, so the Continue-in-Build prefill is always reproducible
3416
+ * even if the chat is idle-reclaimed before the run finishes. Optional; omit when the launch
3417
+ * is not promoted from a chat. Check `GET /health.capabilities.chatIdOnLaunch` before sending —
3418
+ * an older daemon's strict launch schema rejects the key with a 400.
3419
+ */
3420
+ chatId?: string;
3041
3421
  /**
3042
3422
  * Retry lineage (DES-UX-001 §8.3, CREW-UX-3; api-types 0.8.0): the id of the run this
3043
3423
  * launch retries. Must name an EXISTING run id — an unknown id fails the launch (400 with
@@ -3069,6 +3449,21 @@ export interface LaunchRunBody {
3069
3449
  * `AgentSession.group_label`. Mutually exclusive with `campaignId` (both ⇒ 400).
3070
3450
  */
3071
3451
  groupLabel?: string;
3452
+ /**
3453
+ * The surface that triggered this launch (crew#632; additive). `'studio'` (web UI), `'cli'`
3454
+ * (wicked-crew CLI), or `'api'` (programmatic caller). Persisted in the `run.launched` audit
3455
+ * entry and echoed as `AgentSession.channel`. Absent ⇒ origin unknown. Check
3456
+ * `GET /health.capabilities.seatChipOnCreate` to confirm the daemon accepts the field before
3457
+ * sending — an older daemon's strict schema rejects unknown keys with a 400.
3458
+ */
3459
+ channel?: 'studio' | 'cli' | 'api';
3460
+ /**
3461
+ * An opaque caller-supplied identifier for the user or system that triggered the launch
3462
+ * (crew#632; additive). Persisted in the `run.launched` audit entry and echoed as
3463
+ * `AgentSession.launch_actor`. Absent ⇒ caller omitted it. Subject to the same daemon-version
3464
+ * guard as `channel` above.
3465
+ */
3466
+ actor?: string;
3072
3467
  }
3073
3468
 
3074
3469
  /**
@@ -3223,6 +3618,15 @@ export interface WorkflowDef {
3223
3618
  phases: PhaseDef[];
3224
3619
  /** True for built-in workflows that have dedicated entry points and must not appear in the work-mode selector. */
3225
3620
  is_system?: boolean;
3621
+ /**
3622
+ * The BASE skill every agent phase of THIS workflow follows (wicked-core#468; additive, engine
3623
+ * ≥ the release carrying it — an older engine's strict def parser refuses the key). The engine
3624
+ * leads every unit prompt with `Invoke your skill "<base>" … and follow its §<role> section`
3625
+ * before the phase's `skill_ref` directive. Absent ⇒ the daemon's `SystemSettings.baseSkillRef`
3626
+ * default (exported as `WICKED_BASE_SKILL_REF`); `""` ⇒ an explicit opt-out for this workflow.
3627
+ * Gated at intake: a launch whose snapshot lacks the skill is refused before any unit is planned.
3628
+ */
3629
+ base_skill_ref?: string | null;
3226
3630
  }
3227
3631
 
3228
3632
  /** Top-level requirements_graph.json artifact (schema 1.0.0). */
@@ -3287,7 +3691,15 @@ export interface CodeGraphEdge { src: string; tgt: string; }
3287
3691
  export interface CodeGraphData {
3288
3692
  nodes: CodeGraphNode[];
3289
3693
  edges: CodeGraphEdge[];
3694
+ /** Counts of the SERVED SLICE (`graph-view --limit`, `SystemSettings.graphNodeLimit`) — what is in `nodes`/`edges`. */
3290
3695
  stats: { nodeCount: number; edgeCount: number; fileCount: number };
3696
+ /**
3697
+ * Whole-graph counts (api-types 0.38.0, additive; crew#505 / F-RC1-100): what the repository's
3698
+ * estate database holds in total, from `wicked-estate stats`, so a tile can read "150 of 5,470
3699
+ * symbols shown" instead of presenting the slice as the repo. ABSENT when the stats line did not
3700
+ * parse or on a daemon before the field — then only the slice is known; never substitute `stats`.
3701
+ */
3702
+ totals?: { nodes: number; edges: number; files: number };
3291
3703
  }
3292
3704
 
3293
3705
  /**
@@ -3349,6 +3761,26 @@ export interface SystemSettings {
3349
3761
  * launches (those are always `'none'`).
3350
3762
  */
3351
3763
  deliverDefault?: 'pr' | 'none';
3764
+ /**
3765
+ * The BASE skill every governed agent unit follows (crew#554 / wicked-core#468; additive): the
3766
+ * frontmatter name the daemon exports as the engine-config default `WICKED_BASE_SKILL_REF`, so
3767
+ * the engine leads EVERY unit prompt with `Invoke your skill "<base>" … and follow its §<role>
3768
+ * section` (`creator` | `evaluator` | `neutral`) ahead of the phase's own `skill_ref` directive.
3769
+ * A workflow def's own `base_skill_ref` overrides it (`""` there = opt-out for that workflow).
3770
+ * Shipped default `"wicked-garden-governed-worker"`; `""` = OFF (the variable is deleted).
3771
+ * Absent reads as the shipped default.
3772
+ */
3773
+ baseSkillRef?: string;
3774
+ /**
3775
+ * What a published snapshot WITHOUT the base skill means (crew#554; additive). `'warn'` (the
3776
+ * default): the daemon exports the variable ONLY when the current generation holds the skill —
3777
+ * otherwise runs proceed without the discipline directive and `GET /diagnostics.skills` carries a
3778
+ * `skills.base-skill` WARNING (a fresh install lacks the skill until the garden that ships it is
3779
+ * published). `'require'`: the variable is always exported and the engine REFUSES every launch at
3780
+ * intake until a generation holding the skill is published (`POST /runs` → 422
3781
+ * `base_skill_refused`); the finding is an `error`.
3782
+ */
3783
+ baseSkillPolicy?: 'warn' | 'require';
3352
3784
  /**
3353
3785
  * The stall watchdog's DETECTION threshold (crew#287; api-types 0.18.0 — previously a
3354
3786
  * daemon-local extension): minutes a run in `executing` may go without ANY engine event on the
@@ -3401,6 +3833,18 @@ export interface SystemSettings {
3401
3833
  [key: `studio.${string}`]: unknown;
3402
3834
  }
3403
3835
 
3836
+ /**
3837
+ * `GET /settings` → 200 (api-types 0.38.0 — the body's first named type; the shape `{ settings }`
3838
+ * is unchanged since 0.1). `path` (additive; DES-L10 PR-L10-6, crew ≥ 0.7.35) is the ABSOLUTE
3839
+ * path of the settings file the daemon reads and writes (`WICKED_CREW_SYSTEM_SETTINGS` honoured),
3840
+ * so a skin names the real file instead of a guessed default. ABSENT on a daemon before the field
3841
+ * — render "the daemon's settings file", never a fabricated path.
3842
+ */
3843
+ export interface SettingsResponse {
3844
+ settings: SystemSettings;
3845
+ path?: string;
3846
+ }
3847
+
3404
3848
  // ── Requirements management (server-side search + overrides; crew api/requirements.ts) ──
3405
3849
  export interface RequirementSummary {
3406
3850
  key: string;
@@ -3671,6 +4115,18 @@ export interface InteractiveDocCreateRequest {
3671
4115
  repo_ref?: string;
3672
4116
  /** SEVERAL subject repositories (at most 8); the union with `repo_ref`, de-duplicated. */
3673
4117
  repo_refs?: string[];
4118
+ /**
4119
+ * Per-doc council roster override (crew#631; crew ≥ 0.7.40 — check `GET /health` capabilities
4120
+ * `seatChipOnCreate` before sending). JSON-serialised `AgenticCli[]`; defaults to the daemon's
4121
+ * full roster when absent. Seat keys not present in `GET /roster` → 400.
4122
+ */
4123
+ clisJson?: string;
4124
+ /** Launch channel (crew#632). Persisted to the run's `run.launched` audit entry and served on
4125
+ * run DTOs as `channel`. ABSENT when the caller does not know the channel. */
4126
+ channel?: 'studio' | 'cli' | 'api';
4127
+ /** Launch actor (crew#632). Free-form string (≤ 256 chars) identifying the human or service
4128
+ * that triggered the launch. ABSENT when unknown. */
4129
+ actor?: string;
3674
4130
  }
3675
4131
 
3676
4132
  /**
@@ -3916,10 +4372,32 @@ export interface ChatOpenBody {
3916
4372
  /** File the chat into a project (`crew.chat` membership, attached on open) — and, when no
3917
4373
  * `repoRefs` are given, scope it to every registered `crew.repo` member of that project. */
3918
4374
  projectId?: string;
4375
+ /**
4376
+ * The scope the caller NAMES (api-types 0.39.0, studio#323 R4). Omitted ⇒ the legacy inference
4377
+ * above (`repoRefs` ⇒ `repos`, else `projectId` ⇒ `project`, else `none`) — unchanged for older
4378
+ * skins. Named, the daemon resolves exactly that kind or refuses with 400:
4379
+ * - `system` — the platform itself (daemon, seats, runs, config): NO repository and no code
4380
+ * graph. The seats have no live read of the daemon; what they know of it is what
4381
+ * the message carries. Takes no `repoRefs`; `projectId` only files the chat.
4382
+ * - `everything` — EVERY repository registered with the daemon, across all projects, as read
4383
+ * roots — never an enumerated list (no 1–32 cap). No single code graph spans
4384
+ * them, so none is bound. Takes no `repoRefs`; `projectId` only files the chat.
4385
+ * - `project` — requires `projectId`; takes no `repoRefs` (name `repo` to narrow).
4386
+ * - `repo` — requires `repoRefs`; `repos` is the same kind in its legacy spelling.
4387
+ * - `none` — the legacy unscoped chat; takes neither `projectId` nor `repoRefs`.
4388
+ */
4389
+ scopeKind?: ChatScopeRequestKind;
3919
4390
  }
3920
4391
 
3921
- /** How a chat's scope was chosen (crew#502). */
3922
- export type ChatScopeKind = 'project' | 'repos' | 'none';
4392
+ /** The scope a `POST /chats` caller may name (api-types 0.39.0, studio#323 R4). */
4393
+ export type ChatScopeRequestKind = 'system' | 'everything' | 'project' | 'repo' | 'repos' | 'none';
4394
+
4395
+ /**
4396
+ * How a chat's scope was chosen (crew#502). `system` and `everything` (api-types 0.39.0,
4397
+ * studio#323 R4) come back only for a request that named them (`ChatOpenBody.scopeKind`); a named
4398
+ * `repo` comes back as `repos`, the kind for an explicit repo list.
4399
+ */
4400
+ export type ChatScopeKind = 'system' | 'everything' | 'project' | 'repos' | 'none';
3923
4401
 
3924
4402
  /** One repository a chat's seats may read. */
3925
4403
  export interface ChatScopeRepo {
@@ -3938,7 +4416,7 @@ export interface ChatScope {
3938
4416
  kind: ChatScopeKind;
3939
4417
  /** The project the chat is filed into, when any. */
3940
4418
  projectId?: string;
3941
- /** The repositories in scope (read-only). Empty for `kind: 'none'`. */
4419
+ /** The repositories in scope (read-only). Empty for `kind: 'none'` and `kind: 'system'`. */
3942
4420
  repos: ChatScopeRepo[];
3943
4421
  /** The seats' working directory: the chat's private scratch root under the OS temp dir. */
3944
4422
  cwd: string;
@@ -3984,6 +4462,47 @@ export interface ChatSeatRefusal {
3984
4462
  source?: ChatRefusalSource;
3985
4463
  }
3986
4464
 
4465
+ /**
4466
+ * Single-seat degradation disclosure (crew#641/#650): present on `POST /chats` → 201 and on
4467
+ * `POST /chats/:id/messages` → 202 whenever exactly ONE seat is warm — refusals or not, since a
4468
+ * one-seat chat cannot disagree with itself either way. Named here so callers do not have to reason
4469
+ * about the `refused[]` array. The root cause is tracked as wicked-core#563.
4470
+ */
4471
+ export interface ChatSingleSeatDegradation {
4472
+ degraded: true;
4473
+ /** The one warm seat key (e.g. `"claude"`). */
4474
+ warmed: string;
4475
+ /**
4476
+ * The seats currently ON RECORD as refused, with each reason and source — EVIDENCE, not the
4477
+ * trigger. It describes the daemon's RECORD (what the open, or the last `POST /chats/:id/seats`,
4478
+ * observed); `warmed` describes the roster NOW, and the two are different moments.
4479
+ *
4480
+ * `[]` with `refusalsKnown: true` therefore means **no refusal is on record** — NOT "no seat was
4481
+ * ever refused": a seat re-seated through `POST /chats/:id/seats` is removed from the record when
4482
+ * it warms, and a seat RELEASED mid-session (it blew its turn budget) was never refused at all,
4483
+ * yet either can leave a chat with one warm seat and an empty record. `[]` with `refusalsKnown:
4484
+ * false` means the record is GONE — see that field.
4485
+ */
4486
+ refused: ChatSeatRefusal[];
4487
+ /**
4488
+ * Whether `refused` is the record this daemon actually holds (crew#650, review follow-up). `true`
4489
+ * on every 201 (the daemon just opened the chat) and on a 202 for a chat it still holds. `false`
4490
+ * on a 202 for a chat this daemon did not open — the engine keeps a warm session across a restart,
4491
+ * the daemon's in-memory scope index does not — where `refused: []` means THE RECORD IS GONE, not
4492
+ * that nothing was refused, and `message` says so instead of asserting either way. Absent on a
4493
+ * daemon predating this field, where `[]` was ambiguous.
4494
+ */
4495
+ refusalsKnown?: boolean;
4496
+ /**
4497
+ * Human-readable summary naming the degradation, its cause, and wicked-core#563. It states only
4498
+ * what the answering route witnessed: the 201 performed the open, so it may say the chat was
4499
+ * opened with a single seat; a 202 knows the roster NOW and the refusal record and says nothing
4500
+ * about how the chat came to look this way — the one warm seat may be the survivor of a two-seat
4501
+ * chat whose other seat was released.
4502
+ */
4503
+ message: string;
4504
+ }
4505
+
3987
4506
  /** `POST /chats` → 201. */
3988
4507
  export interface ChatOpenResponse {
3989
4508
  chatId: string;
@@ -3995,6 +4514,33 @@ export interface ChatOpenResponse {
3995
4514
  /** Every seat that was asked for or defaulted and is NOT in `seats` as warm, with its reason
3996
4515
  * (api-types 0.35.0). Empty when every seat warmed; absent on a daemon predating the field. */
3997
4516
  refused?: ChatSeatRefusal[];
4517
+ /**
4518
+ * crew#641/#650: present whenever exactly ONE seat is warm — the chat cannot disagree with
4519
+ * itself, which is equally true when nothing was refused (a chat opened with one seat on
4520
+ * purpose). ABSENT only when two or more seats are warm. Absent on a daemon predating this
4521
+ * field; a daemon between #641 and #650 emitted it only when `refused` was non-empty.
4522
+ */
4523
+ singleSeat?: ChatSingleSeatDegradation;
4524
+ }
4525
+
4526
+ /** `POST /chats/:id/messages` → 202. */
4527
+ export interface ChatMessageResponse {
4528
+ /** The seats the engine delivered the message to (warm seat keys). */
4529
+ seats: string[];
4530
+ /** The turn id stamped on every `chatDelta` / `chatReply` frame for this message. Absent on a
4531
+ * daemon predating the turn index (crew ≥ 0.7.35). */
4532
+ turnId?: string;
4533
+ /**
4534
+ * crew#641/#650: re-stated on every turn when the chat has exactly one WARM seat — decided from
4535
+ * the warm roster, never from the seats this turn reached, and never from whether refusals are
4536
+ * still on record (a restarted daemon has none and the chat is still single-seated). ABSENT when
4537
+ * two or more seats are warm. Absent on a daemon predating this field.
4538
+ *
4539
+ * This is a statement about NOW. It never describes how the chat was opened — the single warm seat
4540
+ * may be the survivor of a multi-seat chat whose other seats were refused, re-seated or released —
4541
+ * and only `POST /chats` → 201, which performed the open, says anything about that moment.
4542
+ */
4543
+ singleSeat?: ChatSingleSeatDegradation;
3998
4544
  }
3999
4545
 
4000
4546
  /**
@@ -4035,6 +4581,31 @@ export interface ChatListResponse {
4035
4581
  chats: ChatSummary[];
4036
4582
  }
4037
4583
 
4584
+ /**
4585
+ * Token/cost burn of ONE seat's chat turn (api-types 0.38.0; DES-L5, crew ≥ 0.7.35 on wicked-core-ts
4586
+ * ≥ 0.7.26) — mirrors the engine's `Usage` (`workflow.rs`) as `chatReply.usage`: `null` on bridges
4587
+ * that emit no usage (pi, agy). `costUsd` is `null` when no price is known — never `0`.
4588
+ */
4589
+ export interface ChatUsage {
4590
+ inputTokens: number;
4591
+ outputTokens: number;
4592
+ cacheReadTokens: number;
4593
+ cacheCreationTokens: number;
4594
+ costUsd: number | null;
4595
+ }
4596
+
4597
+ /**
4598
+ * One record of a chat's persisted transcript (api-types 0.38.0; DES-L5 D-13): the daemon appends
4599
+ * the operator's message and every seat's reply to `<state home>/chats/<id>.jsonl` for the CHAT'S
4600
+ * LIFETIME (dropped on `chatClosed`, cleared at boot) and serves them on `GET /chats/:id.messages`
4601
+ * in append order. `at` is epoch millis; `turnId` is always present (only turn-stamped frames are
4602
+ * persisted). A `seat` record's `ok: false` text names the reason (a budget eviction reads
4603
+ * "exceeded the N s turn budget … target it on your next message to re-seat it").
4604
+ */
4605
+ export type ChatTranscriptRecord =
4606
+ | { at: number; turnId: string; kind: 'user'; text: string; seats: string[] }
4607
+ | { at: number; turnId: string; kind: 'seat'; cliKey: string; text: string; ok: boolean; usage: ChatUsage | null };
4608
+
4038
4609
  /** `GET /chats/:id` → 200. */
4039
4610
  export interface ChatDetailResponse {
4040
4611
  chatId: string;
@@ -4045,6 +4616,12 @@ export interface ChatDetailResponse {
4045
4616
  * the admission copy survives a reload; `null` for a chat this daemon did not open; absent on a
4046
4617
  * daemon predating the field. */
4047
4618
  refused?: ChatSeatRefusal[] | null;
4619
+ /**
4620
+ * The chat's transcript so far (api-types 0.38.0, additive; DES-L5, crew ≥ 0.7.35) — `[]` for a
4621
+ * chat with no persisted turn yet; ABSENT on a daemon predating the field (a rejoin then shows
4622
+ * "session continues" without history). Unbounded — a reply may be up to the engine's cap.
4623
+ */
4624
+ messages?: ChatTranscriptRecord[];
4048
4625
  }
4049
4626
 
4050
4627
  // ── Project code graph (DES-PROJECT-001; the co-located multi-repo graph) ──────
@@ -4632,6 +5209,10 @@ export interface DiagnosticsResponse {
4632
5209
  skills: DiagnosticsSkills;
4633
5210
  /** Whether the engine's governance evidence is LANDING (api-types 0.31.0, crew#495) — see `DiagnosticsGovernance`. */
4634
5211
  governance: DiagnosticsGovernance;
5212
+ /** The state-home classification (additive; wicked-core#411 / wicked-crew#497) — see
5213
+ * `DiagnosticsStateHome`. `null` on a daemon booted without the watch (some tests); ABSENT on a
5214
+ * daemon before this field. */
5215
+ stateHome?: DiagnosticsStateHome | null;
4635
5216
  }
4636
5217
 
4637
5218
  // ── Diagnostics — governance store + dead letters (api-types 0.31.0, crew#495 / F-022) ─────────
@@ -4740,14 +5321,22 @@ export interface DiagnosticsSkillsFinding {
4740
5321
  * crew#535) = the CURRENT generation was published under OTHER portability rules than the daemon
4741
5322
  * now runs — accepted (the snapshot is never rewritten; the runtime stays `published`), the rows
4742
5323
  * that now derive differently are named (`SkillsManifestResponse.current.drift`), and a publish
4743
- * records the running rules and clears it. */
5324
+ * records the running rules and clears it; `skills.base-skill` (crew#554 / wicked-core#468) =
5325
+ * the configured BASE skill (`SystemSettings.baseSkillRef`) is not in the current generation —
5326
+ * a `warning` under `baseSkillPolicy: 'warn'` (runs proceed without the discipline directive),
5327
+ * an `error` under `'require'` (the engine refuses every launch at intake); cleared by a publish
5328
+ * that hands it. Also carried as `DiagnosticsSkills.baseSkill.finding`; `skills.phase-skill`
5329
+ * (`warning`, crew#661) = a subsystem's phases declare a skill the published snapshot did not hold
5330
+ * when it armed, so they run without it — one per `DiagnosticsSkills.phaseSkillGaps` entry. */
4744
5331
  kind:
4745
5332
  | 'skills.fallback'
4746
5333
  | 'skills.blocked'
4747
5334
  | 'skills.config'
4748
5335
  | 'skills.source'
4749
5336
  | 'skills.manifest'
4750
- | 'skills.stale-rules';
5337
+ | 'skills.stale-rules'
5338
+ | 'skills.base-skill'
5339
+ | 'skills.phase-skill';
4751
5340
  severity: 'warning' | 'error';
4752
5341
  message: string;
4753
5342
  }
@@ -4776,6 +5365,173 @@ export interface DiagnosticsSkills {
4776
5365
  * layout. `null` only when `disabled`. */
4777
5366
  stateHome: string | null;
4778
5367
  findings: DiagnosticsSkillsFinding[];
5368
+ /** The BASE skill posture (crew#554) — the System page's "discipline skill" row beside the skills
5369
+ * generation; its `finding` (when the generation lacks the skill) also rides `findings`. `null`
5370
+ * when the setting is off or the seam is `disabled`. A daemon before this field omits the key
5371
+ * (read it as `null`). */
5372
+ baseSkill: BaseSkillPosture | null;
5373
+ /** Subsystems whose phases run WITHOUT a skill their workflow declares (crew#661; additive) — one
5374
+ * entry per subsystem, judged when that seam ARMED (the decision holds until the daemon restarts).
5375
+ * Each entry also rides `findings` as a `skills.phase-skill` warning. `[]` when every armed seam
5376
+ * holds its skill; ABSENT on a daemon before this field (read it as unknown, not as `[]`). */
5377
+ phaseSkillGaps?: DiagnosticsPhaseSkillGap[];
5378
+ }
5379
+
5380
+ /**
5381
+ * One subsystem whose phases declare a skill the published snapshot did not hold when the subsystem
5382
+ * armed (crew#661) — today the interactive seams' `wicked-garden-draft` quality floor. The phases
5383
+ * run DEGRADED (no `skill_ref`, no quality floor) and every run launched on it says so in
5384
+ * `AgentSession.skill_gaps`; the engine would refuse a `skill_ref` its snapshot lacks, so the seam
5385
+ * cannot simply stamp it.
5386
+ */
5387
+ export interface DiagnosticsPhaseSkillGap {
5388
+ /** The subsystem (`interactive-draft` / `interactive-edit` / `interactive-chat`). */
5389
+ subsystem: string;
5390
+ /** The workflow id the subsystem launches. */
5391
+ workflow: string;
5392
+ /** The agent phases that declare the skill and run without it. */
5393
+ phases: string[];
5394
+ /** The declared skill (`wicked-garden-draft`). */
5395
+ skill: string;
5396
+ /** The published skills generation judged at arm time, or `null` when none was published. */
5397
+ gen: number | null;
5398
+ /** Epoch ms of the arm-time judgement. */
5399
+ armedAt: number;
5400
+ /** The fix: republish the skills snapshot from a wicked-garden that carries the skill, then restart. */
5401
+ remedy: string;
5402
+ }
5403
+
5404
+ /** A skill gap a run LAUNCHED with (`AgentSession.skill_gaps[]`, crew#661) — the arm-time gap without its timestamp. */
5405
+ export type RunSkillGap = Omit<DiagnosticsPhaseSkillGap, 'armedAt'>;
5406
+
5407
+ /**
5408
+ * The BASE skill in force for the next launch (crew#554 — the launcher half of wicked-core#468).
5409
+ * The engine leads EVERY agent unit's prompt with one role-keyed directive naming this skill and
5410
+ * the unit's `§<role>` section; a launch whose handed snapshot lacks it is refused at intake. The
5411
+ * daemon judges the setting against the CURRENT published generation and exports
5412
+ * `WICKED_BASE_SKILL_REF` per `policy` (see `SystemSettings.baseSkillPolicy`). Carried by
5413
+ * `GET /health.baseSkill`, `GET /diagnostics.skills.baseSkill`, `POST /skills/publish` and
5414
+ * `POST /skills/refresh-baseline` results, and the 422 `base_skill_refused` launch body.
5415
+ */
5416
+ export interface BaseSkillPosture {
5417
+ /** The frontmatter name (`wicked-garden-governed-worker` by default). */
5418
+ name: string;
5419
+ policy: 'warn' | 'require';
5420
+ /** The published generation the engine is handed holds the skill — the intake admission passes. */
5421
+ present: boolean;
5422
+ /** The editor catalog holds the skill enabled — the NEXT publish hands it. */
5423
+ inCatalog: boolean;
5424
+ /** The generation judged (`current`), or `null` when nothing is published. */
5425
+ gen: number | null;
5426
+ /** What `WICKED_BASE_SKILL_REF` is exported as right now: the name, or `null` = unset (runs proceed without a base skill). */
5427
+ engineInput: string | null;
5428
+ /** The `skills.base-skill` finding when the generation lacks the skill (`warning` under `warn`, `error` under `require`); `null` when it holds it. */
5429
+ finding: DiagnosticsSkillsFinding | null;
5430
+ }
5431
+
5432
+ /**
5433
+ * `POST /runs` **422** (crew#554): the engine refused the launch AT INTAKE because the handed skills
5434
+ * snapshot lacks the run's base skill (`SkillsError::BaseSkillRefused` — `baseSkillPolicy:
5435
+ * 'require'`, or a workflow def's own `base_skill_ref`). Nothing was planned or persisted; no run
5436
+ * id exists. `error` is the engine's message naming the skill; `baseSkill` is the daemon's posture
5437
+ * at refusal time; `remedy` names the fix (publish a generation holding it, or relax the policy).
5438
+ */
5439
+ export interface BaseSkillRefusedResponse {
5440
+ code: 'base_skill_refused';
5441
+ error: string;
5442
+ baseSkill: BaseSkillPosture | null;
5443
+ remedy: string;
5444
+ }
5445
+
5446
+ // ── Diagnostics — the state-home classification (additive; wicked-core#411 / wicked-crew#497) ───
5447
+ //
5448
+ // The worker Read fence over the daemon state home is an EXPLICIT registry (design v3.1 §1): an
5449
+ // entry it does not classify refuses the launch by name, fail closed. Right — but it used to fire
5450
+ // at the run's FIRST WORKER, after a planning council and the intake gate, labelled "triage judge
5451
+ // errored", while the daemon booted green (F-RC1-011, F-RC2-020, F-032/F-033). Now the daemon
5452
+ // surveys the state home at boot and on every read of this block, logs one error line per entry
5453
+ // (`recentErrors`), warns on `GET /health`, and answers `POST /runs` 409 `state_home_unregistered`
5454
+ // while a handed snapshot derives a state home with such an entry; the engine refuses at intake.
5455
+
5456
+ /** One entry the state-home registry cannot classify. */
5457
+ export interface DiagnosticsStateHomeEntry {
5458
+ /** The entry's name as listed. */
5459
+ name: string;
5460
+ /** Its absolute path on the daemon host. */
5461
+ path: string;
5462
+ /** `state-home` — a top-level entry; `skills-root` — a child of `<state home>/skills` that is
5463
+ * neither the read slot (`snapshots`) nor a registered denied child. */
5464
+ level: 'state-home' | 'skills-root';
5465
+ }
5466
+
5467
+ /** One finding of the state-home survey — the same text `GET /health.warnings[]` carries. */
5468
+ export interface DiagnosticsStateHomeFinding {
5469
+ kind: 'state-home.unregistered';
5470
+ severity: 'error';
5471
+ message: string;
5472
+ }
5473
+
5474
+ /**
5475
+ * `GET /diagnostics` → `stateHome`: which state home the worker Read fence classifies, every entry
5476
+ * it cannot classify there, who classified, and whether that refuses launches. Honest throughout:
5477
+ * a field the daemon cannot answer is `null`, never invented.
5478
+ */
5479
+ export interface DiagnosticsStateHome {
5480
+ /** The state home surveyed, or `null` when neither input yields one. */
5481
+ stateHome: string | null;
5482
+ /** `snapshot` — derived from the handed `WICKED_SKILLS_SNAPSHOT` by its
5483
+ * `<state home>/skills/snapshots/<gen>` shape (the directory the fence classifies); `db` — the
5484
+ * core db's parent, surveyed for information when no snapshot is handed. */
5485
+ derivedFrom: 'snapshot' | 'db' | null;
5486
+ /** Who classified: the engine (`Core.preflightStateHome`, the fence's own code), crew's registry
5487
+ * copy on an addon without it, or nobody (`unavailable`: no db path and no snapshot). */
5488
+ source: 'engine' | 'crew' | 'unavailable';
5489
+ unregistered: DiagnosticsStateHomeEntry[];
5490
+ /** `true` exactly when a HANDED snapshot derives a state home with unregistered entries — the
5491
+ * condition the engine refuses every launch on, and what `POST /runs` answers 409 for. */
5492
+ refusesLaunches: boolean;
5493
+ findings: DiagnosticsStateHomeFinding[];
5494
+ /** What to do, in operator terms — the same text the 409 body and every finding end with. */
5495
+ remedy: string;
5496
+ /** Why the survey could not run (an unresolvable snapshot, an unlistable directory), or `null`. */
5497
+ error: string | null;
5498
+ /** When this survey was taken (unix ms). */
5499
+ checkedAt: number;
5500
+ }
5501
+
5502
+ /**
5503
+ * The `POST /runs` **409** body while the state home refuses launches (`code:
5504
+ * 'state_home_unregistered'`): the configuration error as fields — every entry and the remedy —
5505
+ * so a composer can show the blocker instead of a bare string.
5506
+ */
5507
+ export interface StateHomeBlockerBody {
5508
+ error: string;
5509
+ code: 'state_home_unregistered';
5510
+ stateHome: string | null;
5511
+ unregistered: DiagnosticsStateHomeEntry[];
5512
+ remedy: string;
5513
+ }
5514
+
5515
+ /**
5516
+ * The `POST /runs` **409** body when the engine refuses the launch at intake because the roster has
5517
+ * NO eligible seat (`code: 'no_eligible_seat'`; wicked-core#461's typed `NoEligibleSeat`, crew#556):
5518
+ * the plan needs a seat and every configured seat was benched by the launcher — signed out, out of
5519
+ * quota, not installed (crew's `council_eligible: false` → the engine's `health.usable: false`).
5520
+ * Nothing was persisted, nothing reached the wire. A 409, not a 400: the body is well-formed and the
5521
+ * same request succeeds once a seat is signed in — the conflict is with the roster's current
5522
+ * standing (the `state_home_unregistered` rule), and not a 5xx: the engine is healthy and said no.
5523
+ */
5524
+ export interface NoEligibleSeatBody {
5525
+ /** The engine's own text: `no eligible seat for <run>: <benched> — sign a seat in, or add one, …`. */
5526
+ error: string;
5527
+ code: 'no_eligible_seat';
5528
+ /** The refused run id (never persisted). */
5529
+ runId: string;
5530
+ /** The bench, seat by seat with its cause — `"2 of 2 seats benched: codex (signed out — launcher),
5531
+ * copilot (quota exhausted — launcher)"` — the engine's `benched_summary`, verbatim. */
5532
+ benched: string;
5533
+ /** What to do, in operator terms. */
5534
+ remedy: string;
4779
5535
  }
4780
5536
 
4781
5537
  // ── Memory proposal queue (DES-MEM-FACETED-001 §5.0, api-types 0.21.0) ─────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-crew-api-types",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "The wire contract of the wicked-crew daemon's /api/v1 REST surface and /ws CoreEvent frames \u2014 types only, zero runtime",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",