wicked-crew-api-types 0.39.0 → 0.69.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 +2397 -70
  2. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -84,7 +84,25 @@ export interface AuditEntry {
84
84
  [k: string]: unknown;
85
85
  }
86
86
 
87
- /** `GET /audit` — newest first, filterable by `?runId=` / `?action=` / `?limit=`. */
87
+ /**
88
+ * `GET /audit`'s query (api-types 0.43.0 adds `since`). Every filter is optional and they
89
+ * compose (AND); an empty value is absent. A malformed `limit` or `since` answers 400.
90
+ */
91
+ export interface AuditQuery {
92
+ runId?: string;
93
+ action?: string;
94
+ /** Max entries returned, newest first. Default 200, capped at 1000. */
95
+ limit?: number;
96
+ /**
97
+ * Inclusive lower bound on {@link AuditEntry.ts} (unix MILLIS): keep entries with
98
+ * `ts >= since`. Applied before `limit`. The "while you were away" read: a skin passes the
99
+ * operator's last-visit instant and gets what happened since, including `system` actors'
100
+ * actions (the stall watchdog's `run.stall.*` entries).
101
+ */
102
+ since?: number;
103
+ }
104
+
105
+ /** `GET /audit` — newest first, filterable by {@link AuditQuery} (`?runId=` / `?action=` / `?limit=` / `?since=`). */
88
106
  export interface AuditPage {
89
107
  entries: AuditEntry[];
90
108
  }
@@ -111,7 +129,15 @@ export type EntityMode = 'shared' | 'isolated';
111
129
  /** Human-confirm gate policy. serde: `None`→"none", `All`→"all", `Before(n)`→{ before: n }. */
112
130
  export type HumanConfirm = 'none' | 'all' | { before: number };
113
131
 
114
- /** Why a CLI was assigned to a unit (`RoutingInfo`, internally tagged on `method`). */
132
+ /**
133
+ * Why a CLI was assigned to a unit (`RoutingInfo`, internally tagged on `method`).
134
+ *
135
+ * `teamed` (wicked-core#590 S5) is what every seated unit of a new run carries: the engine no
136
+ * longer convenes a council to route, it picks the first eligible seat the unit's skills admit, in
137
+ * roster order (`winner`), and the evaluator ≠ creator fence then moves a review/test unit off a
138
+ * builder seat (`evaluator_distinct`, whose `was` is the teamed pick). `council` and `degraded` are
139
+ * no longer produced; they stay because a recorded run's units carry them.
140
+ */
115
141
  export type RoutingInfo =
116
142
  /**
117
143
  * `seated` is the seats CONVENED — the denominator `returned` must be read against.
@@ -135,7 +161,8 @@ export type RoutingInfo =
135
161
  }
136
162
  | { method: 'degraded'; reason: string }
137
163
  | { method: 'evaluator_distinct'; winner: string; was: string }
138
- | { method: 'tool' };
164
+ | { method: 'tool' }
165
+ | { method: 'teamed'; winner: string };
139
166
 
140
167
  /**
141
168
  * `GET /health` (api-types 0.37.0 — the shape was previously undeclared here; `status`,
@@ -157,6 +184,8 @@ export type RoutingInfo =
157
184
  export interface HealthResponse {
158
185
  status: string;
159
186
  version: string;
187
+ /** The engine actor's answer (`"ok"`), or `"busy"` when the actor did not answer within ~1 s
188
+ * (crew#471): the daemon serves while the engine is occupied — a large launch is planning. */
160
189
  ping: string;
161
190
  capabilities?: HealthCapabilities;
162
191
  /**
@@ -280,12 +309,18 @@ export interface AgentSession {
280
309
  *
281
310
  * - `'delivered'` — a PR was opened for this run (by the deliver phase, or post-hoc via
282
311
  * `POST /runs/:id/deliver`); `deliverUrl` carries the PR URL.
312
+ * - `'pushed'` — (api-types 0.69.0, N1) the run's branch was PUSHED and that is the whole
313
+ * delivery: no pull request could be opened because the origin is not a GitHub host gh can
314
+ * resolve (a local path, SSH, GitLab, ADO, Gitea…). `deliverBranch` + `deliverRemote` say
315
+ * what is where. Nothing is left to lift, so no post-hoc deliver is offered (`POST
316
+ * /runs/:id/deliver` answers 409); opening the pull/merge request is the forge's, by hand.
283
317
  * - `'stranded'` — reviewable work nobody lifted, reached two ways: a COMPLETED repo-scoped
284
318
  * run with no recorded PR whose worktree still exists on disk AND carries work (derived
285
319
  * honestly for OLD runs too — records written before this field existed strand the same
286
- * way); OR (crew#418) a run whose deliver phase hit a LIFT collision — a rebase conflict or
287
- * non-fast-forward push — which the daemon reinterprets from the engine's `failed` to
288
- * `completed`+`stranded`: its work is committed on the `wicked/<id>` branch (the engine
320
+ * way); OR (crew#418) a run whose deliver phase hit a LIFT collision — a rebase conflict
321
+ * (a push the remote REFUSED no longer strands: since 0.69.0 it parks the run at the
322
+ * engine's deliver-refusal gate, N4) — which the daemon reinterprets from the engine's
323
+ * `failed` to `completed`+`stranded`: its work is committed on the `wicked/<id>` branch (the engine
289
324
  * reaps the now-clean worktree, but never the branch), recoverable via `POST /runs/:id/
290
325
  * deliver`, which stands a throwaway worktree back up from that branch to lift it.
291
326
  * - `'vacuous'` — a COMPLETED repo-scoped run whose surviving worktree carries NO
@@ -308,7 +343,7 @@ export interface AgentSession {
308
343
  * FAILURE of a deliver phase is not spelled here; it is `units[].status === 'rejected'`
309
344
  * plus `denial_reason` (crew#318's message), both already on the list wire.
310
345
  */
311
- delivery?: 'delivered' | 'stranded' | 'vacuous' | 'none';
346
+ delivery?: 'delivered' | 'pushed' | 'stranded' | 'vacuous' | 'none';
312
347
  /**
313
348
  * The delivered PR's URL (crew#393; api-types 0.18.0) — present exactly when
314
349
  * `delivery === 'delivered'`; absent otherwise (absence is the one spelling, never `null`).
@@ -319,6 +354,13 @@ export interface AgentSession {
319
354
  * idempotent against the existing branch) and `'none'` after it is cleaned up.
320
355
  */
321
356
  deliverUrl?: string;
357
+ /** The pushed run branch (api-types 0.69.0, N1) — present exactly when `delivery === 'pushed'`. */
358
+ deliverBranch?: string;
359
+ /**
360
+ * The remote the branch was pushed to (api-types 0.69.0, N1), with any URL userinfo removed —
361
+ * present exactly when `delivery === 'pushed'`. May be a filesystem path (a local bare origin).
362
+ */
363
+ deliverRemote?: string;
322
364
  /**
323
365
  * The campaign this AD-HOC run was attached to at launch (`LaunchRunBody.campaignId`;
324
366
  * wicked-studio#27, api-types 0.19.0) — daemon-joined at DTO assembly on `GET /runs` and
@@ -410,6 +452,15 @@ export interface AgentSession {
410
452
  /** Wave 6 (F-7R2-006): the seats BENCHED for this run — never convened, never a failover or
411
453
  * judge target. Absent (never `[]`) when none / on an older engine. */
412
454
  benched_seats?: BenchedSeat[];
455
+ /**
456
+ * The run's APPROVED INTENT AMENDMENTS, in the order a human decided them (wicked-core#555;
457
+ * additive, engine ≥ the core-ts release after 0.7.30) — the `amend_intent` arm of a gate
458
+ * ({@link GateDecision.action}). Each amendment is appended to every unit at or after the
459
+ * gate's cursor, so the acceptance list each LATER EVALUATOR is handed changed with the
460
+ * decision. Render them beside the run's original intent: this is what was withdrawn or
461
+ * changed, and when. ABSENT (never `[]`) on an unamended run and on an older engine.
462
+ */
463
+ intent_amendments?: IntentAmendment[];
413
464
  /** crew#661: the declared skills this run's phases ran WITHOUT (the published skills snapshot did not
414
465
  * hold them when the launching subsystem armed) — the run proceeded DEGRADED, and says so here.
415
466
  * ABSENT (never `[]`) when the run launched fully armed, and on a daemon before this field. */
@@ -441,6 +492,78 @@ export interface AgentSession {
441
492
  * this field.
442
493
  */
443
494
  chat_grounded?: boolean;
495
+ /**
496
+ * The engine's plan state (wicked-core `TeamPlanState`, DES-TEAMING-002 §8.4-§8.6; api-types
497
+ * 0.46.0 types the engine field) — present on a run launched from a PLAN or a PRESET, ABSENT on
498
+ * every other run (a registered def, the free-text planner) and on a team run before its launch
499
+ * is scored. Engine-computed; typed here only as far as a skin reads it. Read the run's kind from
500
+ * {@link AgentSession.run_identity}, which is resolved from this.
501
+ */
502
+ team_plan?: TeamPlanState;
503
+ /**
504
+ * What this run IS (seam X2; api-types 0.46.0) — daemon-resolved at DTO assembly on BOTH
505
+ * `GET /runs` and `GET /runs/:id` from the engine's own record (the plan state above, else the
506
+ * launch's recorded `sessionStarted`), never from the run's phase sequence. Always present on a
507
+ * 0.46.0+ daemon; ABSENT from an older one. `system` replaces a skin's own list of system
508
+ * workflow ids.
509
+ */
510
+ run_identity?: RunIdentity;
511
+ }
512
+
513
+ /**
514
+ * The fields of the engine's `TeamPlanState` a skin reads (the engine sends more — every field is
515
+ * engine-computed and additive).
516
+ */
517
+ export interface TeamPlanState {
518
+ /** The last plan rev assigned (accepted or held). `0` = none yet. */
519
+ rev: number;
520
+ /** The last ACCEPTED plan rev. `0` = no plan accepted yet. */
521
+ accepted_rev: number;
522
+ /** The preset the launch named. ABSENT on a user plan. */
523
+ preset?: string;
524
+ /**
525
+ * (wicked-core#633, DES-TEAMING-002 X1; api-types 0.47.0) PRESENT while the run's PA is scoping
526
+ * its launch plan — a creator plan or preset that declared no `touch` set. Until the `pa-scope`
527
+ * unit's boundary decides the plan, the run's only unit is `pa-scope` (plan rev 1): what the run
528
+ * will contain is not known yet. ABSENT once the plan is scoped (or when none needed scoping).
529
+ */
530
+ scope?: TeamPlanScopeHold;
531
+ }
532
+
533
+ /** The launch plan the PA is scoping (the engine's `ScopeHold`). */
534
+ export interface TeamPlanScopeHold {
535
+ /** The launch plan as authored (the preset's steps or the user's plan), without `pa-scope`. */
536
+ plan: { steps: TeamPlanStep[] } & Record<string, unknown>;
537
+ /** The run has no repo: the PA rates the risk (`RISK`) instead of declaring a touch set. */
538
+ unbound: boolean;
539
+ /** The scope step's `SCOPE` / `RISK` lines once its turn folds; ABSENT until then. */
540
+ answer?: { ord: number; attempt: number; by: string; lines: string };
541
+ }
542
+
543
+ /**
544
+ * What a run is (seam X2; api-types 0.46.0), resolved by the daemon from the engine's record.
545
+ *
546
+ * - `'preset'` — launched from a preset; `name` is the preset.
547
+ * - `'user_plan'` — launched from a user-composed plan (the engine's per-run `<run>:plan-<rev>`
548
+ * def); `name` is `null`.
549
+ * - `'workflow'` — launched on a registered workflow def; `name` is its id (a crew-composed
550
+ * per-run delivery/floor def reads as its base).
551
+ * - `'free_text'` — no workflow and no plan: the free-text planner; `name` is `null`.
552
+ * - `'unknown'` — the daemon has no record of what the launch named (a run predating the
553
+ * engine's event log, or an engine without the log read); `name` is `null`.
554
+ */
555
+ export interface RunIdentity {
556
+ kind: 'preset' | 'user_plan' | 'workflow' | 'free_text' | 'unknown';
557
+ /** The preset or workflow name — `null` unless `kind` is `'preset'` or `'workflow'`. */
558
+ name: string | null;
559
+ /** `kind === 'user_plan'`, spelled out. */
560
+ user_plan: boolean;
561
+ /**
562
+ * A machine-owned run (chat, onboarding, the interactive document and video seams, …): skins keep
563
+ * it off delivery surfaces. Keyed by `name` against the daemon's ONE list — the same list that
564
+ * stamps `WorkflowDef.is_system` on `GET /workflows`.
565
+ */
566
+ system: boolean;
444
567
  }
445
568
 
446
569
  /** An ordered unit of work within a run (`WorkUnit`). */
@@ -485,6 +608,34 @@ export interface WorkUnit {
485
608
  * planned before the field existed, and on an older engine.
486
609
  */
487
610
  base_skill_ref?: string | null;
611
+ /**
612
+ * The phase-catalog entry this unit instantiates (`build`, `review`, …) when it was planned from
613
+ * a catalog-composed plan — a preset or a user plan (wicked-core `WorkUnit.catalog`,
614
+ * DES-TEAMING-002 T3; api-types 0.46.0 types the engine field). ABSENT on every other unit.
615
+ */
616
+ catalog?: string;
617
+ /** TRUE on a unit of a TEAM run (planned from the per-run plan def); ABSENT otherwise. Engine field. */
618
+ team_run?: boolean;
619
+ /**
620
+ * TRUE when this unit's phase declared `requires_capture_report` (wicked-core#535 / BC-80) —
621
+ * carried from the def at plan time. ABSENT on every other unit and on an older engine.
622
+ */
623
+ requires_capture_report?: boolean;
624
+ /**
625
+ * What the capture phase REPORTED, read from its output marker by the gate fold
626
+ * (wicked-core#535 / BC-80): `derived` learnings, `submitted` proposals, `failed` submissions.
627
+ * Present once a capture unit emitted a well-formed marker — whether it passed or denied, so the
628
+ * counts are readable beside the denial. ABSENT on every other unit, on a capture unit whose
629
+ * marker was MISSING (that denies; the denial is the record), and on an older engine.
630
+ */
631
+ capture_report?: CaptureReport;
632
+ }
633
+
634
+ /** The counts a capture phase reported (`WorkUnit.capture_report`; wicked-core#535). */
635
+ export interface CaptureReport {
636
+ derived: number;
637
+ submitted: number;
638
+ failed: number;
488
639
  }
489
640
 
490
641
  /** A run plus its ordered units (`SessionView`) — the shape `GET /runs` returns. */
@@ -617,9 +768,29 @@ export interface RosterSeat {
617
768
  * while every ballot failed. Absent = the probe decided (and since 0.36.0 the probe itself needs
618
769
  * a credential-SHAPED file, never mere presence).
619
770
  */
620
- auth_source?: 'seat-stderr';
621
- /** Present with `auth_source: 'seat-stderr'`: the seat's own words, bounded. */
771
+ auth_source?: 'seat-stderr' | 'probe';
772
+ /** Present with `auth_source: 'seat-stderr'`, and with `probe` when it read signed out: the
773
+ * seat's own words, bounded. */
622
774
  auth_evidence?: string;
775
+ /**
776
+ * Present with `auth_source: 'probe'` (crew#630): ISO-8601 of the seat's own auth-status check
777
+ * (`claude auth status`, `codex login status`) under the seat's configuration home, which then
778
+ * decided `auth` and `signed_in` over the credential file. The daemon re-checks in the
779
+ * background (a signed-in answer after 5 minutes, any other after 30 s). A probed seat whose
780
+ * check has not answered yet reads `unknown`, never `signed_in` off its file.
781
+ */
782
+ probed_at?: string;
783
+ /**
784
+ * How far `auth` was VERIFIED (crew#645): `live` — the seat answered an authenticated request, or
785
+ * refused one for want of a credential (a status command reads the stored login, so an expired
786
+ * OAuth login still reads signed in there; the daemon makes one short live request when it says
787
+ * signed in); `status` — only the status command answered (the live request could not tell);
788
+ * `unverified` — nothing asked the seat (it has no status command, or its check has not answered
789
+ * or could not tell). Optional on the wire: a daemon before it sends none.
790
+ */
791
+ login_check?: 'live' | 'status' | 'unverified';
792
+ /** Present unless `login_check` is `live`: why, in the operator's words ("login unverified — …"). */
793
+ login_note?: string;
623
794
  /** Present when `auth` is `not_required`: the free tier the seat answers on. */
624
795
  free_tier?: string;
625
796
  /**
@@ -650,9 +821,33 @@ export interface RosterSeat {
650
821
  * a reader must tolerate absence today.
651
822
  */
652
823
  council_bench?: RosterSeatCouncilBench;
824
+ /**
825
+ * (core#581) Present on a seat whose ACP config pins `verified_version`, once the daemon's
826
+ * probe has answered: the build the seat's input-governance admission was proven against and
827
+ * what the binary reports now. `matched: false` with `governanceClaimed: true` means the engine
828
+ * fails the seat's ACP governance closed at every spawn.
829
+ */
830
+ version_pin?: SeatVersionPin;
653
831
  [k: string]: unknown;
654
832
  }
655
833
 
834
+ /**
835
+ * One pinned seat's `--version` reading (core#581): `RosterSeat.version_pin` and
836
+ * `AcpCliDiagnostics.versionPin`. Read the way the engine's spawn-time check reads it: the pin
837
+ * matches only the first line of `--version`, trimmed; `observed` is the first non-empty line,
838
+ * `null` when the binary did not answer.
839
+ */
840
+ export interface SeatVersionPin {
841
+ cli: string;
842
+ binary: string;
843
+ pinned: string;
844
+ observed: string | null;
845
+ matched: boolean;
846
+ governanceClaimed: boolean;
847
+ /** The operator-facing reason when a governed seat's pin does not hold; else `null`. */
848
+ disclosure: string | null;
849
+ }
850
+
656
851
  /** `RosterSeat.council_bench` (api-types 0.35.0).
657
852
  * @deprecated ABSENT from crew 0.7.36 (see {@link RosterSeat.council_bench}); removed one minor later. */
658
853
  export interface RosterSeatCouncilBench {
@@ -670,6 +865,62 @@ export interface RosterSeatCouncilBench {
670
865
  window_ms: number;
671
866
  }
672
867
 
868
+ /**
869
+ * `GET /roster/record?days=N` (api-types 0.52.0): each seat's record over the last N days (default 7,
870
+ * max 30), folded from the runs' durable event logs (studio's weekly 1:1 per agent). Nothing new is
871
+ * stored; the window is by event time. A unit's seat is `unitDistributed.cli` (moved by
872
+ * `unitReassigned.newCli`), else the unit row's `assigned_cli`. 503 when the engine has no event-log
873
+ * read binding (a missing binding is not an empty week).
874
+ */
875
+ export interface SeatRecordResponse {
876
+ days: number;
877
+ /** Window start, unix MILLIS. */
878
+ since: number;
879
+ /** Window end (the read time), unix MILLIS. */
880
+ until: number;
881
+ /** Runs whose event log was read. */
882
+ runsRead: number;
883
+ /** True when the daemon's per-read run cap cut the read (the oldest runs in the window were skipped). */
884
+ truncated: boolean;
885
+ /** Seats with any record in the window, by key. A roster seat absent here had no units and no bench. */
886
+ seats: SeatRecord[];
887
+ }
888
+
889
+ /** One seat's week (`SeatRecordResponse.seats[]`). */
890
+ export interface SeatRecord {
891
+ /** The roster key. */
892
+ cli: string;
893
+ /** Distinct units the seat was dispatched on in the window. */
894
+ units: number;
895
+ /** Units of this seat with a gate evaluation in the window. */
896
+ gated: number;
897
+ /** Of `gated`: the FIRST evaluation passed (`gateEvaluated.combined` and no `denial`). */
898
+ firstPass: number;
899
+ /** Units the seat ran first that were dispatched again (a retry or a send-back) or given a `unitReworkAmended`. */
900
+ rework: number;
901
+ /** Distinct units that went quiet (`workerStalled`, engine or daemon watchdog) while the seat ran them. */
902
+ stalls: number;
903
+ /** Runs in the window whose `benched_seats` names the seat. */
904
+ benched: number;
905
+ /** Bench reason → runs. */
906
+ benchReasons: Record<string, number>;
907
+ /** Summed `cliUsage.costUsd` where a price was known; `null` when no usage frame carried one (never `0` for "unknown"). */
908
+ costUsd: number | null;
909
+ /** Usage frames that carried a price. */
910
+ costedUsage: number;
911
+ /** Phase (`phase_ref`, else `stage`) → the same counts, so a move can name the phase. */
912
+ byPhase: Record<string, SeatPhaseRecord>;
913
+ }
914
+
915
+ /** One phase's slice of a {@link SeatRecord}. */
916
+ export interface SeatPhaseRecord {
917
+ units: number;
918
+ gated: number;
919
+ firstPass: number;
920
+ rework: number;
921
+ stalls: number;
922
+ }
923
+
673
924
  /** A seat's auth reading (`RosterSeat.auth`; api-types 0.35.0). */
674
925
  export type SeatAuth = 'signed_in' | 'signed_out' | 'not_required' | 'unknown';
675
926
 
@@ -765,8 +1016,39 @@ export interface GateDecision {
765
1016
  * `unitDispatched{attempt: last + 1}`); requires `approve: false` — a disagreement between
766
1017
  * `action` and `approve` answers 400, a gate with no creator phase before it 409. An older
767
1018
  * daemon's strict schema rejects the key with a 400 — omit it against such servers.
1019
+ *
1020
+ * The ESCALATION arms (additive; wicked-core#469 / #467, engine ≥ the core-ts release after
1021
+ * 0.7.30): all require `approve: true` and take no `amend` / `amendScope` / `plan` (a 400
1022
+ * otherwise). `'extend'` | `'targeted'` | `'accept_partial'` answer the escalation gate of a
1023
+ * repo-checks floor that did not finish (`gateEscalated.denialSource: 'repo_checks_timeout'`):
1024
+ * the engine re-runs the floor on the tree as it stands — the seat does not run again — with
1025
+ * every check under 2× its bound, with the repo's declared `test_targeted` in place of the full
1026
+ * test set (no targeted command ⇒ the test set is waived), or with the checks that did not
1027
+ * finish waived; the re-run goes through the ordinary gate and waived checks are named in
1028
+ * `gateEvaluated.floorNote`. `'accept_suggestion'` answers an evaluator's worktree-guard denial
1029
+ * whose edit was restored and pinned (`gateEscalated.suggestionRef`): the engine applies the
1030
+ * pinned edit and rewinds to the creator (`unitReworkAmended{scope: 'accept_suggestion'}`).
1031
+ * The engine refuses each at any other gate — a 409 — and the gate stays open.
1032
+ *
1033
+ * `'amend_intent'` (additive; wicked-core#555, engine ≥ the core-ts release after 0.7.30)
1034
+ * approves the gate AND AMENDS THE RUN'S INTENT: `amend` carries what the acceptance list now
1035
+ * says (required, non-empty), `approve: true`, and no `amendScope` — the scope IS every unit at
1036
+ * or after the cursor, which is what makes it reach the LATER EVALUATOR. Before it, nothing
1037
+ * amended the list an evaluator is handed, so a mid-run descope could only end in a relaunch.
1038
+ * The engine appends the text to those units' descriptions, records it on the run
1039
+ * (`AgentSession.intent_amendments`) and emits `intentAmended`. Refused at a plan or team gate,
1040
+ * and with empty text — a 409 with the gate left open.
768
1041
  */
769
- action?: 'approve' | 'request_changes' | 'reject';
1042
+ action?:
1043
+ | 'approve'
1044
+ | 'request_changes'
1045
+ | 'reject'
1046
+ | 'edit_plan'
1047
+ | 'extend'
1048
+ | 'targeted'
1049
+ | 'accept_partial'
1050
+ | 'accept_suggestion'
1051
+ | 'amend_intent';
770
1052
  /**
771
1053
  * Where an approve's `amend` lands (api-types 0.38.0, additive; same release as `action`).
772
1054
  * Absent = `'cursor'` (today: the gated unit's description). `'creator'` — the first creator
@@ -775,6 +1057,38 @@ export interface GateDecision {
775
1057
  * Identical text already present on the target is not appended twice.
776
1058
  */
777
1059
  amendScope?: 'cursor' | 'creator';
1060
+ /**
1061
+ * Approve a `plan_approval` gate WITH AN EDIT (DES-TEAMING-002 §8.6, seam T3; additive; needs an
1062
+ * engine whose addon carries `Core.supportsPlanLaunch`). The edited plan: the engine publishes
1063
+ * `plan.proposed{by:"human", kind:"edit"}`, floor-fills it (phases below the floor are ADDED,
1064
+ * never refused) and accepts it as the next `plan_rev` — or refuses it (`plan.refused`) and
1065
+ * re-opens the gate under a new `gate_id`, answering `awaiting_human`. Requires `approve: true`
1066
+ * and `action` omitted or `'edit_plan'`; `amend` / `amendScope` do not apply. A plain approve
1067
+ * (no `plan`) releases the held plan as it is; `approve: false` rejects (cancels).
1068
+ */
1069
+ plan?: LaunchPlan;
1070
+ /**
1071
+ * The gate this decision answers — the unit `ord` its open gate paused before, as `GET
1072
+ * /runs/:id/gate` served it (api-types 0.44.0, additive). When present the daemon compares it to
1073
+ * the run's open gate and answers **409** `{ error, code: 'gate_changed', openOrd }` on a
1074
+ * mismatch, confirming nothing: the gate was answered elsewhere or replaced while the decision
1075
+ * waited (a skin's undo window). When the daemon cannot tell which gate is open it answers
1076
+ * **409** `{ error, code: 'gate_unknown' }` — refresh the gate and decide again. Absent = today's
1077
+ * behaviour (no gate check).
1078
+ */
1079
+ ord?: number;
1080
+ }
1081
+
1082
+ /**
1083
+ * A USER-COMPOSED plan (DES-TEAMING-002 §8.4, seam T3): ordered `steps[]` over the phase catalog,
1084
+ * the predicted `touch` set (the intent score's input: a plan with a creator step and no `touch`
1085
+ * scores 100, "no declared scope") and an optional MANUAL-mode floor `override` (refused in auto
1086
+ * mode). A step's `id` defaults to its catalog id. The ENGINE validates every key.
1087
+ */
1088
+ export interface LaunchPlan {
1089
+ steps: Array<{ catalog: string; id?: string | undefined; [k: string]: unknown }>;
1090
+ touch?: string[];
1091
+ override?: TeamPlanOverride;
778
1092
  }
779
1093
 
780
1094
  /**
@@ -1012,7 +1326,9 @@ export interface CoreEvent {
1012
1326
  library?: string;
1013
1327
  transform?: string;
1014
1328
  known?: boolean;
1015
- detail?: string;
1329
+ /** `adviceDelivered` (wicked-core#602, api-types 0.40.0) spells an absent detail `null`, never an
1330
+ * absent key — widened to admit that `null`; every other producer still sends a string. */
1331
+ detail?: string | null;
1016
1332
  [k: string]: unknown;
1017
1333
  }
1018
1334
 
@@ -1089,6 +1405,22 @@ export type WorkerStallEscalatedFrame = {
1089
1405
  escalations?: number;
1090
1406
  /** outcome `failed`: bounded excerpt of what the recovery call threw. */
1091
1407
  error?: string;
1408
+ /**
1409
+ * Why the watchdog did not do a plain reassign (crew#638 / #581 / #580; additive, absent on older
1410
+ * daemons): `tool_unit` — the cursor is the engine's own command, nothing to fail over to;
1411
+ * `evaluating` — the worker returned and its gate evaluation is in flight, a reassign would start
1412
+ * a second creator; `evaluator_distinct` — every other seat is one evaluator ≠ creator forbids
1413
+ * (see `avoided`), so the unit was left for a human; `no_output` (outcome `exhausted`) — the
1414
+ * reassigned seat produced nothing past a startup banner.
1415
+ */
1416
+ reason?: 'tool_unit' | 'evaluating' | 'evaluator_distinct' | 'no_output';
1417
+ /**
1418
+ * The seats the failover pick excluded under evaluator ≠ creator (crew#638; additive): a
1419
+ * creator cursor avoids the run's evaluator seats, an evaluator cursor the seats that built its
1420
+ * work, an `evaluator_distinct`-routed unit the seat it was routed away from. Present on an `ok`
1421
+ * reassign that skipped one and on a `reason: 'evaluator_distinct'` notify; absent otherwise.
1422
+ */
1423
+ avoided?: string[];
1092
1424
  };
1093
1425
 
1094
1426
  export interface RecordedEvent extends CoreEvent {
@@ -1900,6 +2232,58 @@ export type WorkerToolCallDeniedEvent = {
1900
2232
  remedy: string;
1901
2233
  };
1902
2234
 
2235
+ /**
2236
+ * DES-TEAMING-001 §5.2 / §7 (wicked-core#602, S3; api-types 0.40.0) — what became of HIGH monitor
2237
+ * advice for one attempt of a unit. The one mid-turn carrier is the ACP adapter's
2238
+ * `_session/steering` request, always sent with `idleBehavior: "promptRequired"` so a steer that
2239
+ * lands after the turn settled never starts a detached turn.
2240
+ *
2241
+ * - `carrier: 'acp_steering'` + `outcome: 'injected'` — the steer reached the running turn.
2242
+ * - `outcome: 'turn_ended'` — the adapter answered `promptRequired` (the turn had settled; nothing
2243
+ * was started), or the turn ended with the steer unanswered. The advice goes to the gate.
2244
+ * - `outcome: 'refused'` — the adapter answered a JSON-RPC error (in `detail`); the turn continues.
2245
+ * - `outcome: 'not_delivered'` — the advice never rode a steer: `carrier: 'none'` when the unit's
2246
+ * carrier has no mid-turn channel (wrapped, PTY, an ACP adapter not advertising steering), or
2247
+ * `'acp_steering'` when the turn ended before the next tool-call boundary. `detail` says which.
2248
+ *
2249
+ * Every key is always present; `detail` is `null` when there is nothing to add. Spelled exactly as
2250
+ * wicked-core's `CoreEvent::to_json` emits it. `type` alias on purpose (relays through the
2251
+ * `CoreEvent`-typed seams unchanged).
2252
+ */
2253
+ export type AdviceDeliveredEvent = {
2254
+ type: 'adviceDelivered';
2255
+ session: string;
2256
+ ord: number;
2257
+ attempt: number;
2258
+ /** `f-` + 16 lowercase hex per finding; one steer may carry several. */
2259
+ findingIds: string[];
2260
+ carrier: 'acp_steering' | 'none' | (string & {});
2261
+ outcome: 'injected' | 'turn_ended' | 'refused' | 'not_delivered' | (string & {});
2262
+ detail: string | null;
2263
+ };
2264
+
2265
+ /**
2266
+ * DES-TEAMING-001 §5.3 / §7 (wicked-core#602, S3; api-types 0.40.0) — the worker's answer to one
2267
+ * DELIVERED finding, read from its final output line `ADVICE <id>: ACCEPT|DECLINE — <reason>` (the
2268
+ * last line per id wins). The worker may decline and must say why: `reason` (≤2 KB) is its
2269
+ * evidence, and may be `""` — a refusal with no evidence is recorded as exactly that. A delivered
2270
+ * finding with no line emits nothing here and is `unanswered` in the team ledger. The gate, not the
2271
+ * monitor, decides.
2272
+ */
2273
+ export type WorkerAdviceResponseEvent = {
2274
+ type: 'workerAdviceResponse';
2275
+ session: string;
2276
+ ord: number;
2277
+ attempt: number;
2278
+ findingId: string;
2279
+ disposition: 'accepted' | 'declined' | (string & {});
2280
+ reason: string;
2281
+ };
2282
+
2283
+ /** The S3 team-advice events as a discriminated union for consumers that narrow on `type`; they
2284
+ * also flow through the permissive {@link CoreEvent}. */
2285
+ export type TeamAdviceEvent = AdviceDeliveredEvent | WorkerAdviceResponseEvent;
2286
+
1903
2287
  export type GateEvidenceEvent =
1904
2288
  | EvaluatorMutatedWorktreeEvent
1905
2289
  | RepoChecksEvaluatedEvent
@@ -1935,10 +2319,12 @@ export interface UnitReworkAmendedEvent {
1935
2319
  * 0.7.27): `'cursor'` — the gated unit, today's approve-with-steer (`GateDecision.amendScope`
1936
2320
  * absent or `'cursor'`); `'creator'` — the first creator phase at/after the cursor
1937
2321
  * (`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'`.
2322
+ * `amendment` then carries the evaluator's full findings + the operator's note;
2323
+ * `'accept_suggestion'` (wicked-core#467) — the same rewind after the operator adopted the
2324
+ * evaluator's pinned edit, and `amendment` names it. ABSENT on an engine before the field — read
2325
+ * as `'cursor'`.
1940
2326
  */
1941
- scope?: 'cursor' | 'creator' | 'request_changes';
2327
+ scope?: 'cursor' | 'creator' | 'request_changes' | 'accept_suggestion';
1942
2328
  }
1943
2329
 
1944
2330
  /** P2 — a worker's ApplyStepResult arrived and output is ready to be gated. Fires before GateDecided.
@@ -2006,9 +2392,12 @@ export interface UnitDistributedEvent {
2006
2392
  ord: number;
2007
2393
  /** The roster key of the assigned seat. */
2008
2394
  cli: string;
2009
- /** How the seat was chosen: the council verdict, a degrade to the first candidate, an
2010
- * evaluator ≠ creator reassignment, or a deterministic tool execution. */
2011
- routingMethod: 'council' | 'degraded' | 'evaluator_distinct' | 'tool';
2395
+ /** How the seat was chosen: `'teamed'` — the deterministic pick, no council (wicked-core#590 S5;
2396
+ * every seated unit of a new run) — an evaluator ≠ creator reassignment, or a deterministic tool
2397
+ * execution. `'council'` / `'degraded'` are no longer emitted by a new run and stay for recorded
2398
+ * frames. On `'teamed'` the council-only fields (`agreementPct`, `returned`, `seated`, `dissent`)
2399
+ * are `null`. Consumers must render an unrecognised method as "routed", never as a council. */
2400
+ routingMethod: 'council' | 'degraded' | 'evaluator_distinct' | 'tool' | 'teamed';
2012
2401
  agreementPct: number | null;
2013
2402
  /** Ballots that came back. Read against `seated`. */
2014
2403
  returned: number | null;
@@ -2046,13 +2435,23 @@ export interface UnitDistributedEvent {
2046
2435
  */
2047
2436
  distinctnessFallback?: 'creator_seat' | 'same_cli_instance' | null;
2048
2437
  /** @deprecated api-types 0.36.0 — the engine emits `routingMethod`; removed in 0.37. */
2049
- routing_method?: 'council' | 'degraded' | 'evaluator_distinct' | 'tool';
2438
+ routing_method?: 'council' | 'degraded' | 'evaluator_distinct' | 'tool' | 'teamed';
2050
2439
  /** @deprecated api-types 0.36.0 — the engine emits `agreementPct`; removed in 0.37. */
2051
2440
  agreement_pct?: number | null;
2052
2441
  /** @deprecated api-types 0.36.0 — the engine emits `degradedReason`; removed in 0.37. */
2053
2442
  degraded_reason?: string | null;
2054
2443
  }
2055
2444
 
2445
+ /** One APPROVED INTENT AMENDMENT (`AgentSession.intent_amendments`; wicked-core#555). */
2446
+ export interface IntentAmendment {
2447
+ /** The human's own words — what the run's acceptance list now says. */
2448
+ text: string;
2449
+ /** The gate's cursor unit `ord`: the first unit the amendment reached. */
2450
+ ord: number;
2451
+ /** When it was decided, unix millis. */
2452
+ at: number;
2453
+ }
2454
+
2056
2455
  /** One seat the wave-6 engine BENCHED for a run (`AgentSession.benched_seats`, F-7R2-006): never
2057
2456
  * convened, never a failover or judge target, named in `unitDistributed.degradedReason`. */
2058
2457
  export interface BenchedSeat {
@@ -2552,6 +2951,30 @@ export interface SkillsManifestResponse {
2552
2951
  * `recorded.reasons` is `null` for a row written before per-reason portability (0.34.0). */
2553
2952
  drift?: SnapshotRowDrift[];
2554
2953
  } | null;
2954
+ /**
2955
+ * The wicked-garden plugin INSTALLED on this host right now (wicked-studio#388) — so a surface
2956
+ * can say when the daemon's baseline, and therefore every snapshot published from it, is behind
2957
+ * the operator's install. `null` = no plugin is installed (the unseeded case the 503 covers);
2958
+ * absent on a daemon older than the release carrying it. Compare `installed.baseline` with
2959
+ * `manifest.baseline`: different = Refresh baseline, then Publish.
2960
+ */
2961
+ installed?: InstalledPlugin | null;
2962
+ }
2963
+
2964
+ /** `SkillsManifestResponse.installed` — the live plugin's identity (wicked-studio#388). */
2965
+ export interface InstalledPlugin {
2966
+ source: { kind: SkillSourceKind; path: string; plugin_version: string };
2967
+ /** HEAD sha for a `checkout` source; `null` otherwise (or when git could not answer). */
2968
+ git_sha: string | null;
2969
+ /**
2970
+ * The content hash of the INSTALLED bundle, comparable with `SkillManifest.baseline` — the same
2971
+ * identity `POST /skills/refresh-baseline` decides on, never the version string (two installs of
2972
+ * one version with different bytes are two baselines). `null` when the bundle could not be read,
2973
+ * and then `unreadable` says why: a comparison crew could not make is stated, not reported as
2974
+ * agreement.
2975
+ */
2976
+ baseline: string | null;
2977
+ unreadable: string | null;
2555
2978
  }
2556
2979
 
2557
2980
  /** The identity of a portability-rule table (api-types 0.36.0, F-083): its version and the
@@ -3181,6 +3604,14 @@ export interface TestingReconResponse {
3181
3604
  /** Present only when the fan was campaign-registered with a `projectId` and filing a sibling
3182
3605
  * into the project failed — the runs are LIVE; re-attach via POST /projects/:id/members. */
3183
3606
  projectAttachError?: string;
3607
+ /**
3608
+ * `true` on a **202**: the engine was still launching the fan when the daemon answered (crew#471 —
3609
+ * it plans every node's units before it replies, which held the POST for minutes). The `runIds`
3610
+ * are final (deterministic node ids) and can be followed now; the trail entries and project
3611
+ * filing land when the engine answers, and a late refusal is audited as `campaign.launch_failed`.
3612
+ * Absent on a 201.
3613
+ */
3614
+ launching?: true;
3184
3615
  }
3185
3616
 
3186
3617
  // ── The governed test-authoring launch (wave 6; api-types 0.36.0) ──────────────────────────────
@@ -3355,6 +3786,21 @@ export interface LaunchRunBody {
3355
3786
  repoRef?: string;
3356
3787
  /** Built-in workflow id (`feature` | `bug` | `migration`); omit for free-text single-unit mode. */
3357
3788
  workflow?: string;
3789
+ /**
3790
+ * A USER-COMPOSED plan (DES-TEAMING-002 §8.4, seam T3; additive; needs an engine whose addon
3791
+ * carries `Core.supportsPlanLaunch` — an older one is refused with a 501, never run unplanned).
3792
+ * The ENGINE publishes `plan.proposed{by:"human"}` before any unit runs, scores and floor-fills
3793
+ * it, and holds it at a `plan_approval` gate (`awaiting_human{gate_kind:"plan_approval"}`) when
3794
+ * the approval matrix says so: always in manual mode, and in auto mode when high risk (band
3795
+ * 70-100 or destructive). Mutually exclusive with `workflow` (a plan or a preset).
3796
+ * A creator plan with NO `touch` set — and every creator preset — is scoped by the run's PA
3797
+ * first (wicked-core#633, X1): rev 1 is the read-only `pa-scope` step alone (unit ord 1), the
3798
+ * scored plan is rev 2 proposed by the PA seat (`plan.proposed{by:<PA seat>}`), and its
3799
+ * `plan_approval` gate, when one opens, is at ord 2. Declare `touch` to be scored at launch.
3800
+ * `deliver: "pr"` with a plan (api-types 0.42.0, T8) hands the engine the deliver step, which it
3801
+ * appends to the plan and puts in the floor; `deliver` omitted with a plan means `"none"`.
3802
+ */
3803
+ plan?: LaunchPlan;
3358
3804
  /**
3359
3805
  * The project to file this run into (DES-PROJECT-001 §2.2). The `crew.run` membership is
3360
3806
  * attached atomically with the launch record; an unknown or archived project fails the launch
@@ -3466,6 +3912,29 @@ export interface LaunchRunBody {
3466
3912
  actor?: string;
3467
3913
  }
3468
3914
 
3915
+ /**
3916
+ * One issue a launch's intent linked (`#N`, `owner/repo#N`, an issue URL) and whether the daemon
3917
+ * could read it (crew#627, additive). A governed worker cannot read GitHub, so for a workflow launch
3918
+ * the daemon reads each linked issue under its own identity (`gh issue view`, at most 5) and appends
3919
+ * the title, state, body and comments to the problem the engine plans from, inside a marked block.
3920
+ * `resolved: false` names why (`error`) — shown, never skipped.
3921
+ */
3922
+ export interface LinkedIssue {
3923
+ ref: string;
3924
+ resolved: boolean;
3925
+ /** Present when `resolved`. */
3926
+ title?: string;
3927
+ /** Present when not `resolved`: why, in one line. */
3928
+ error?: string;
3929
+ }
3930
+
3931
+ /** The `POST /runs` 201 body. `linkedIssues` (crew#627, additive) is present when the intent of a
3932
+ * workflow launch named at least one issue; absent otherwise and on an older daemon. */
3933
+ export interface LaunchRunResponse {
3934
+ runId: string;
3935
+ linkedIssues?: LinkedIssue[];
3936
+ }
3937
+
3469
3938
  /**
3470
3939
  * Body for `PUT /runs/:id/guidance` (DES-UX-002 §7.2, CREW-UX-7; api-types 0.9.0) — upsert the
3471
3940
  * run's ONE durable operator guidance note. `text: ''` CLEARS the note (the DTO field returns
@@ -3597,12 +4066,20 @@ export interface PhaseDef {
3597
4066
  * run `cmd` directly. */
3598
4067
  executor?: PhaseExecutor;
3599
4068
  /** Per-phase agent instructions folded into the unit prompt by the engine (core PhaseDef.instructions,
3600
- * Option<String>). Absent = no extra instruction. Mirrors of core drop-ins that carry it (e.g.
3601
- * survey-repo) MUST reproduce it verbatim or the runtime def diverges from core's — FINDING-011. */
4069
+ * Option<String>). Absent = no extra instruction. Mirrors of core drop-ins that carry it MUST
4070
+ * reproduce it verbatim or the runtime def diverges from core's — FINDING-011. */
3602
4071
  instructions?: string | null;
3603
4072
  gate_type: GateType | null;
3604
4073
  gate: GateSpec;
3605
4074
  executes_code: boolean;
4075
+ /**
4076
+ * The phase must REPORT what it captured (wicked-core#535 / BC-80; additive, engine ≥ the
4077
+ * release carrying it — an older engine's strict def parser refuses the key). Its output must
4078
+ * carry `wicked-capture-report {"derived": N, "submitted": M, "failed": K}`; a missing marker, a
4079
+ * failed submission and proposals derived but never submitted each deny the unit into the human
4080
+ * gate. An honest `0/0/0` passes. Absent/false ⇒ not parsed (every phase but `capture`).
4081
+ */
4082
+ requires_capture_report?: boolean;
3606
4083
  verified_evidence: boolean;
3607
4084
  required_deliverables: string[];
3608
4085
  depends_on: string[];
@@ -3629,6 +4106,29 @@ export interface WorkflowDef {
3629
4106
  base_skill_ref?: string | null;
3630
4107
  }
3631
4108
 
4109
+ /** A drop-in workflow definition the ENGINE refused, with the engine's own reason (wicked-crew#718). */
4110
+ export interface RefusedWorkflow {
4111
+ /** The def's id, as its file named it. */
4112
+ id: string;
4113
+ /**
4114
+ * wicked-core's refusal, verbatim — e.g. `gate evaluates nothing: write-a-note — the phase
4115
+ * declares executes_code but pins no validator and has no human gate, so its gate would approve
4116
+ * with nothing checked.`
4117
+ */
4118
+ reason: string;
4119
+ }
4120
+
4121
+ /**
4122
+ * `GET /workflows` (wicked-crew#718). `workflows` is what the ENGINE accepted — the only defs a
4123
+ * launch can name. `unavailable` is the drop-ins it refused, with its reason, so a def that cannot
4124
+ * be launched is NAMED rather than offered and then 400'd `unknown workflow`. Absent on a daemon
4125
+ * older than the release carrying it (read it as `[]`).
4126
+ */
4127
+ export interface WorkflowsResponse {
4128
+ workflows: WorkflowDef[];
4129
+ unavailable?: RefusedWorkflow[];
4130
+ }
4131
+
3632
4132
  /** Top-level requirements_graph.json artifact (schema 1.0.0). */
3633
4133
  export interface DomainGraph {
3634
4134
  metadata: { schema_version: string; migration_mode: string; source?: string };
@@ -3702,6 +4202,24 @@ export interface CodeGraphData {
3702
4202
  totals?: { nodes: number; edges: number; files: number };
3703
4203
  }
3704
4204
 
4205
+ /**
4206
+ * `GET /api/v1/repos/:id/deliver-target` (R3) — where a delivering launch on this repo would push,
4207
+ * read by the same origin preflight the deliver gate card uses (crew#730). `sentence` is the gate's
4208
+ * own target sentence for a run not yet started ("the run branch"); show it verbatim.
4209
+ * - `github` — a github.com origin; `githubRepo` is its `owner/repo` on github.com or
4210
+ * ssh.github.com, and `null` on another `*.github.com` host (gist, api);
4211
+ * - `local` — a filesystem path or `file://` URL: no pull request can be opened against it;
4212
+ * - `other` — another host: a pull request opens only if gh resolves it as a GitHub host;
4213
+ * - `none` — the checkout has no `origin` remote: the push will fail;
4214
+ * - `unknown` — the origin could not be read; the sentence claims nothing either way.
4215
+ */
4216
+ export interface DeliverTargetResponse {
4217
+ repo: string;
4218
+ origin: 'github' | 'local' | 'other' | 'none' | 'unknown';
4219
+ githubRepo: string | null;
4220
+ sentence: string;
4221
+ }
4222
+
3705
4223
  /**
3706
4224
  * `GET /repos/:id/graph` → 200 (api-types 0.35.0, F-2R2-005). `graph: null` = the repo's code
3707
4225
  * graph has not been built (nothing at its registered graph path) — and `reason` says WHY, so a
@@ -3781,6 +4299,18 @@ export interface SystemSettings {
3781
4299
  * `base_skill_refused`); the finding is an `error`.
3782
4300
  */
3783
4301
  baseSkillPolicy?: 'warn' | 'require';
4302
+ /**
4303
+ * The DELIVER IDENTITY (crew#549; additive): the GitHub login the deliver phase must push as.
4304
+ * The deliver script reads `gh api user` AND asks git which credential it would use for the
4305
+ * remote's host, and REFUSES — before anything is staged, committed or pushed — when either
4306
+ * disagrees with this login (or, with nothing configured, when those two disagree with each
4307
+ * other). `''`/absent ⇒ the `GH_ACCOUNT` env var decides, and unset there means the phase
4308
+ * pushes as whatever login gh holds, said aloud on the gate card.
4309
+ *
4310
+ * A LOGIN ONLY — never a token. The credential itself stays in gh's keyring or in the daemon's
4311
+ * `GH_TOKEN`; `GET /settings` returns this login and no secret ever.
4312
+ */
4313
+ deliverIdentityLogin?: string;
3784
4314
  /**
3785
4315
  * The stall watchdog's DETECTION threshold (crew#287; api-types 0.18.0 — previously a
3786
4316
  * daemon-local extension): minutes a run in `executing` may go without ANY engine event on the
@@ -3926,6 +4456,60 @@ export interface ElicitationResponse {
3926
4456
  content?: { response: string };
3927
4457
  }
3928
4458
 
4459
+ // ── Presets (DES-TEAMING-002 §8.4, seam C2) — saved phase selections ──────────
4460
+
4461
+ /**
4462
+ * One plan step: the catalog entry it instantiates (`understand`, `build`, …), its phase id,
4463
+ * and the step fields the catalog lets a step set (`gate`, `validator_pin`, `depends_on`, …).
4464
+ * The engine validates it (`plan::compose`); unknown keys are refused there.
4465
+ */
4466
+ export interface PresetStep {
4467
+ catalog: string;
4468
+ id: string;
4469
+ [k: string]: unknown;
4470
+ }
4471
+
4472
+ /**
4473
+ * A preset (`GET /presets`, `PUT /presets/:name`): a named phase selection stored in the core
4474
+ * store. A launch's `workflow` names one (`POST /runs {workflow}`); a project-scoped preset
4475
+ * shadows a global one of the same name for that project. Built-ins carry
4476
+ * `created_by: "builtin"` and cannot be deleted.
4477
+ */
4478
+ export interface Preset {
4479
+ name: string;
4480
+ /** `"global"` or `"project:<id>"`. */
4481
+ scope: string;
4482
+ steps: PresetStep[];
4483
+ /** `"builtin"` for a seeded built-in; otherwise the writing surface. */
4484
+ created_by: string;
4485
+ /** Unix millis. */
4486
+ updated_at: number;
4487
+ /**
4488
+ * A machine-owned preset (`chat`, `onboarding`, …): keep it off delivery surfaces and the work-mode
4489
+ * selector. The preset's half of the one name-keyed classification that stamps
4490
+ * `WorkflowDef.is_system` and `run_identity.system` (DES-TEAMING-002 §11.3). The daemon stamps it
4491
+ * on every preset it serves (api-types 0.48.0); absent from an older daemon.
4492
+ */
4493
+ system?: boolean;
4494
+ [k: string]: unknown;
4495
+ }
4496
+
4497
+ /** `PUT /presets/:name` body. `projectId` absent ⇒ a global preset. */
4498
+ export interface PutPresetBody {
4499
+ steps: PresetStep[];
4500
+ projectId?: string;
4501
+ }
4502
+
4503
+ /** `GET /presets` (`?projectId=`) — the presets a launch in that project sees, by name. */
4504
+ export interface PresetListResponse {
4505
+ presets: Preset[];
4506
+ }
4507
+
4508
+ /** `GET /presets/:name`, `PUT /presets/:name`. */
4509
+ export interface PresetResponse {
4510
+ preset: Preset;
4511
+ }
4512
+
3929
4513
  // ── Projects (DES-PROJECT-001) — the experience-plane keystone ─────────────────
3930
4514
 
3931
4515
  /** Project lifecycle: `active ⇄ archived`, no hard delete (ADR §1.3). */
@@ -4561,6 +5145,60 @@ export type ChatSeatRefusedFrame = {
4561
5145
  project_id?: string;
4562
5146
  };
4563
5147
 
5148
+ // ── Chat citations (crew#561, F-RC1-117; api-types 0.67.0) ───────────────────
5149
+ //
5150
+ // A seat's answer cites paths, `path:line` / `path:symbol` refs and commit SHAs, and nothing used
5151
+ // to check them: on the RC1 Phase 6 re-run 2 of 26 cited SHAs existed in no repo and three line
5152
+ // refs were off, rendered as plain text a reader could not tell apart from the real ones. The
5153
+ // daemon verifies each citation against the very read roots it handed the seats and publishes the
5154
+ // verdicts; the seat's TEXT is never edited — a skin MARKS it from these items.
5155
+
5156
+ /** What class of thing a citation is. */
5157
+ export type ChatCitationKind = 'path' | 'line' | 'symbol' | 'sha';
5158
+
5159
+ /**
5160
+ * The verdict on one citation.
5161
+ * - `verified` — it exists, exactly as cited;
5162
+ * - `corrected` — it exists, but not where the reply said (`resolved` is the real place);
5163
+ * - `unverified` — it is not in the chat's scope (the fabricated-SHA case);
5164
+ * - `unchecked` — the bounded pass did not reach it. NOT a claim either way.
5165
+ */
5166
+ export type ChatCitationStatus = 'verified' | 'corrected' | 'unverified' | 'unchecked';
5167
+
5168
+ /** One citation as verified. `raw` is the token EXACTLY as it appears in the reply, so a skin can
5169
+ * find it in the rendered text without re-parsing. */
5170
+ export interface ChatCitationItem {
5171
+ raw: string;
5172
+ kind: ChatCitationKind;
5173
+ status: ChatCitationStatus;
5174
+ /** Where the thing actually is: `alpha/src/foo.ts:2069` for a corrected line ref, the repo name
5175
+ * for a SHA, the defining line for a symbol. Absent when there is nothing to add. */
5176
+ resolved?: string;
5177
+ /** Why, in one short phrase — a skin's hover text. */
5178
+ note?: string;
5179
+ }
5180
+
5181
+ /**
5182
+ * DAEMON-SYNTHETIC (like {@link ChatSeatRefusedFrame}): ONE frame per terminal `chatReply` of a
5183
+ * chat that has read roots, broadcast AFTER that reply (verification never delays an answer) and
5184
+ * stamped with the reply's own `turn_id`. ABSENT — never empty — when the reply cites nothing, when
5185
+ * the chat has no read roots to verify against, and for a not-ok reply (a refusal or eviction line
5186
+ * cites nothing a reader would paste). The counts match `items`; `unverifiable` counts `unverified`
5187
+ * only, so `unchecked` is never read as a fabrication.
5188
+ */
5189
+ export type ChatCitationsFrame = {
5190
+ type: 'chatCitations';
5191
+ chat: string;
5192
+ cliKey: string;
5193
+ turn_id?: string;
5194
+ verified: number;
5195
+ unverifiable: number;
5196
+ corrected: number;
5197
+ unchecked: number;
5198
+ items: ChatCitationItem[];
5199
+ project_id?: string;
5200
+ };
5201
+
4564
5202
  /** One live chat on `GET /chats` (FINDING-027 gap 4; scope fields with crew#502). */
4565
5203
  export interface ChatSummary {
4566
5204
  chatId: string;
@@ -4604,7 +5242,25 @@ export interface ChatUsage {
4604
5242
  */
4605
5243
  export type ChatTranscriptRecord =
4606
5244
  | { 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 };
5245
+ | { at: number; turnId: string; kind: 'seat'; cliKey: string; text: string; ok: boolean; usage: ChatUsage | null }
5246
+ /**
5247
+ * The citation verdicts of the `seat` record with the same `turnId` + `cliKey` (api-types 0.68.0,
5248
+ * crew#561) — a SEPARATE record because the transcript is append-only and verification finishes
5249
+ * after the reply is stored. A reader folds it onto that reply; a skin that does not know this
5250
+ * kind ignores it (the `kind` discriminant is the extension point). Present only for a reply that
5251
+ * cited something; the live frame is {@link ChatCitationsFrame}, with the same fields.
5252
+ */
5253
+ | {
5254
+ at: number;
5255
+ turnId: string;
5256
+ kind: 'citations';
5257
+ cliKey: string;
5258
+ verified: number;
5259
+ unverifiable: number;
5260
+ corrected: number;
5261
+ unchecked: number;
5262
+ items: ChatCitationItem[];
5263
+ };
4608
5264
 
4609
5265
  /** `GET /chats/:id` → 200. */
4610
5266
  export interface ChatDetailResponse {
@@ -4979,8 +5635,12 @@ export interface Campaign {
4979
5635
  * `delivery === 'delivered'`.
4980
5636
  */
4981
5637
  export interface CampaignNodeDelivery {
4982
- delivery: 'delivered' | 'stranded' | 'vacuous' | 'none';
5638
+ delivery: 'delivered' | 'pushed' | 'stranded' | 'vacuous' | 'none';
4983
5639
  deliverUrl?: string;
5640
+ /** Present exactly when `delivery === 'pushed'` (api-types 0.69.0, N1). */
5641
+ deliverBranch?: string;
5642
+ /** Present exactly when `delivery === 'pushed'`; userinfo removed. */
5643
+ deliverRemote?: string;
4984
5644
  }
4985
5645
 
4986
5646
  /**
@@ -4993,8 +5653,12 @@ export interface CampaignNodeDelivery {
4993
5653
  export interface AttachedRunView {
4994
5654
  runId: string;
4995
5655
  status: SessionStatus;
4996
- delivery: 'delivered' | 'stranded' | 'vacuous' | 'none';
5656
+ delivery: 'delivered' | 'pushed' | 'stranded' | 'vacuous' | 'none';
4997
5657
  deliverUrl?: string;
5658
+ /** Present exactly when `delivery === 'pushed'` (api-types 0.69.0, N1). */
5659
+ deliverBranch?: string;
5660
+ /** Present exactly when `delivery === 'pushed'`; userinfo removed. */
5661
+ deliverRemote?: string;
4998
5662
  }
4999
5663
 
5000
5664
  /**
@@ -5182,6 +5846,9 @@ export interface AcpCliDiagnostics {
5182
5846
  lastStartedTs: number | null;
5183
5847
  /** Epoch ms of the newest `acpFallback`, or `null` when none recorded. */
5184
5848
  lastFallbackTs: number | null;
5849
+ /** (core#581) The seat's ACP version pin, probed now — present only for a pinned seat, which
5850
+ * lists here even with no ACP events yet (zero counts). */
5851
+ versionPin?: SeatVersionPin;
5185
5852
  }
5186
5853
 
5187
5854
  /** The ACP fold of `GET /diagnostics` — keyed by `cliKey` (`claude`, `pi`, `codex`, …). */
@@ -5298,6 +5965,36 @@ export interface DiagnosticsGovernance {
5298
5965
  findings: DiagnosticsGovernanceFinding[];
5299
5966
  }
5300
5967
 
5968
+ /** `POST /governance/deadletters/replay` body (api-types 0.51.0): replay THIS daemon's dead-letter
5969
+ * outbox into its own governance store. `dryRun: true` folds the outbox and moves nothing. */
5970
+ export interface GovernanceReplayBody {
5971
+ dryRun?: boolean;
5972
+ }
5973
+
5974
+ /** `POST /governance/deadletters/replay` answer (api-types 0.51.0) — the `governance replay` CLI's
5975
+ * outcome. A dry run carries `fold` (what the outbox holds) and `blocker`: why a real replay could
5976
+ * not run on this daemon (in-memory store, an engine without the binding), `null` when it can. A
5977
+ * real replay reports what landed (`replayed`), what an earlier replay had already landed
5978
+ * (`alreadyPresent`, `null` on an engine that does not say) and what did not (`failed` — those
5979
+ * entries are back on the live outbox and stay dead letters). 409: no governance store / a replay
5980
+ * already running / a blocker; 501: the engine cannot replay. */
5981
+ export interface GovernanceReplayOutcome {
5982
+ outbox: string;
5983
+ store: { path: string; source: DiagnosticsGovernanceStoreSource };
5984
+ /** Where a real replay archived the drained outbox; `null` on a dry run. */
5985
+ archive: string | null;
5986
+ read: number;
5987
+ replayed: number;
5988
+ alreadyPresent: number | null;
5989
+ failed: number;
5990
+ dryRun: boolean;
5991
+ /** The same-event conflation caveat the CLI prints, or `null`. */
5992
+ note: string | null;
5993
+ /** Dry run only: the outbox fold (the `/diagnostics` dead-letter block minus the legacy pointer). */
5994
+ fold?: Omit<DiagnosticsGovernanceDeadletters, 'legacyOutbox'>;
5995
+ blocker: string | null;
5996
+ }
5997
+
5301
5998
  /** The skills seam's state as `GET /diagnostics` reports it (skills keystone, api-types 0.28.0). */
5302
5999
  export type DiagnosticsSkillsState = 'published' | 'fallback' | 'blocked' | 'config-error' | 'disabled';
5303
6000
 
@@ -5604,55 +6301,240 @@ export interface PolicyLandingResult {
5604
6301
  * predating the policy→steering landing (forward-additive, DES-MEM-FACETED-001 §5.2).
5605
6302
  */
5606
6303
  export type ApproveProposalResponse =
5607
- | { outcome: 'promoted'; active_id: string }
6304
+ | { outcome: 'promoted'; active_id: string; edited?: ProposalEdit }
5608
6305
  | { outcome: 'handed_off'; payload: unknown; landing?: PolicyLandingResult };
5609
6306
 
5610
- /** `POST /proposals/:id/reject` → 200. */
5611
- export interface RejectProposalResponse {
5612
- ok: true;
6307
+ /**
6308
+ * `POST /proposals/:id/approve` body (api-types 0.49.0) — present only for an accept WITH an edit;
6309
+ * no body (or `{}`) is the plain approve. estate has no proposal edit, so the daemon submits the
6310
+ * edited copy to the same queue (`payload.edited_from = <id>`), approves it, then rejects the
6311
+ * original. Memory proposals only (400 otherwise; 404 when `id` is not pending; 409 while another
6312
+ * accept of the same `id` is in flight).
6313
+ */
6314
+ export interface ApproveProposalBody {
6315
+ /** The edited memory text (replaces `payload.content`). */
6316
+ content?: string;
6317
+ /** `"pattern"`: the copy drops its `project`/`repo` facets, so it may be recalled on ANY project
6318
+ * (the human act that lets a pattern cross projects; refused on anything but a captured memory, `payload.capture: "memory"`).
6319
+ * `"project"`: it stays scoped to its project — gaining the proposing run's project as its
6320
+ * `project` facet when the worker filed none (400 when no project files that run). An accept
6321
+ * that changes nothing the store keeps approves the original as filed (no copy, no `edited`). */
6322
+ reach?: 'project' | 'pattern';
5613
6323
  }
5614
6324
 
5615
- // ── Memory management (governed-knowledge surface, DES-MEM-FACETED-001, api-types 0.22.0) ──────
5616
- //
5617
- // The studio's surface for managing the memories that ALREADY exist in the operator store — the
5618
- // counterpart to the proposal queue (which decides learnings not yet stored). Backed by the estate
5619
- // MCP memory tools (memory.recall / memory.coverage / memory.erase) exposed over crew's /api/v1.
5620
- // The granularity is estate's, surfaced honestly: browse is query-based (no "list all"), and
5621
- // retire is SUBTREE-scoped (no per-id delete).
5622
-
5623
6325
  /**
5624
- * One memory as browsed from the store (`GET /memory`, from estate `memory.recall`).
5625
- *
5626
- * estate `memory.recall` is query-based and token-budgeted — there is NO "list all" — and returns
5627
- * `{ memory_id, scope, content, tier, score }` per item. It does NOT surface per-item `facets`, so
5628
- * `facets` is presently always `{}` (kept in the shape for forward-compatibility and to mirror
5629
- * `Proposal.facets`); `score` is the recall relevance score, absent when estate omits it.
6326
+ * `POST /proposals` body (api-types 0.54.0) — a skin files ONE preference into the review queue
6327
+ * (studio's takes: "prefers takes like X", filed when a person picks one of two candidate outputs).
6328
+ * The daemon files it as `kind_type: "memory"` with `payload {content, tier: "semantic",
6329
+ * capture: "preference", source?}`; the kind is never the caller's (unknown keys are a 400).
6330
+ * Nothing is learned until the person accepts it on `/proposals`.
5630
6331
  */
5631
- export interface MemoryItem {
5632
- /** Stable memory-node id (estate `memory_id`). */
5633
- id: string;
5634
- /** The memory body. */
6332
+ export interface FileProposalBody {
6333
+ /** The preference in words (1–4000 chars, trimmed). */
5635
6334
  content: string;
5636
- /** Tier: `working` | `episodic` | `semantic` | `procedural` | `archival`. */
5637
- tier: string;
5638
- /** The memory's own hierarchical scope path (slash-separated `kind:id` segments; `""` = root). */
5639
- scope: string;
5640
- /** Orthogonal facets (axis → value) the memory is tagged with. Populated by estate `memory.list`
5641
- * (the management browse) — this is what powers the surface's facet filter. `{}` for an
5642
- * unfaceted memory (and for a `memory.recall`-sourced item, which carries no facets). */
5643
- facets: Record<string, string>;
5644
- /** Relevance score — only present on a `memory.recall` item; `memory.list` (the browse) is not
5645
- * relevance-ranked, so it is absent there. */
5646
- score?: number;
5647
- /**
5648
- * Unix-SECONDS timestamp of when this memory FIRST entered the store — the "added" time the
5649
- * governance surface sorts + date-filters on (api-types 0.23.0). Populated from estate
5650
- * `memory.list`, which returns `created_at` per item; ABSENT — never fabricated — on a memory
5651
- * whose creation time the store did not report (and on a `memory.recall`-sourced item, which
5652
- * carries none). Additive: absence is the one spelling of "unknown", so a `since`/`until` filter
5653
- * cannot assert an undated memory in range.
5654
- */
5655
- created_at?: number;
6335
+ /** Becomes the `project` facet, so the preference is recalled on that project only. */
6336
+ project?: string;
6337
+ /** Where the pick was made, e.g. `doc:<docId>@v3` (kept on the payload). */
6338
+ source?: string;
6339
+ }
6340
+
6341
+ /** `POST /proposals` → 201: the pending proposal's id. */
6342
+ export interface FileProposalResponse {
6343
+ id: string;
6344
+ }
6345
+
6346
+ /** An accept-with-edit's trail: the original proposal (now rejected) and the approved copy. */
6347
+ export interface ProposalEdit {
6348
+ from: string;
6349
+ to: string;
6350
+ /** The copy was approved but rejecting the original failed: it is still pending, for the person
6351
+ * to reject. Absent when the original was rejected. */
6352
+ originalPending?: true;
6353
+ }
6354
+
6355
+ // ── Capture (Studio OS behaviour 8, api-types 0.49.0) ─────────────────────────────
6356
+
6357
+ /** One dropped text file (a transcript, meeting notes). */
6358
+ export interface CaptureTextFile {
6359
+ name: string;
6360
+ text: string;
6361
+ }
6362
+
6363
+ /** One dropped photo, read by a vision-capable seat. `mediaType` is image/png, image/jpeg,
6364
+ * image/gif or image/webp. */
6365
+ export interface CaptureImageFile {
6366
+ name: string;
6367
+ mediaType: string;
6368
+ dataBase64: string;
6369
+ }
6370
+
6371
+ /**
6372
+ * `POST /projects/:id/capture` body: notes and/or files (at least one). The daemon lands them in a
6373
+ * per-run inbox and launches a repo-less run filed to the project (through `POST /runs`, so its
6374
+ * refusals are that route's). The run's output is proposals in the `/proposals` queue — each with
6375
+ * `kind_type: "memory"` (or `"policy:<type>"` for a rule), `payload.capture` = `intent` |
6376
+ * `decision` | `memory` | `rule`, `provenance.run_id` = the returned `runId`, and `facets.project`
6377
+ * unless the team marked a memory `payload.reach: "pattern"` (the user decides on accept).
6378
+ */
6379
+ export interface CaptureBody {
6380
+ notes?: string;
6381
+ files?: Array<CaptureTextFile | CaptureImageFile>;
6382
+ }
6383
+
6384
+ /** `POST /projects/:id/capture` → 201. */
6385
+ export interface CaptureResponse {
6386
+ runId: string;
6387
+ }
6388
+
6389
+ // ── The Demo experience (wicked-studio#373, api-types 0.61.0) ────────────────────────────────
6390
+
6391
+ /**
6392
+ * `POST /projects/:id/demo` body: the app to film, who the demo is for and what to show. The daemon
6393
+ * mints the run's demo root, writes the brief there and launches the built-in `demo` preset (the
6394
+ * wicked-garden demo skill's plan → record → review) through `POST /runs`, so its refusals are that
6395
+ * route's. The demo root is the run's only extra write root; a caller never names it.
6396
+ */
6397
+ export interface DemoLaunchBody {
6398
+ /** The running app (http or https). The recorder is read-only against it. */
6399
+ url: string;
6400
+ audience: string;
6401
+ show: string;
6402
+ /** The roster to seat the team from, as `POST /runs` takes it. Omit for the daemon's roster. */
6403
+ clisJson?: string;
6404
+ }
6405
+
6406
+ /** `POST /projects/:id/demo` → 201. */
6407
+ export interface DemoLaunchResponse {
6408
+ runId: string;
6409
+ }
6410
+
6411
+ /** `PUT /runs/:id/demo/script` body: the presenter's edit of `script.md` (plan gate only; ≤ 256 KB). */
6412
+ export interface DemoScriptBody {
6413
+ content: string;
6414
+ }
6415
+
6416
+ /**
6417
+ * Where a demo run stands. `preparing` covers the PA's scope step and any phase the floor added before
6418
+ * `plan`; `team_gate` is the team plan's approval gate (`plan_approval`: the run holds a plan rev past
6419
+ * the accepted one), which opens before `plan` runs and is answered like any gate; `plan_gate` and `review_gate` are the run's human gates (answered through `POST /runs/:id/gate`:
6420
+ * approve, or `action: 'request_changes'` with the note as `amend` — at the plan gate it re-runs `plan`,
6421
+ * at the review gate it rewinds to `record`). The review gate is the one after `review` passed, or the
6422
+ * engine's escalation gate when the review did not pass (`review.rejected`).
6423
+ */
6424
+ export type DemoStage =
6425
+ | 'preparing'
6426
+ | 'team_gate'
6427
+ | 'planning'
6428
+ | 'plan_gate'
6429
+ | 'recording'
6430
+ | 'reviewing'
6431
+ | 'review_gate'
6432
+ | 'done'
6433
+ | 'failed';
6434
+
6435
+ /** One chapter of the plan's `chapters.json`, with whether its segment has been recorded. */
6436
+ export interface DemoChapter {
6437
+ /** `NN-slug` — the segment key record.mjs re-records by. */
6438
+ key: string;
6439
+ title: string;
6440
+ blurb: string;
6441
+ tags: string[];
6442
+ resets: string[];
6443
+ recorded: boolean;
6444
+ }
6445
+
6446
+ /** A chapter marker of the stitched MP4 (record.mjs's `chapters.md`). */
6447
+ export interface DemoMarker {
6448
+ /** `m:ss`. */
6449
+ at: string;
6450
+ sec: number;
6451
+ title: string;
6452
+ }
6453
+
6454
+ /** One finding of the reviewer's verdict block. */
6455
+ export interface DemoFinding {
6456
+ at: string;
6457
+ chapter: string;
6458
+ issue: string;
6459
+ verdict: 're-encode' | 're-record' | 'fix-app';
6460
+ }
6461
+
6462
+ /** `GET /runs/:id/demo` → 200 (404 for a run that is not a demo run). */
6463
+ export interface DemoView {
6464
+ runId: string;
6465
+ /** From the brief the daemon wrote. */
6466
+ url: string | null;
6467
+ audience: string | null;
6468
+ stage: DemoStage;
6469
+ /** `script.md` — the presenter script (null until `plan` writes it). */
6470
+ script: string | null;
6471
+ chapters: DemoChapter[];
6472
+ markers: DemoMarker[];
6473
+ /** Root-relative paths for `GET /runs/:id/demo/file?path=`. */
6474
+ sheets: Array<{ name: 'chapters' | 'joins' | 'end'; path: string }>;
6475
+ video: { path: string; bytes: number } | null;
6476
+ /** From the recorder's `recording.json`: true only when every stitched segment was recorded read-only;
6477
+ * null before a stitch (or from a recorder without the guard). */
6478
+ recording: { readOnly: boolean | null };
6479
+ /**
6480
+ * The reviewer's last output and its parsed verdict block (empty until `review` has finished).
6481
+ * `rejected`: the engine judged the review NOT PASS, so the review gate is its escalation gate — a
6482
+ * failed review cannot be accepted: approve re-runs the reviewer, `request_changes` sends the note back
6483
+ * to `record`, reject cancels the run.
6484
+ */
6485
+ review: { verdict: 'accept' | 'changes' | null; findings: DemoFinding[]; text: string | null; rejected: boolean };
6486
+ /** The seats that recorded and reviewed — evaluator ≠ creator puts them apart. */
6487
+ seats: { recorder: string | null; reviewer: string | null };
6488
+ /** The script says which data is synthetic. */
6489
+ syntheticLabelled: boolean;
6490
+ }
6491
+
6492
+ /** `POST /proposals/:id/reject` → 200. */
6493
+ export interface RejectProposalResponse {
6494
+ ok: true;
6495
+ }
6496
+
6497
+ // ── Memory management (governed-knowledge surface, DES-MEM-FACETED-001, api-types 0.22.0) ──────
6498
+ //
6499
+ // The studio's surface for managing the memories that ALREADY exist in the operator store — the
6500
+ // counterpart to the proposal queue (which decides learnings not yet stored). Backed by the estate
6501
+ // MCP memory tools (memory.recall / memory.coverage / memory.erase) exposed over crew's /api/v1.
6502
+ // The granularity is estate's, surfaced honestly: browse is query-based (no "list all"), and
6503
+ // retire is SUBTREE-scoped (no per-id delete).
6504
+
6505
+ /**
6506
+ * One memory as browsed from the store (`GET /memory`, from estate `memory.recall`).
6507
+ *
6508
+ * estate `memory.recall` is query-based and token-budgeted — there is NO "list all" — and returns
6509
+ * `{ memory_id, scope, content, tier, score }` per item. It does NOT surface per-item `facets`, so
6510
+ * `facets` is presently always `{}` (kept in the shape for forward-compatibility and to mirror
6511
+ * `Proposal.facets`); `score` is the recall relevance score, absent when estate omits it.
6512
+ */
6513
+ export interface MemoryItem {
6514
+ /** Stable memory-node id (estate `memory_id`). */
6515
+ id: string;
6516
+ /** The memory body. */
6517
+ content: string;
6518
+ /** Tier: `working` | `episodic` | `semantic` | `procedural` | `archival`. */
6519
+ tier: string;
6520
+ /** The memory's own hierarchical scope path (slash-separated `kind:id` segments; `""` = root). */
6521
+ scope: string;
6522
+ /** Orthogonal facets (axis → value) the memory is tagged with. Populated by estate `memory.list`
6523
+ * (the management browse) — this is what powers the surface's facet filter. `{}` for an
6524
+ * unfaceted memory (and for a `memory.recall`-sourced item, which carries no facets). */
6525
+ facets: Record<string, string>;
6526
+ /** Relevance score — only present on a `memory.recall` item; `memory.list` (the browse) is not
6527
+ * relevance-ranked, so it is absent there. */
6528
+ score?: number;
6529
+ /**
6530
+ * Unix-SECONDS timestamp of when this memory FIRST entered the store — the "added" time the
6531
+ * governance surface sorts + date-filters on (api-types 0.23.0). Populated from estate
6532
+ * `memory.list`, which returns `created_at` per item; ABSENT — never fabricated — on a memory
6533
+ * whose creation time the store did not report (and on a `memory.recall`-sourced item, which
6534
+ * carries none). Additive: absence is the one spelling of "unknown", so a `since`/`until` filter
6535
+ * cannot assert an undated memory in range.
6536
+ */
6537
+ created_at?: number;
5656
6538
  }
5657
6539
 
5658
6540
  /**
@@ -5708,16 +6590,1461 @@ export interface MemoryCoverageResponse {
5708
6590
  }
5709
6591
 
5710
6592
  /**
5711
- * `POST /memory/retire` body. Retire is SUBTREE-scoped, never per-id: estate `memory.erase`
5712
- * hard-deletes EVERY memory whose scope equals or descends from `scope_prefix`. `scope_prefix` is
5713
- * required and must be non-empty (estate refuses a total wipe) — the surface must show the operator
5714
- * the whole subtree it will remove, not a single row.
6593
+ * `POST /memory/retire` body. This retire is SUBTREE-scoped: estate `memory.erase` hard-deletes
6594
+ * EVERY memory whose scope equals or descends from `scope_prefix`. `scope_prefix` is required and
6595
+ * must be non-empty (estate refuses a total wipe) — the surface must show the operator the whole
6596
+ * subtree it will remove. To remove ONE row, use {@link RetireMemoryItemBody}.
5715
6597
  */
5716
6598
  export interface RetireMemoryBody {
5717
6599
  scope_prefix: string;
5718
6600
  }
5719
6601
 
5720
- /** `POST /memory/retire` → 200. The number of memories the subtree wipe deleted. */
6602
+ /**
6603
+ * `POST /memory/retire-item` body (studio#206; api-types 0.60.0): exactly ONE memory, by the
6604
+ * `memory_id` `GET /memory` listed. 404 when no memory has that id; 501 `estate_upgrade_required`
6605
+ * when the estate predates erase-by-id (wicked-estate#188) — nothing is deleted then.
6606
+ */
6607
+ export interface RetireMemoryItemBody {
6608
+ memory_id: string;
6609
+ }
6610
+
6611
+ /** `POST /memory/retire` / `POST /memory/retire-item` → 200. How many memories were deleted
6612
+ * (always 1 for `retire-item`). */
5721
6613
  export interface RetireMemoryResponse {
5722
6614
  erased: number;
5723
6615
  }
6616
+
6617
+ // ── Team events on the bus (DES-TEAMING-002 §6, wicked-core T1; api-types 0.41.0) ──────────────
6618
+ //
6619
+ // The `wicked.team.*` facts wicked-core publishes on the cross-product bus (`domain`
6620
+ // `wicked-core`, `subdomain` `core.team`), one owner per type (DES-002 §7: E engine, S supervisor,
6621
+ // R attempt runner). Crew publishes none of them: it relays and reads. Mirrors wicked-core
6622
+ // `src/team/events.rs`; `packages/crew/tests/team-events.test.ts` pins every type against the
6623
+ // engine's own round-trip fixtures by value. Payload keys are snake_case and every key is
6624
+ // present (an absent value is `null`), except a plan step's optional fields, which a plan omits
6625
+ // when unset. The embedded {@link TeamLedger} keeps DES-001 §7's camelCase.
6626
+
6627
+ /** The 25 `wicked.team.<noun>.<verb>` event types. */
6628
+ export type TeamEventType =
6629
+ | 'wicked.team.path.started'
6630
+ | 'wicked.team.path.scored'
6631
+ | 'wicked.team.plan.proposed'
6632
+ | 'wicked.team.plan.revised'
6633
+ | 'wicked.team.plan.accepted'
6634
+ | 'wicked.team.plan.refused'
6635
+ | 'wicked.team.member.joined'
6636
+ | 'wicked.team.member.left'
6637
+ | 'wicked.team.step.claimed'
6638
+ | 'wicked.team.checkpoint.reached'
6639
+ | 'wicked.team.finding.raised'
6640
+ | 'wicked.team.advice.delivered'
6641
+ | 'wicked.team.advice.answered'
6642
+ | 'wicked.team.help.requested'
6643
+ | 'wicked.team.help.answered'
6644
+ | 'wicked.team.change.requested'
6645
+ | 'wicked.team.step.completed'
6646
+ | 'wicked.team.step.reviewed'
6647
+ | 'wicked.team.finding.settled'
6648
+ | 'wicked.team.council.called'
6649
+ | 'wicked.team.council.ruled'
6650
+ | 'wicked.team.ledger.folded'
6651
+ | 'wicked.team.gate.opened'
6652
+ | 'wicked.team.gate.decided'
6653
+ | 'wicked.team.path.ended';
6654
+
6655
+ /** The fields every team payload carries. */
6656
+ export interface TeamEnvelope {
6657
+ run_id: string;
6658
+ ord: number | null;
6659
+ attempt: number | null;
6660
+ /**
6661
+ * Who acted: a seated CLI instance (the PA, a member or the authoring monitor, e.g.
6662
+ * `'claude#1'`, `'claude#2'`, `'codex'`), `'engine'`, `'human'` or `'council:<task id>'`.
6663
+ */
6664
+ by: string;
6665
+ /** Epoch milliseconds at the producer. */
6666
+ at: number;
6667
+ /** The row this one answers, e.g. `'finding.raised#4'`. */
6668
+ re: string | null;
6669
+ }
6670
+
6671
+ /** A plan step: a catalog entry plus the step fields a plan may set (omitted when unset). */
6672
+ export interface TeamPlanStep {
6673
+ catalog: string;
6674
+ id: string;
6675
+ instructions?: string;
6676
+ owner?: 'pa' | 'team' | (string & {});
6677
+ depends_on?: string[];
6678
+ /** A raised gate, as the workflow's externally tagged `GateSpec`. */
6679
+ gate?: unknown;
6680
+ added_by?: 'plan' | 'floor' | (string & {});
6681
+ floor_reason?: string;
6682
+ late?: boolean;
6683
+ }
6684
+
6685
+ export interface TeamPlanOverride {
6686
+ remove: string[];
6687
+ reason: string;
6688
+ }
6689
+
6690
+ export type TeamPathStartedPayload = TeamEnvelope & {
6691
+ cli: string;
6692
+ selection: 'chosen' | 'random' | (string & {});
6693
+ roster: string[];
6694
+ request: string;
6695
+ workflow: string | null;
6696
+ plan: boolean;
6697
+ };
6698
+
6699
+ export type TeamPathScoredPayload = TeamEnvelope & {
6700
+ /** `'intent:<proposal_id>'` | `'diff:<ord>:<attempt>:<rescore_seq>'`. */
6701
+ score_source: string;
6702
+ basis: 'intent' | 'diff' | (string & {});
6703
+ score: number;
6704
+ deterministic: number;
6705
+ reasons: string[];
6706
+ model: { add: number; rationale: string } | null;
6707
+ signals: {
6708
+ changed_symbols: number;
6709
+ dependents: number;
6710
+ products: number;
6711
+ contract_change: boolean;
6712
+ test_gap: number;
6713
+ critical: boolean;
6714
+ destructive: boolean;
6715
+ truncated: boolean;
6716
+ } | null;
6717
+ plan: { monitors: number; depth: string; post_hoc_reviewer: boolean; post_hoc_other_cli: boolean };
6718
+ tree: string | null;
6719
+ };
6720
+
6721
+ export type TeamPlanProposedPayload = TeamEnvelope & {
6722
+ proposal_id: string;
6723
+ base_rev: number | null;
6724
+ kind: 'initial' | 'change' | 'edit' | (string & {});
6725
+ preset: string | null;
6726
+ steps: TeamPlanStep[];
6727
+ monitors: { asked: number };
6728
+ asks: string[];
6729
+ touch: string[];
6730
+ override: TeamPlanOverride | null;
6731
+ rationale: string;
6732
+ };
6733
+
6734
+ export type TeamPlanRevisedPayload = TeamEnvelope & {
6735
+ plan_rev: number;
6736
+ proposal_id: string | null;
6737
+ reason: 'floor_raised' | 'pa_added' | 'member_request' | (string & {});
6738
+ from_band: string;
6739
+ to_band: string;
6740
+ high_risk: boolean;
6741
+ added: TeamPlanStep[];
6742
+ };
6743
+
6744
+ export type TeamPlanAcceptedPayload = TeamEnvelope & {
6745
+ plan_rev: number;
6746
+ workflow_id: string;
6747
+ band: string;
6748
+ high_risk: boolean;
6749
+ mode: 'auto' | 'manual' | (string & {});
6750
+ steps: TeamPlanStep[];
6751
+ override: TeamPlanOverride | null;
6752
+ proposal_id: string | null;
6753
+ };
6754
+
6755
+ export type TeamPlanRefusedPayload = TeamEnvelope & {
6756
+ proposal_id: string;
6757
+ base_rev: number | null;
6758
+ reason: string;
6759
+ };
6760
+
6761
+ export type TeamMemberJoinedPayload = TeamEnvelope & {
6762
+ member_id: string;
6763
+ open_seq: number;
6764
+ seat: string;
6765
+ role: 'monitor' | (string & {});
6766
+ status: 'attached' | 'failed' | (string & {});
6767
+ reason: string;
6768
+ error: string | null;
6769
+ };
6770
+
6771
+ export type TeamMemberLeftPayload = TeamEnvelope & {
6772
+ member_id: string;
6773
+ open_seq: number;
6774
+ seat: string;
6775
+ status: 'completed' | 'budget_exhausted' | 'failed' | 'timed_out' | (string & {});
6776
+ batches: number;
6777
+ error: string | null;
6778
+ };
6779
+
6780
+ export type TeamStepClaimedPayload = TeamEnvelope & {
6781
+ step_id: string;
6782
+ role: string;
6783
+ kind: string;
6784
+ phase: string;
6785
+ criterion: string;
6786
+ baseline_tree: string | null;
6787
+ repo: { workdir: string; git_dir: string } | null;
6788
+ code_graph_db: string | null;
6789
+ };
6790
+
6791
+ export type TeamCheckpointReachedPayload = TeamEnvelope & {
6792
+ seq: number;
6793
+ tool_call_id: string;
6794
+ kind: string;
6795
+ title: string;
6796
+ status: 'completed' | 'failed' | (string & {});
6797
+ paths: string[];
6798
+ };
6799
+
6800
+ export type TeamFindingRaisedPayload = TeamEnvelope & {
6801
+ raise_seq: number;
6802
+ finding_id: string;
6803
+ member_id: string;
6804
+ line_key: string | null;
6805
+ anchor: string | null;
6806
+ anchor_source: 'graph' | 'hunk' | 'none' | (string & {}) | null;
6807
+ severity: 'high' | 'medium' | (string & {});
6808
+ path: string;
6809
+ line: number;
6810
+ evidence: string;
6811
+ claim: string;
6812
+ suggestion: string | null;
6813
+ tree: string;
6814
+ in_diff: boolean;
6815
+ corroborated_by: string[];
6816
+ };
6817
+
6818
+ export type TeamAdviceDeliveredPayload = TeamEnvelope & {
6819
+ raise_seq: number;
6820
+ finding_id: string;
6821
+ /** The steer id | `'boundary:<step_id>:<attempt>'` | `'end:<attempt>'`. */
6822
+ delivery_id: string;
6823
+ steer_id: string | null;
6824
+ channel: 'acp_steering' | 'boundary' | 'none' | (string & {});
6825
+ outcome: 'injected' | 'turn_ended' | 'refused' | 'not_delivered' | (string & {});
6826
+ detail: string | null;
6827
+ };
6828
+
6829
+ export type TeamAdviceAnsweredPayload = TeamEnvelope & {
6830
+ raise_seq: number;
6831
+ /** `'<step_id>:<attempt>'`. */
6832
+ answered_in: string;
6833
+ finding_id: string;
6834
+ disposition: 'accepted' | 'declined' | (string & {});
6835
+ reason: string;
6836
+ };
6837
+
6838
+ export type TeamHelpRequestedPayload = TeamEnvelope & {
6839
+ help_id: string;
6840
+ help_seq: number;
6841
+ question: string;
6842
+ context: string;
6843
+ };
6844
+
6845
+ export type TeamHelpAnsweredPayload = TeamEnvelope & {
6846
+ help_id: string;
6847
+ answer_id: string;
6848
+ answer: string;
6849
+ evidence: string[];
6850
+ };
6851
+
6852
+ export type TeamChangeRequestedPayload = TeamEnvelope & {
6853
+ change_id: string;
6854
+ change_seq: number;
6855
+ steps: TeamPlanStep[];
6856
+ reason: string;
6857
+ };
6858
+
6859
+ export type TeamStepCompletedPayload = TeamEnvelope & {
6860
+ step_id: string;
6861
+ status: 'ok' | 'failed' | 'cancelled' | 'elicitation_failed' | 'timed_out' | (string & {});
6862
+ tree: string | null;
6863
+ output_bytes: number;
6864
+ output_ref: string;
6865
+ };
6866
+
6867
+ export type TeamStepReviewedPayload = TeamEnvelope & {
6868
+ step_id: string;
6869
+ verdict: 'accepted' | 'rejected' | (string & {});
6870
+ to: 'member' | 'pa' | (string & {}) | null;
6871
+ reason: string;
6872
+ };
6873
+
6874
+ export type TeamFindingSettledPayload = TeamEnvelope & {
6875
+ raise_seq: number;
6876
+ finding_id: string;
6877
+ status: 'held' | 'withdrawn' | 'superseded' | (string & {});
6878
+ reason: string;
6879
+ final_line: number | null;
6880
+ };
6881
+
6882
+ export type TeamCouncilCalledPayload = TeamEnvelope & {
6883
+ /** `'finding:<raise_seq>'` | `'step:<step_id>:<attempt>'`. */
6884
+ subject: string;
6885
+ finding_id: string | null;
6886
+ trigger: 'unresolved_high' | 'member_step' | (string & {});
6887
+ question: string;
6888
+ positions: { by: string; position: string; reason: string }[];
6889
+ evidence: string;
6890
+ excluded_seats: string[];
6891
+ transcript: number[];
6892
+ };
6893
+
6894
+ export type TeamCouncilRuledPayload = TeamEnvelope & {
6895
+ subject: string;
6896
+ verdict: 'yes' | 'no' | 'no_verdict' | (string & {});
6897
+ reason: 'no_quorum' | 'seats_benched' | 'error' | 'timeout' | 'cap' | (string & {}) | null;
6898
+ task_id: string | null;
6899
+ consensus: boolean;
6900
+ agreement_pct: number;
6901
+ dissent: string[];
6902
+ returned: number;
6903
+ seated: number;
6904
+ };
6905
+
6906
+ /** DES-001 §7 `teamLedger.findings[]` (camelCase). */
6907
+ export interface TeamLedgerFinding {
6908
+ findingId: string;
6909
+ monitorId: string;
6910
+ seat: string;
6911
+ severity: 'high' | 'medium' | (string & {});
6912
+ path: string;
6913
+ line: number;
6914
+ evidence: string;
6915
+ claim: string;
6916
+ suggestion: string | null;
6917
+ tree: string;
6918
+ inDiff: boolean;
6919
+ finalLine: number | null;
6920
+ corroboratedBy: string[];
6921
+ delivery: 'injected' | 'not_delivered' | (string & {});
6922
+ status: 'accepted' | 'declined' | 'withdrawn' | 'unanswered' | 'superseded' | (string & {});
6923
+ workerReason: string | null;
6924
+ monitorReply: { kind: 'hold' | 'withdraw' | (string & {}); reason: string } | null;
6925
+ dispute: {
6926
+ verdict: 'yes' | 'no' | 'no_verdict' | (string & {});
6927
+ agreementPct: number | null;
6928
+ dissent: number | null;
6929
+ seats: string[];
6930
+ reason: string | null;
6931
+ } | null;
6932
+ /**
6933
+ * (T6, api-types 0.42.0) The enclosing location the finding id is minted over (git's funcname
6934
+ * heuristic). Omitted when file-level.
6935
+ */
6936
+ anchor?: string;
6937
+ /** (T6, api-types 0.42.0) The attempt that first raised it, when it was carried into a redriven attempt. Omitted otherwise. */
6938
+ carriedFromAttempt?: number;
6939
+ }
6940
+
6941
+ /**
6942
+ * (T6, DES-002 §8.8; api-types 0.42.0) The PA's review of a member's step, as the attempt that
6943
+ * carried the review recorded it.
6944
+ */
6945
+ export interface TeamStepReview {
6946
+ stepId: string;
6947
+ /** The member attempt the review is about. */
6948
+ reviewedAttempt: number;
6949
+ verdict: 'accepted' | 'rejected' | (string & {});
6950
+ to: 'member' | 'pa' | (string & {}) | null;
6951
+ reason: string;
6952
+ /** On a rejection: `true` the member held (`HOLD`), `false` it accepted, `null` no answer on record. */
6953
+ held: boolean | null;
6954
+ memberReason: string | null;
6955
+ /** The one-off council a hold convened. */
6956
+ dispute: TeamLedgerFinding['dispute'];
6957
+ }
6958
+
6959
+ /** DES-001 §7 `teamLedger` without its envelope: the fold of one attempt's team stream. */
6960
+ export interface TeamLedger {
6961
+ finalPass: 'completed' | 'timed_out' | 'skipped' | 'stream_gap' | (string & {});
6962
+ renderedToJudge: boolean;
6963
+ teamPause: boolean;
6964
+ monitors: {
6965
+ monitorId: string;
6966
+ seat: string;
6967
+ batches: number;
6968
+ status: 'completed' | 'budget_exhausted' | 'failed' | 'timed_out' | (string & {});
6969
+ error: string | null;
6970
+ }[];
6971
+ findings: TeamLedgerFinding[];
6972
+ rejected: { malformed: number; belowBar: number; unconfirmed: number; duplicate: number };
6973
+ /** (T6, api-types 0.42.0) The PA's reviews of member steps this attempt carried. Omitted when none. */
6974
+ stepReviews?: TeamStepReview[];
6975
+ }
6976
+
6977
+ export type TeamLedgerFoldedPayload = TeamEnvelope & {
6978
+ final_pass: 'completed' | 'timed_out' | 'skipped' | 'stream_gap' | (string & {});
6979
+ ledger: TeamLedger;
6980
+ transport: 'bus' | 'none' | (string & {});
6981
+ transcript: {
6982
+ from_event_id: number;
6983
+ to_event_id: number;
6984
+ count: number;
6985
+ truncated: boolean;
6986
+ events: { event_id: number; event_type: string; payload: unknown }[];
6987
+ };
6988
+ };
6989
+
6990
+ /** `gate.opened`'s kind-specific fields, discriminated on `kind`. */
6991
+ export type TeamGateOpenedPayload = TeamEnvelope & { gate_id: string } & (
6992
+ | { kind: 'unit_review'; ledger_ref: string | null; ledger_source: 'folded' | 'synthesized' | 'no_bus' | (string & {}) }
6993
+ | {
6994
+ kind: 'plan_approval';
6995
+ reviewing_ord: number;
6996
+ plan_rev: number;
6997
+ band: string;
6998
+ high_risk: boolean;
6999
+ mode: 'auto' | 'manual' | (string & {});
7000
+ reason: 'manual_mode' | 'high_risk' | 'into_high_risk' | 'override' | (string & {});
7001
+ diff: { from_rev: number | null; added: string[] };
7002
+ }
7003
+ | { kind: 'team_dispute'; finding_ids: string[] }
7004
+ | { kind: 'team_transport'; fact: string; reason: string }
7005
+ );
7006
+
7007
+ export type TeamGateDecidedPayload = TeamEnvelope & {
7008
+ gate_id: string;
7009
+ kind: 'unit_review' | 'plan_approval' | 'team_dispute' | 'team_transport' | (string & {});
7010
+ decision:
7011
+ | 'allow'
7012
+ | 'deny'
7013
+ | 'paused'
7014
+ | 'human_approved'
7015
+ | 'human_amended'
7016
+ | 'human_rejected'
7017
+ | (string & {});
7018
+ combined: boolean | null;
7019
+ team_pause: boolean;
7020
+ unresolved: string[];
7021
+ };
7022
+
7023
+ export type TeamPathEndedPayload = TeamEnvelope & {
7024
+ status: 'completed' | 'failed' | 'cancelled' | (string & {});
7025
+ };
7026
+
7027
+ /** Each team event type's payload. */
7028
+ export interface TeamEventPayloads {
7029
+ 'wicked.team.path.started': TeamPathStartedPayload;
7030
+ 'wicked.team.path.scored': TeamPathScoredPayload;
7031
+ 'wicked.team.plan.proposed': TeamPlanProposedPayload;
7032
+ 'wicked.team.plan.revised': TeamPlanRevisedPayload;
7033
+ 'wicked.team.plan.accepted': TeamPlanAcceptedPayload;
7034
+ 'wicked.team.plan.refused': TeamPlanRefusedPayload;
7035
+ 'wicked.team.member.joined': TeamMemberJoinedPayload;
7036
+ 'wicked.team.member.left': TeamMemberLeftPayload;
7037
+ 'wicked.team.step.claimed': TeamStepClaimedPayload;
7038
+ 'wicked.team.checkpoint.reached': TeamCheckpointReachedPayload;
7039
+ 'wicked.team.finding.raised': TeamFindingRaisedPayload;
7040
+ 'wicked.team.advice.delivered': TeamAdviceDeliveredPayload;
7041
+ 'wicked.team.advice.answered': TeamAdviceAnsweredPayload;
7042
+ 'wicked.team.help.requested': TeamHelpRequestedPayload;
7043
+ 'wicked.team.help.answered': TeamHelpAnsweredPayload;
7044
+ 'wicked.team.change.requested': TeamChangeRequestedPayload;
7045
+ 'wicked.team.step.completed': TeamStepCompletedPayload;
7046
+ 'wicked.team.step.reviewed': TeamStepReviewedPayload;
7047
+ 'wicked.team.finding.settled': TeamFindingSettledPayload;
7048
+ 'wicked.team.council.called': TeamCouncilCalledPayload;
7049
+ 'wicked.team.council.ruled': TeamCouncilRuledPayload;
7050
+ 'wicked.team.ledger.folded': TeamLedgerFoldedPayload;
7051
+ 'wicked.team.gate.opened': TeamGateOpenedPayload;
7052
+ 'wicked.team.gate.decided': TeamGateDecidedPayload;
7053
+ 'wicked.team.path.ended': TeamPathEndedPayload;
7054
+ }
7055
+
7056
+ /** One team bus row, discriminated on `event_type`. */
7057
+ export type TeamBusEvent = {
7058
+ [K in TeamEventType]: { event_type: K; payload: TeamEventPayloads[K] };
7059
+ }[TeamEventType];
7060
+
7061
+ // ── The crew team surface (DES-TEAMING-002 T8; api-types 0.42.0) ─────────────────────────────
7062
+ //
7063
+ // Commands go through the API, facts go on the bus (§4.0). Crew RELAYS the engine's
7064
+ // `wicked.team.*` rows onto `/ws`, READS them back per run, and POSTs commands to the engine. It
7065
+ // publishes no team row.
7066
+
7067
+ /**
7068
+ * A `/ws` frame carrying one `wicked.team.*` bus row, relayed verbatim (the `interactiveEvent`
7069
+ * pattern). `project_id` is set when the run's membership files it, as on every other frame.
7070
+ */
7071
+ export interface TeamEventFrame {
7072
+ type: 'teamEvent';
7073
+ /** The whole bus row as wicked-bus delivers it. */
7074
+ event: TeamBusEvent & { event_id: number; [k: string]: unknown };
7075
+ project_id?: string;
7076
+ }
7077
+
7078
+ /** One `wicked.team.*` bus row as `GET /runs/:id/team` returns it. */
7079
+ export type TeamRow = TeamBusEvent & {
7080
+ event_id: number;
7081
+ /** wicked-bus `emitted_at` (Unix millis). */
7082
+ emitted_at: number;
7083
+ /**
7084
+ * Set on a `wicked.team.ledger.folded` row the unit's gate did NOT read: a `gate.opened
7085
+ * {kind:"unit_review"}` for the same ord and attempt names another ledger (`ledger_ref` null,
7086
+ * a synthesized ledger). `gate.opened.ledger_ref` is the only authority on what the gate read.
7087
+ */
7088
+ unused?: true;
7089
+ };
7090
+
7091
+ /**
7092
+ * One unit's team state in {@link RunTeamResponse}: the engine's persisted snapshot
7093
+ * (`Core.runTeam`; the last five fields are `null` until the unit's attempt folded) plus the
7094
+ * unit's bus rows.
7095
+ */
7096
+ export interface RunTeamUnit {
7097
+ ord: number;
7098
+ /** `null` until the unit dispatched. */
7099
+ transport: 'bus' | 'none' | (string & {}) | null;
7100
+ reason: string | null;
7101
+ /** Where the gate's ledger came from: folded by the supervisor, synthesized by the worker on the final-pass timeout, or local (no bus). */
7102
+ ledgerSource: 'folded' | 'synthesized' | 'no_bus' | (string & {}) | null;
7103
+ /** The supervisor row the gate read (`"ledger.folded#<ord>:<attempt>"`); `null` for a synthesized or local ledger. */
7104
+ ledgerRef: string | null;
7105
+ finalPass: 'completed' | 'timed_out' | 'skipped' | 'stream_gap' | (string & {}) | null;
7106
+ /** An unresolved HIGH (or an incomplete record) stands without a council YES: the run pauses `team_dispute`. */
7107
+ teamPause: boolean | null;
7108
+ /** How many findings the attempt's ledger holds. */
7109
+ findings: number | null;
7110
+ /** The unit's bus rows (`payload.ord` = this ord), every attempt, ordered by `event_id`. Empty when the bus has none. */
7111
+ rows: TeamRow[];
7112
+ /**
7113
+ * The folded ledger the gate read (the `ledger.folded` row `ledgerRef` names), or `null`: not
7114
+ * folded yet, synthesized or local (read `UnitEvidence.team` on the evidence route), or the bus
7115
+ * no longer has the row (the snapshot fields above still stand).
7116
+ */
7117
+ ledger: TeamLedger | null;
7118
+ }
7119
+
7120
+ /**
7121
+ * `GET /runs/:id/team`. The run's team transport and per-unit team state. A run that is not a team
7122
+ * run (free text, a registered def: bug, chat, onboarding, …) answers `teamed: false`,
7123
+ * `transport: null` and `units: []` — nothing to render, and no outage.
7124
+ */
7125
+ export interface RunTeamResponse {
7126
+ runId: string;
7127
+ /** Whether the run is a team run at all. `false` ⇒ `transport` and `reason` are `null`. */
7128
+ teamed: boolean;
7129
+ /**
7130
+ * For a team run: `"bus"`; `"none"` (the teamed run fell back to no team transport: render the
7131
+ * "team transport unavailable" banner with `reason`); `"pending"` (not decided yet);
7132
+ * `"unavailable"` (this daemon has no bus: the run is paused `team_transport` until the operator
7133
+ * answers it). `null` for a run that is not a team run.
7134
+ */
7135
+ transport: 'bus' | 'none' | 'pending' | 'unavailable' | (string & {}) | null;
7136
+ reason: string | null;
7137
+ /** The first bus event id of the run's stream. */
7138
+ streamFloor: number | null;
7139
+ planRev: number | null;
7140
+ /** The required fact the run waits on (its event type), if any. */
7141
+ pending: string | null;
7142
+ /** The run is terminal and its end is on record (a run that is not a team run: its status is terminal). */
7143
+ ended: boolean;
7144
+ units: RunTeamUnit[];
7145
+ /** The run-level rows (`payload.ord` null: `path.*`, `plan.*`), ordered by `event_id`. */
7146
+ rows: TeamRow[];
7147
+ }
7148
+
7149
+ /**
7150
+ * `POST /runs/:id/plan` body: a plan edit. At a `plan_approval` gate the edit IS the gate answer
7151
+ * (the engine proposes, floor-fills and accepts it, or refuses it and re-opens the gate) and the
7152
+ * route answers `{ status }`. Anywhere else on a live planned run (running, or paused at another
7153
+ * gate) it is a MID-RUN edit (`Core.proposePlan`): `plan.steps` are the steps to ADD, held and
7154
+ * applied at the run's next step boundary — its author approved it, so no gate opens — and the
7155
+ * route answers {@link PlanProposalResponse}. A mid-run edit carries no `touch` / `override`.
7156
+ */
7157
+ export interface EditPlanBody {
7158
+ plan: LaunchPlan;
7159
+ /**
7160
+ * The caller's id for this edit: the engine's idempotency key for a mid-run edit, so a retried
7161
+ * POST carrying the same id proposes once (`duplicate: true`). Omitted ⇒ the daemon mints a
7162
+ * fresh UUID for that POST, so an omitted-id retry is a SECOND edit — send one to retry safely.
7163
+ * A gate edit is keyed on its gate, not on this id.
7164
+ */
7165
+ requestId?: string;
7166
+ }
7167
+
7168
+ /** `POST /runs/:id/plan` at a `plan_approval` gate: the run's status after the gate answer. */
7169
+ export interface EditPlanGateResponse {
7170
+ status: SessionStatus;
7171
+ }
7172
+
7173
+ /**
7174
+ * `POST /runs/:id/plan` mid-run: the engine's `PlanProposal` (`Core.proposePlan`), what the held
7175
+ * edit does to the run — read off the dry run it was validated by, since the author's own edit
7176
+ * opens no gate. Refusals (a plan awaiting approval — edit it at the gate —, a started deliver
7177
+ * step, a finished or unplanned run, an edit the revision refuses) answer 409 with the engine
7178
+ * reason.
7179
+ */
7180
+ export interface PlanProposalResponse {
7181
+ /** The `plan.proposed` id the edit is published under. */
7182
+ proposal_id: string;
7183
+ /** `true`: this `requestId` was already taken — nothing new is held or published. */
7184
+ duplicate: boolean;
7185
+ /** The floor band of the rev the edit makes (`"40-69"`, …); `null` for a duplicate. */
7186
+ band: string | null;
7187
+ /** §8.5's high-risk rule on that rev: `true` = the edit moved the run into high risk. `null` for a duplicate. */
7188
+ high_risk: boolean | null;
7189
+ /** The floor phase types (catalog ids) the edit adds; empty for a duplicate. */
7190
+ floor_added: string[];
7191
+ }
7192
+
7193
+ /** `POST /runs/:id/plan`: a gate answer (`status`) or a mid-run proposal (`proposal_id`). */
7194
+ export type EditPlanResponse = EditPlanGateResponse | PlanProposalResponse;
7195
+
7196
+ /** `POST /team/outbox/replay`: the engine's replay of `<state home>/team-outbox.ndjson`. */
7197
+ export interface TeamOutboxReplayReport {
7198
+ /** `[idempotency key, bus event id]` of every fact that reached the bus, in publish order. */
7199
+ published: Array<[string, number]>;
7200
+ superseded: number;
7201
+ invalid: number;
7202
+ remaining: number;
7203
+ /** `[run id, reason]` per lane that stopped. */
7204
+ failures: Array<[string, string]>;
7205
+ }
7206
+
7207
+ /** One phase type of the engine's catalog (`GET /catalog`, `Core.catalog`), in catalog order —
7208
+ * the row studio's phase picker renders. */
7209
+ export interface CatalogEntry {
7210
+ id: string;
7211
+ kind: StageKindPhase;
7212
+ role: PhaseRole;
7213
+ gate: GateSpec;
7214
+ gate_type: GateType | null;
7215
+ executes_code: boolean;
7216
+ /** `"tool"`: a step of this entry supplies its own command (`deliver`); else `"agent"`. */
7217
+ executor: 'agent' | 'tool';
7218
+ validator_pin: string | null;
7219
+ /** The entry carries a validator pin (a step may not remove or swap it). */
7220
+ pinned: boolean;
7221
+ /** That pin is the evidence floor. */
7222
+ evidence_floor: boolean;
7223
+ /**
7224
+ * The entry declares re-verified evidence: a step of it is an acceptance requirement of the run
7225
+ * that contains it (`test`, `domain_coverage`). ABSENT from an engine before the flag (api-types
7226
+ * 0.46.0) — the daemon then fails a plan run's acceptance closed rather than guess.
7227
+ */
7228
+ verified_evidence?: boolean;
7229
+ skill_ref: string | null;
7230
+ /** The entry's one-line description; `null` when it has none. */
7231
+ description: string | null;
7232
+ }
7233
+
7234
+ /** `GET /catalog`. */
7235
+ export interface CatalogResponse {
7236
+ entries: CatalogEntry[];
7237
+ }
7238
+
7239
+ /** `POST /plans/preview` body: a draft plan, previewed as the `POST /runs {plan}` launch with the
7240
+ * same fields would decide it. */
7241
+ export interface PlanPreviewBody {
7242
+ plan: LaunchPlan;
7243
+ /** Only matters to a preset name, so it does not change a plan's preview. */
7244
+ projectId?: string;
7245
+ /** The launch's gate mode (`humanConfirm` on `POST /runs`); absent = auto. */
7246
+ humanConfirm?: string;
7247
+ /** The registered repo the launch would run on: a behavioural `touch` set is scored against its
7248
+ * code graph at the base the launch would start from (read locally, no fetch). An unregistered
7249
+ * repo is a 400. Absent ⇒ the fail-closed score (`graph: "unavailable"`). */
7250
+ repoRef?: string;
7251
+ /** The launch's delivery (`deliver` on `POST /runs`): `"pr"` hands the engine the launch's
7252
+ * deliver step, so the preview's floor carries `deliver` as the launch's would. Absent or
7253
+ * `"none"` ⇒ no deliver step (a plan launch's own default). */
7254
+ deliver?: 'pr' | 'none';
7255
+ }
7256
+
7257
+ /**
7258
+ * `POST /plans/preview`: the engine's `PlanPreview` (`Core.previewPlan`) — what the launch would
7259
+ * compute (precheck, intent score, floor fill, approval matrix, planning checks), persisting and
7260
+ * publishing nothing. A refusal (the launch's own, an unregistered repo) is a 400.
7261
+ */
7262
+ export interface PlanPreviewResponse {
7263
+ /** The intent score (§8.2), its deterministic part, one reason per contribution. */
7264
+ score: number;
7265
+ deterministic: number;
7266
+ reasons: string[];
7267
+ /** The destructive signal the floor fill reads. */
7268
+ destructive: boolean;
7269
+ /** The floor band the score lands in (`"40-69"`, `"70-100"`, …). */
7270
+ band: string;
7271
+ high_risk: boolean;
7272
+ /** The floor phase types the plan owes, in order. The steps the floor ADDED are the `steps`
7273
+ * with `added_by: "floor"`. */
7274
+ floor: string[];
7275
+ /** The floor override as recorded (manual mode only). */
7276
+ floor_override: TeamPlanOverride | null;
7277
+ /** The floor-filled steps; each carries `added_by`, and `floor_reason` when the floor added it. */
7278
+ steps: TeamPlanStep[];
7279
+ /** The def the run would plan from (`compose` of `steps`). */
7280
+ def: WorkflowDef;
7281
+ /** `true` ⇒ the launch would pause at a `plan_approval` gate before its first unit. */
7282
+ pauses: boolean;
7283
+ pause_reason: 'manual_mode' | 'high_risk' | 'override' | (string & {}) | null;
7284
+ /** `"ready"`: the score read the repo's code graph. `"not_needed"`: a docs-only touch set (or no
7285
+ * creator and no touch set). `"unavailable"`: the fail-closed score (no repo, no or a stale
7286
+ * graph — `reasons` says which). `"pending_pa_scope"` (wicked-core#633, DES-TEAMING-002 X1;
7287
+ * api-types 0.47.0): a creator plan with no `touch` set — the launch runs the PA's read-only
7288
+ * `pa-scope` step first and scores the plan from its answer, so `score`, `band` and the floor
7289
+ * shown are the baseline's with `pa-scope` first, NOT the plan's final risk, and `pauses` is
7290
+ * manual mode's alone (an auto-mode launch may still pause once the PA's scope lands high). */
7291
+ graph: 'ready' | 'not_needed' | 'unavailable' | 'pending_pa_scope';
7292
+ }
7293
+
7294
+ // ── Standing orders (Studio OS behaviour 10; api-types 0.50.0) ─────────────────────────────────
7295
+
7296
+ /**
7297
+ * A standing order's structured rule — parsed from plain words by a seat and CONFIRMED by the
7298
+ * operator before it is stored. `trigger.phase` is the phase the gate reviews (the reviewed unit's
7299
+ * `phase_ref`) or `*`. The invariant: `approve` never answers the deliver gate, a plan approval,
7300
+ * an escalation, a failure or a triage gate, and never a finding (the create route refuses a rule
7301
+ * that approves `deliver`, `plan_approval` or a finding with a 400 `order_refused`).
7302
+ */
7303
+ export interface StandingOrderRule {
7304
+ scope: { kind: 'all' } | { kind: 'project'; projectId: string };
7305
+ /** `band` (api-types 0.53.0, optional): the order matches only a run whose accepted plan landed in
7306
+ * that band (`"0-19"`, …); a run with no scored plan never matches a band-scoped order.
7307
+ * `preset` (api-types 0.56.0, optional): only a run launched from that preset (`team_plan.preset`).
7308
+ * `phase: "plan_approval"` with `action: "approve"` is a TRUST RECEIPT (0.56.0): accepted only
7309
+ * with `band: "0-19"`, a `preset` and a `project` scope, and it answers a run's plan gate only
7310
+ * when that open gate itself reports band 0-19 and no high risk. `deliver` is never approvable. */
7311
+ trigger: { kind: 'gate'; phase: string; band?: string | undefined; preset?: string | undefined } | { kind: 'finding'; severity: 'high' | 'medium' | '*' };
7312
+ action: 'approve' | 'hold' | 'notify';
7313
+ activeWhen: 'away' | 'always';
7314
+ }
7315
+
7316
+ export interface StandingOrder {
7317
+ id: string;
7318
+ /** The operator's own words. */
7319
+ text: string;
7320
+ rule: StandingOrderRule;
7321
+ /** Unix millis. */
7322
+ createdAt: number;
7323
+ }
7324
+
7325
+ /** An outbound message a `notify` order wanted sent. Only ever QUEUED — the daemon sends nothing. */
7326
+ export interface QueuedStandingMessage {
7327
+ id: string;
7328
+ orderId: string;
7329
+ orderText: string;
7330
+ runId: string | null;
7331
+ text: string;
7332
+ /** Unix millis. */
7333
+ at: number;
7334
+ status: 'queued';
7335
+ }
7336
+
7337
+ /** `GET /standing-orders`, and the answer to `PUT /standing-orders/away`. */
7338
+ export interface StandingOrdersState {
7339
+ away: boolean;
7340
+ /** Unix millis the flag turned on; `null` while present. */
7341
+ awaySince: number | null;
7342
+ orders: StandingOrder[];
7343
+ outbox: QueuedStandingMessage[];
7344
+ }
7345
+
7346
+ /** `PUT /standing-orders/away`. */
7347
+ export interface StandingAwayBody {
7348
+ away: boolean;
7349
+ }
7350
+
7351
+ /** `POST /standing-orders/parse` — the plain words one seat reads. */
7352
+ export interface ParseStandingOrderBody {
7353
+ text: string;
7354
+ }
7355
+
7356
+ /** `POST /standing-orders/parse`'s 200: the rule to show back for confirmation. `refused` is set
7357
+ * when storing it would be refused (the invariant), in plain words. A 422 carries the seat's own
7358
+ * `answer` when it could not produce a rule; 409 = no seat can take a turn; 502 = the seat failed. */
7359
+ export interface ParsedStandingOrder {
7360
+ rule: StandingOrderRule;
7361
+ seat: string;
7362
+ refused?: string;
7363
+ }
7364
+
7365
+ /** `POST /standing-orders` — the confirmed rule. 201 `{ order }`. */
7366
+ export interface CreateStandingOrderBody {
7367
+ text: string;
7368
+ rule: StandingOrderRule;
7369
+ }
7370
+
7371
+ // ── Decided-gate history (api-types 0.53.0) ────────────────────────────────────────────────────
7372
+
7373
+ /**
7374
+ * One decided gate — `GET /gates/decided?since=<unix ms>` (newest first, at most 1000). One row per
7375
+ * `gate.decided` audit line, joined to the engine's gate row for the same `(run, ord)` and to the
7376
+ * run. What a skin needs for a seat's track record on a step, and for the preview of a proposed
7377
+ * standing order (`phase` and `orderApprovable` are the order evaluator's own readings).
7378
+ */
7379
+ export interface DecidedGate {
7380
+ runId: string;
7381
+ /** The unit the gate paused before; `null` when the decision named none (a plan edit). */
7382
+ ord: number | null;
7383
+ /** Unix millis. */
7384
+ decidedAt: number;
7385
+ decision: 'approve' | 'request_changes' | 'reject' | 'edit_plan';
7386
+ /** Who decided: a person's actor id, or `standing-order:<id>`. */
7387
+ actor: string;
7388
+ /** A standing order decided it (the actor is `standing-order:<id>`). */
7389
+ byOrder: boolean;
7390
+ /** The engine's gate kind (`def`, `run_level`, `terminal`, `deliver`, `plan_approval`, …); `null` when unknown. */
7391
+ gateKind: string | null;
7392
+ /** The phase an order's trigger matches (`intake`, or the reviewed unit's phase); `null` when unknown. */
7393
+ phase: string | null;
7394
+ projectId: string | null;
7395
+ /** The band the run's accepted plan landed in; `null` for an unscored run. */
7396
+ band: string | null;
7397
+ /** The unit whose work the gate judged (the newest creator before the gate) and its seat; `null` when none. */
7398
+ creator: { seat: string; phase: string | null; ord: number } | null;
7399
+ /** A standing order could have approved this gate (a phase-review gate of a run that lands no doctrine). */
7400
+ orderApprovable: boolean;
7401
+ }
7402
+
7403
+ /** `GET /gates/decided`'s 200. A malformed `since` answers 400. */
7404
+ export interface DecidedGatesResponse {
7405
+ gates: DecidedGate[];
7406
+ }
7407
+
7408
+ // ── Freeze deliveries (api-types 0.55.0) ───────────────────────────────────────────────────────
7409
+
7410
+ /**
7411
+ * `GET|PUT /deliveries/freeze` — the one switch that holds every deliver gate. While `frozen`, an
7412
+ * approve of a gate that would run a deliver unit answers 409 `{code: "deliveries_frozen"}` and
7413
+ * `POST /runs/:id/deliver` refuses the same way; the gate stays open, so unfreezing lets the same
7414
+ * approve through. A run launched with `deliverGate: "auto"` has no gate and is not held.
7415
+ * Audited as `deliveries.frozen {reason?}` / `deliveries.unfrozen`; a restart keeps the freeze.
7416
+ */
7417
+ export interface DeliveryFreezeState {
7418
+ frozen: boolean;
7419
+ /** ISO time it was turned on; `null` while thawed. */
7420
+ since: string | null;
7421
+ /** The actor id that turned it on; `null` while thawed. */
7422
+ by: string | null;
7423
+ reason: string | null;
7424
+ }
7425
+
7426
+ /** `PUT /deliveries/freeze`. `reason` (1–500 chars) is kept only when freezing. */
7427
+ export interface PutDeliveryFreezeBody {
7428
+ frozen: boolean;
7429
+ reason?: string;
7430
+ }
7431
+
7432
+ // ── MCP tools registry (DES-MCP-TOOLS-001 §5, §8, slice S2; api-types 0.58.0) ─────────────────
7433
+
7434
+ /**
7435
+ * How crew reaches an upstream: an MCP server over stdio or streamable HTTP, or a plain REST API
7436
+ * wrapped as tools from its OpenAPI document (`rest`, api-types 0.64.0).
7437
+ */
7438
+ export type McpUpstreamKind = 'mcp-stdio' | 'mcp-http' | 'rest';
7439
+ /**
7440
+ * A tool's class, from its own `tools/list` annotations (never from a carrier): `read` when
7441
+ * `readOnlyHint` is true; else `destructive` unless `destructiveHint` is false; else `write`. A tool
7442
+ * with no annotations, or with neither `readOnlyHint` nor `destructiveHint`, is `write`. An
7443
+ * operator's `classOverride` wins.
7444
+ */
7445
+ export type McpToolClass = 'read' | 'write' | 'destructive';
7446
+ /**
7447
+ * `registered`: the schema the operator saved is the one the server lists. `unregistered`: the
7448
+ * schema changed, or the server added the tool after the save; it is denied until previewed and
7449
+ * saved again. `gone`: the server no longer lists it (kept so old records resolve).
7450
+ */
7451
+ export type McpToolStatus = 'registered' | 'unregistered' | 'gone';
7452
+ /** `failing` after 3 consecutive failed probes; `never` = no probe has succeeded yet. */
7453
+ export type McpHealthState = 'ok' | 'failing' | 'never';
7454
+ /** `none` = the server needs no secret; `missing` = its reference resolves to nothing. */
7455
+ export type McpAuthState = 'none' | 'set' | 'missing';
7456
+
7457
+ /** The spec's tool annotations, only the fields it defines. */
7458
+ export interface McpToolAnnotations {
7459
+ title?: string;
7460
+ readOnlyHint?: boolean;
7461
+ destructiveHint?: boolean;
7462
+ idempotentHint?: boolean;
7463
+ openWorldHint?: boolean;
7464
+ }
7465
+
7466
+ /**
7467
+ * Where a server's secret comes from and where crew injects it. The registry stores this
7468
+ * reference only, never a value. `ref` is `keychain:wicked-mcp/<name>` (written by
7469
+ * `PUT /mcp/servers/:name/secret`) or `env:<NAME>` (the daemon's env). An `mcp-stdio` server gets
7470
+ * the value in env variable `env`; an `mcp-http` or `rest` server in header `header`, after `prefix`.
7471
+ */
7472
+ export interface McpAuthConfig {
7473
+ ref: string;
7474
+ env?: string;
7475
+ header?: string;
7476
+ prefix?: string;
7477
+ }
7478
+
7479
+ /**
7480
+ * `POST /mcp/servers/preview`. `mcp-stdio` takes `command` (+ `args`); `mcp-http` takes `url`.
7481
+ * `rest` takes `url`, the base URL every call of the API is pinned to (its scheme, host and port,
7482
+ * and its path as a prefix), plus its OpenAPI 3 document: `openapiUrl` (fetched by crew) or
7483
+ * `openapi` (the document itself, pasted), never both. `operations` picks which operations become
7484
+ * tools (by `operationId` or tool name); absent = all of them.
7485
+ */
7486
+ export interface McpServerConfigBody {
7487
+ /** 1–63 of `a-z 0-9 _ -`; the `<server>` of the subject `mcp:<server>/<tool>`. */
7488
+ name: string;
7489
+ kind: McpUpstreamKind;
7490
+ command?: string;
7491
+ args?: string[];
7492
+ url?: string;
7493
+ auth?: McpAuthConfig | null;
7494
+ /** `rest` only: where crew fetches the OpenAPI 3 document (JSON or YAML). */
7495
+ openapiUrl?: string;
7496
+ /** `rest` only: the OpenAPI 3 document itself (instead of `openapiUrl`); at most 1 MB as JSON. */
7497
+ openapi?: Record<string, unknown>;
7498
+ /** `rest` only: the operations to wrap, by `operationId` or tool name; absent = all. */
7499
+ operations?: string[];
7500
+ /**
7501
+ * The secret VALUE this server's keychain entry will hold (wicked-crew#719). The preview is
7502
+ * probed with it and it is held with the preview; `POST /mcp/servers` writes it to the OS store
7503
+ * as part of the save, so the secret and the registry row commit together — studio used to write
7504
+ * the keychain itself first, and a failure between the two writes left a keychain entry with no
7505
+ * server, or a server whose secret never landed. Requires `auth.ref` =
7506
+ * `keychain:wicked-mcp/<name>`; `501` when the platform has no OS secret store. It is in no
7507
+ * response, log, audit entry or file.
7508
+ */
7509
+ secret?: string;
7510
+ }
7511
+
7512
+ /**
7513
+ * How a `rest` tool turns its arguments into one HTTP request (api-types 0.64.0). The arguments are
7514
+ * an ALLOWLIST: an argument that no map names is dropped, never sent (its name is disclosed in the
7515
+ * result's `_meta["wicked/rest"].droppedArgs`). The request goes only to the server's base URL: a
7516
+ * request that would leave its scheme, host, port or base path, or a redirect to another host, is
7517
+ * refused before (or instead of) being followed, and recorded as a `guard_error`.
7518
+ */
7519
+ export interface McpRestMapping {
7520
+ method: 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
7521
+ /** The OpenAPI path, e.g. `/issues/{id}`; joined onto the base URL's path. */
7522
+ pathTemplate: string;
7523
+ /** argument → `{variable}` of `pathTemplate`. */
7524
+ pathMap: Record<string, string>;
7525
+ /** argument → query parameter. */
7526
+ queryMap: Record<string, string>;
7527
+ /** argument → request header (never the auth header, `Host`, `Cookie` or a hop-by-hop header). */
7528
+ headerMap: Record<string, string>;
7529
+ /** argument → top-level key of the JSON request body; `null` when the body is one argument or none. */
7530
+ bodyMap: Record<string, string> | null;
7531
+ /** The argument that is the whole JSON request body; `null` when the body is mapped by key or absent. */
7532
+ bodyArg: string | null;
7533
+ /** Every argument that is sent: the union of the maps and `bodyArg`. */
7534
+ argAllowlist: string[];
7535
+ timeoutMs: number;
7536
+ }
7537
+
7538
+ export interface McpHealth {
7539
+ state: McpHealthState;
7540
+ consecutiveFailures: number;
7541
+ checkedAt: string | null;
7542
+ /** The last probe's error, with any secret scrubbed out. */
7543
+ lastError: string | null;
7544
+ }
7545
+
7546
+ export interface McpTool {
7547
+ name: string;
7548
+ /** A `rest` tool's request mapping (api-types 0.64.0); `null` for an MCP server's tool. */
7549
+ rest?: McpRestMapping | null;
7550
+ /** The policy token: `mcp:<server>/<tool>`. */
7551
+ subject: string;
7552
+ description: string | null;
7553
+ annotations: McpToolAnnotations | null;
7554
+ inputSchema: Record<string, unknown> | null;
7555
+ derivedClass: McpToolClass;
7556
+ classOverride: McpToolClass | null;
7557
+ /** `classOverride ?? derivedClass`: what a policy sees. */
7558
+ class: McpToolClass;
7559
+ enabled: boolean;
7560
+ status: McpToolStatus;
7561
+ /** The saved schema's hash; `null` = never saved. */
7562
+ schemaHash: string | null;
7563
+ /** The hash the last probe saw. */
7564
+ observedSchemaHash: string;
7565
+ }
7566
+
7567
+ export interface McpServer {
7568
+ name: string;
7569
+ kind: McpUpstreamKind;
7570
+ command: string | null;
7571
+ args: string[];
7572
+ url: string | null;
7573
+ auth: McpAuthConfig | null;
7574
+ /** `rest` only (api-types 0.64.0): the OpenAPI URL, `null` when the document was pasted. */
7575
+ openapiUrl?: string | null;
7576
+ /** `rest` only: the operations that were picked; `null` = all. */
7577
+ operations?: string[] | null;
7578
+ authState: McpAuthState;
7579
+ enabled: boolean;
7580
+ health: McpHealth;
7581
+ registeredAt: string;
7582
+ updatedAt: string;
7583
+ tools: McpTool[];
7584
+ /** Over the tools that are not `gone`. */
7585
+ counts: { total: number; enabled: number; registered: number; read: number; write: number; destructive: number };
7586
+ }
7587
+
7588
+ /**
7589
+ * A server configured in a CLI's own home, by name only (never its command, URL or env).
7590
+ * `origin: 'worker'` is a server in a seat's worker home, which no wicked-handed server may be.
7591
+ */
7592
+ export interface McpDiscoveredServer {
7593
+ name: string;
7594
+ cli: string;
7595
+ origin: 'operator' | 'worker';
7596
+ /** The config file, relative to `~` or `<worker home>`. */
7597
+ source: string;
7598
+ /** Whether a registered server has the same name. */
7599
+ managed: boolean;
7600
+ }
7601
+
7602
+ /** `GET /mcp/servers`. */
7603
+ export interface McpServersResponse {
7604
+ servers: McpServer[];
7605
+ discovered: McpDiscoveredServer[];
7606
+ }
7607
+
7608
+ /** The difference between the registered tools and a probe, by tool name. */
7609
+ export interface McpToolDiff {
7610
+ added: string[];
7611
+ removed: string[];
7612
+ changed: string[];
7613
+ unchanged: string[];
7614
+ }
7615
+
7616
+ export interface McpPreviewTool {
7617
+ name: string;
7618
+ /** A `rest` tool's request mapping (api-types 0.64.0); `null` for an MCP server's tool. */
7619
+ rest?: McpRestMapping | null;
7620
+ subject: string;
7621
+ description: string | null;
7622
+ annotations: McpToolAnnotations | null;
7623
+ inputSchema: Record<string, unknown> | null;
7624
+ class: McpToolClass;
7625
+ schemaHash: string;
7626
+ }
7627
+
7628
+ /**
7629
+ * `POST /mcp/servers/preview`'s 200. Save it with `POST /mcp/servers {previewHash}` before
7630
+ * `expiresAt`. `diff` compares with the registered server of the same name, `null` when none.
7631
+ * A server that does not answer is a 502 `probe_failed`.
7632
+ */
7633
+ export interface McpPreviewResponse {
7634
+ previewHash: string;
7635
+ expiresAt: string;
7636
+ server: {
7637
+ name: string;
7638
+ kind: McpUpstreamKind;
7639
+ command: string | null;
7640
+ args: string[];
7641
+ url: string | null;
7642
+ auth: McpAuthConfig | null;
7643
+ openapiUrl?: string | null;
7644
+ operations?: string[] | null;
7645
+ };
7646
+ serverInfo: { name: string; version: string } | null;
7647
+ tools: McpPreviewTool[];
7648
+ diff: McpToolDiff | null;
7649
+ /** `rest` only (api-types 0.64.0): operations that could not be wrapped, each with why. */
7650
+ skipped?: string[];
7651
+ /**
7652
+ * Each tool's decision per phase role × seat × mode under the current policies and approvals,
7653
+ * judged as if this preview were saved now (api-types 0.63.0); see `withdrawOnSave`. `null` when
7654
+ * the engine cannot preview.
7655
+ */
7656
+ policies?: McpPolicyPreviewResponse | null;
7657
+ }
7658
+
7659
+ /** `POST /mcp/servers`. No `previewHash`, or one that is unknown or expired, is a 409. */
7660
+ export interface SaveMcpServerBody {
7661
+ previewHash?: string;
7662
+ }
7663
+
7664
+ /** `PATCH /mcp/servers/:name`. */
7665
+ export interface PatchMcpServerBody {
7666
+ enabled: boolean;
7667
+ }
7668
+
7669
+ /** `PATCH /mcp/tools/:subject` (subject URL-encoded). At least one field. */
7670
+ export interface PatchMcpToolBody {
7671
+ enabled?: boolean;
7672
+ classOverride?: McpToolClass | null;
7673
+ }
7674
+
7675
+ /** `POST /mcp/servers/:name/test`'s 200: a failed probe is `ok: false` and counts toward `failing`. */
7676
+ export interface McpServerTestResponse {
7677
+ ok: boolean;
7678
+ error: string | null;
7679
+ /** `null` when the probe failed. */
7680
+ diff: McpToolDiff | null;
7681
+ server: McpServer;
7682
+ }
7683
+
7684
+ /** `PUT /mcp/servers/:name/secret`: one line, 8–8192 characters. */
7685
+ export interface PutMcpSecretBody {
7686
+ value: string;
7687
+ }
7688
+
7689
+ /** The value is never echoed; `ref` goes into the server's `auth.ref`. 501 = no OS secret store. */
7690
+ export interface McpSecretResponse {
7691
+ ref: string;
7692
+ set: true;
7693
+ }
7694
+
7695
+ // ── MCP broker call path (DES-MCP-TOOLS-001 §6, §8 `POST /mcp/call`; slice S3) ─────────────────
7696
+
7697
+ /** `POST /mcp/call`: what the garden shim sends. `token` is the worker's `WICKED_MCP_TOKEN`. */
7698
+ export interface McpCallBody {
7699
+ token: string;
7700
+ /** `mcp:<server>/<tool>`. */
7701
+ subject: string;
7702
+ /** The tool's arguments (a JSON object); absent = `{}`. */
7703
+ args?: Record<string, unknown>;
7704
+ }
7705
+
7706
+ /** How a brokered call ended. Every outcome but `ok` means the result was not handed back. */
7707
+ export type McpCallOutcome =
7708
+ /** The call ran; `result` is the scrubbed upstream result (it may itself be a tool error, `isError`). */
7709
+ | 'ok'
7710
+ /** Refused by a policy or an engine gate before anything ran (403). The worker continues. */
7711
+ | 'denied'
7712
+ /** Waits for the operator's approval (first use of a server, or the run's mode) (409). */
7713
+ | 'pending_approval'
7714
+ /** The call ran, but the output policy withheld its result (403). */
7715
+ | 'withheld'
7716
+ /** The unit's call budget is spent (429). */
7717
+ | 'budget_exhausted'
7718
+ /** The tool failed 5 times in a row and is refused for 60 s (503). */
7719
+ | 'breaker_open'
7720
+ /** The upstream failed (502) or timed out (504). */
7721
+ | 'upstream_error'
7722
+ /**
7723
+ * The call could not be judged, scrubbed or recorded, or (a `rest` tool) its request would have
7724
+ * left the API's pinned host (`status.errorClass` `rest_host_escape`): refused, fail closed (500).
7725
+ */
7726
+ | 'guard_error';
7727
+
7728
+ /** Who decided a call: an engine gate (`engine:mcp-*`), a steering rule, or the broker itself. */
7729
+ export type McpDecisionBy = string;
7730
+
7731
+ /** `POST /mcp/call`'s answer, on every status. Never carries a secret; `result` only on `ok`. */
7732
+ export interface McpCallResponse {
7733
+ outcome: McpCallOutcome;
7734
+ subject: string;
7735
+ /** The call record's span id (`GET /mcp/usage` and the run's Governance panel resolve it). */
7736
+ callId: string | null;
7737
+ /** The scrubbed `tools/call` result — only when `outcome` is `ok`. */
7738
+ result?: Record<string, unknown>;
7739
+ /** The rules or gates that decided (deny, ask, withheld). */
7740
+ ruleIds: string[];
7741
+ /** Why the result is not handed back; absent on `ok`. */
7742
+ reason?: string;
7743
+ /** What the worker can do instead. */
7744
+ remedy?: string;
7745
+ /** `pending_approval` only: the subject the operator is asked to approve. */
7746
+ pending_approval?: string;
7747
+ /** `denied` / `withheld`: the call was refused (the unit continues). */
7748
+ denied?: true;
7749
+ }
7750
+
7751
+ export type McpCallDecision = 'allow' | 'ask' | 'deny' | 'guard_error';
7752
+
7753
+ /**
7754
+ * One call record (`<state home>/mcp/calls.ndjson`), OTel span shaped. Arguments and results are
7755
+ * NEVER captured: only the subject, the decision, timings and the byte count.
7756
+ */
7757
+ export interface McpCallRecord {
7758
+ /** The run id, or `null` when the token resolved to no unit (a guard error before judging). */
7759
+ traceId: string | null;
7760
+ spanId: string;
7761
+ /** The unit, `<ord>:<attempt>`; `null` with `traceId`. */
7762
+ parentSpanId: string | null;
7763
+ name: 'mcp.tool.call';
7764
+ start: string;
7765
+ end: string;
7766
+ ms: number;
7767
+ status: { code: 'ok' | 'error'; errorClass: string | null };
7768
+ decision: { decision: McpCallDecision; by: McpDecisionBy; ruleIds: string[]; claimId: string | null };
7769
+ outcome: McpCallOutcome;
7770
+ attrs: {
7771
+ 'mcp.subject': string;
7772
+ 'mcp.class': McpToolClass | null;
7773
+ 'mcp.kind': McpUpstreamKind | null;
7774
+ 'wicked.seat': string | null;
7775
+ 'wicked.phase': string | null;
7776
+ 'wicked.carrier': 'shim';
7777
+ 'bytes.out': number;
7778
+ retries: number;
7779
+ };
7780
+ }
7781
+
7782
+ /** The `/ws` frame each call record rides (additive CoreEvent contract: skins that don't know it ignore it). */
7783
+ export interface McpCallCompletedFrame {
7784
+ type: 'mcpCallCompleted';
7785
+ record: McpCallRecord;
7786
+ }
7787
+
7788
+ // ── The unit's tool list (DES-MCP-TOOLS-001 §8, slice S4; api-types 0.65.0) ─────────────────────
7789
+
7790
+ /**
7791
+ * `POST /mcp/tools`: what the garden shim's `list` sends. The token travels in the body, never the
7792
+ * query string, so it stays out of request logs.
7793
+ */
7794
+ export interface McpToolsBody {
7795
+ token: string;
7796
+ }
7797
+
7798
+ /** One tool the token's unit may try, judged now with no arguments. A certain deny is left out. */
7799
+ export interface McpVisibleTool {
7800
+ /** `mcp:<server>/<tool>`. */
7801
+ subject: string;
7802
+ class: McpToolClass;
7803
+ /** `allow` = a call runs; `ask` = it waits for the operator's approval. */
7804
+ decision: 'allow' | 'ask';
7805
+ ruleIds: string[];
7806
+ /** Why a call would ask. */
7807
+ reason?: string;
7808
+ /** The tool's description, from the server's `tools/list`. */
7809
+ description: string | null;
7810
+ /** The tool's argument JSON schema, from `tools/list`. */
7811
+ inputSchema: Record<string, unknown> | null;
7812
+ }
7813
+
7814
+ /** `POST /mcp/tools`'s answer: the unit the token is bound to and its tools. */
7815
+ export interface McpToolsResponse {
7816
+ unit: { runId: string; ord: number; attempt: number; phase: string; seat: string };
7817
+ tools: McpVisibleTool[];
7818
+ }
7819
+
7820
+ // ── MCP policies: the preview matrix and approvals (DES-MCP-TOOLS-001 §4.5, §4.7, §8, slice S6; api-types 0.63.0) ──
7821
+
7822
+ /** A policy decision: `ask` = the call waits for the operator's approval (it does not run). */
7823
+ export type McpDecision = 'allow' | 'ask' | 'deny';
7824
+ /**
7825
+ * The phase role a matrix row stands for: `creator` = a creator phase with full write posture;
7826
+ * `evaluator` = an evaluator phase (read-only); `recon` = a neutral phase with a read-only posture.
7827
+ */
7828
+ export type McpPhaseRole = 'creator' | 'evaluator' | 'recon';
7829
+ /** The run's mode (studio's launch autonomy): Gate every step / Gate by risk / Auto. */
7830
+ export type McpRunMode = 'ask' | 'balanced' | 'autonomous';
7831
+
7832
+ /** One matrix cell: what a call from a unit of this shape would get now. Nothing is recorded. */
7833
+ export interface McpPolicyCell {
7834
+ role: McpPhaseRole;
7835
+ seat: string;
7836
+ mode: McpRunMode;
7837
+ phaseId?: string;
7838
+ decision: McpDecision;
7839
+ /** The class the engine judged (the tool's override, else its annotations; none = `write`). */
7840
+ class: McpToolClass;
7841
+ /** The rules that decided: `engine:*` gates first, then steering rule ids (each a Steering row). */
7842
+ ruleIds: string[];
7843
+ obligations: string[];
7844
+ reason: string | null;
7845
+ }
7846
+
7847
+ /** Whether a tool is approved in each ledger, and by which token (`server` = `mcp:<server>`). */
7848
+ export interface McpApprovalState {
7849
+ /** `MCP-FIRST-USE`: the first use of a server asks in every mode until approved. */
7850
+ firstUse: 'server' | 'tool' | null;
7851
+ /** `MCP-POSTURE-WRITE`: in balanced mode a write asks until approved. */
7852
+ write: 'server' | 'tool' | null;
7853
+ }
7854
+
7855
+ export interface McpPolicyPreviewTool {
7856
+ subject: string;
7857
+ server: string;
7858
+ tool: string;
7859
+ class: McpToolClass;
7860
+ status: McpToolStatus;
7861
+ /** The server and the tool are both enabled. */
7862
+ enabled: boolean;
7863
+ /** What the broker sends the engine: enabled and `registered`; otherwise D-5 denies it. */
7864
+ registered: boolean;
7865
+ approval: McpApprovalState;
7866
+ /** One per role × seat × mode, in `roles`, `seats`, `modes` order. */
7867
+ cells: McpPolicyCell[];
7868
+ }
7869
+
7870
+ /** `POST /mcp/policies/preview`. Every field narrows; none = every tool, role, seat and mode. */
7871
+ export interface McpPolicyPreviewBody {
7872
+ /** One tool: `mcp:<server>/<tool>`. */
7873
+ subject?: string;
7874
+ server?: string;
7875
+ phaseRole?: McpPhaseRole;
7876
+ seat?: string;
7877
+ mode?: McpRunMode;
7878
+ /** A workflow phase id a policy may name (`review`, `build`, ...). */
7879
+ phaseId?: string;
7880
+ }
7881
+
7882
+ /** `POST /mcp/policies/preview`'s 200, and `McpPreviewResponse.policies`. */
7883
+ export interface McpPolicyPreviewResponse {
7884
+ roles: McpPhaseRole[];
7885
+ seats: string[];
7886
+ modes: McpRunMode[];
7887
+ phaseId: string | null;
7888
+ /**
7889
+ * Server previews only (`[]` elsewhere): the approvals saving this preview withdraws, because a
7890
+ * saved tool's schema changed. The cells are judged under the CURRENT approvals, so a tool listed
7891
+ * here (or every tool, when `mcp:<server>` is listed) asks again after the save.
7892
+ */
7893
+ withdrawOnSave: string[];
7894
+ tools: McpPolicyPreviewTool[];
7895
+ }
7896
+
7897
+ /** An approved subject and the ledgers that hold it. */
7898
+ export interface McpApproval {
7899
+ subject: string;
7900
+ scope: 'server' | 'tool';
7901
+ firstUse: boolean;
7902
+ write: boolean;
7903
+ }
7904
+
7905
+ /** A registered, enabled tool that would ask in balanced mode, and why. */
7906
+ export interface McpPendingApproval {
7907
+ subject: string;
7908
+ server: string;
7909
+ tool: string;
7910
+ class: McpToolClass;
7911
+ needs: Array<'first-use' | 'write'>;
7912
+ }
7913
+
7914
+ export interface McpLedgerState {
7915
+ id: string;
7916
+ /** `false` = the mcp-defaults pack was not seeded; approvals are refused (503). */
7917
+ present: boolean;
7918
+ /** A retired first-use ledger still asks: the engine reads it either way. */
7919
+ retired: boolean;
7920
+ }
7921
+
7922
+ /** `GET /mcp/approvals`. */
7923
+ export interface McpApprovalsResponse {
7924
+ approved: McpApproval[];
7925
+ pending: McpPendingApproval[];
7926
+ ledgers: { firstUse: McpLedgerState; write: McpLedgerState };
7927
+ }
7928
+
7929
+ /**
7930
+ * `POST /mcp/approvals`, an audited edit of the ledger rules' `excludes`. `mcp:<server>` approves
7931
+ * the server's FIRST USE (`MCP-FIRST-USE`); its write tools still ask in balanced mode.
7932
+ * `mcp:<server>/<tool>` approves that tool's first use and its writes (`MCP-POSTURE-WRITE` too).
7933
+ * An approval never lifts an engine gate or an explicit deny rule.
7934
+ */
7935
+ export interface McpApprovalBody {
7936
+ subject: string;
7937
+ }
7938
+
7939
+ /** `POST /mcp/approvals` and `DELETE /mcp/approvals/:subject`: the ledger rules the edit changed. */
7940
+ export interface McpApprovalResponse {
7941
+ subject: string;
7942
+ approved: boolean;
7943
+ rulesChanged: string[];
7944
+ }
7945
+
7946
+ // ── MCP usage: the fold over the call records (DES-MCP-TOOLS-001 §7 Usage, §8 `GET /mcp/usage`; slice S7; api-types 0.66.0) ──
7947
+
7948
+ /** Every call record in a window, split by the broker's decision. */
7949
+ export interface McpDecisionCounts {
7950
+ allow: number;
7951
+ ask: number;
7952
+ deny: number;
7953
+ guard_error: number;
7954
+ }
7955
+
7956
+ /**
7957
+ * The numbers a set of call records folds to. `ran` counts the calls the broker let through
7958
+ * (`decision: allow`); the error rate and the latency percentiles are over those calls only, since
7959
+ * a denied or asked call never reached an upstream. Percentiles are nearest-rank: the value at
7960
+ * position `ceil(q * n)` of the sorted durations. `null` = no call ran.
7961
+ */
7962
+ export interface McpUsageStats {
7963
+ calls: number;
7964
+ decisions: McpDecisionCounts;
7965
+ ran: number;
7966
+ /** Ran calls whose status is `error` (an upstream failure or a tool error, `isError`). */
7967
+ errors: number;
7968
+ errorRate: number | null;
7969
+ p50Ms: number | null;
7970
+ p95Ms: number | null;
7971
+ p99Ms: number | null;
7972
+ }
7973
+
7974
+ /** One tool's row of the per-tool table. */
7975
+ export interface McpUsageTool extends McpUsageStats {
7976
+ subject: string;
7977
+ server: string;
7978
+ tool: string;
7979
+ /** The class the engine judged on the newest record; `null` = never judged (a guard error). */
7980
+ class: McpToolClass | null;
7981
+ /** The seats that called it, sorted. */
7982
+ seats: string[];
7983
+ lastCall: string;
7984
+ }
7985
+
7986
+ /** One server's totals: its row's "last used" and "calls over the window". */
7987
+ export interface McpUsageServer {
7988
+ server: string;
7989
+ calls: number;
7990
+ decisions: McpDecisionCounts;
7991
+ lastCall: string;
7992
+ }
7993
+
7994
+ /** The drill-down: one tool × seat × run. `runId: null` = calls the broker refused before judging. */
7995
+ export interface McpUsageRun {
7996
+ subject: string;
7997
+ seat: string | null;
7998
+ runId: string | null;
7999
+ calls: number;
8000
+ decisions: McpDecisionCounts;
8001
+ /** Ran calls whose status is `error`, as in {@link McpUsageStats}. */
8002
+ errors: number;
8003
+ lastCall: string;
8004
+ }
8005
+
8006
+ /**
8007
+ * A tool chain: `from` then `to`, two consecutive calls of one unit attempt (by start time), with
8008
+ * different subjects. `count` = how often; `runs` = in how many runs.
8009
+ */
8010
+ export interface McpUsageChain {
8011
+ from: string;
8012
+ to: string;
8013
+ count: number;
8014
+ runs: number;
8015
+ }
8016
+
8017
+ /** One UTC day the window touches (its first day is partial), days with no calls included. */
8018
+ export interface McpUsageDay {
8019
+ day: string;
8020
+ calls: number;
8021
+ decisions: McpDecisionCounts;
8022
+ }
8023
+
8024
+ /**
8025
+ * `GET /mcp/usage?days&subject&seat&decision`. `days` is 1-30 (default 7); the window is the last
8026
+ * `days` × 24 h. Every filter narrows every section, except `chains`: a chain is folded from the
8027
+ * unit's calls under the seat and decision filters, then kept when either end is `subject`.
8028
+ * Records older than 30 days are folded into `calls-daily.ndjson` and are not in any window.
8029
+ */
8030
+ export interface McpUsageResponse {
8031
+ days: number;
8032
+ since: string;
8033
+ until: string;
8034
+ filters: { subject: string | null; seat: string | null; decision: McpCallDecision | null };
8035
+ totals: McpUsageStats;
8036
+ /** Most calls first, then by subject. */
8037
+ tools: McpUsageTool[];
8038
+ /** Most calls first, then by name. */
8039
+ servers: McpUsageServer[];
8040
+ /** Newest first, at most 200. */
8041
+ runs: McpUsageRun[];
8042
+ /** Most frequent first, at most 20. */
8043
+ chains: McpUsageChain[];
8044
+ /** Oldest first. */
8045
+ daily: McpUsageDay[];
8046
+ /** The seats seen in the window before any filter (the seat filter's options), sorted. */
8047
+ seats: string[];
8048
+ /** Lines of `calls.ndjson` that did not parse as a call record. */
8049
+ skipped: number;
8050
+ }