wicked-crew-api-types 0.40.0 → 0.73.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 +2570 -23
  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
  }
@@ -166,6 +184,8 @@ export type RoutingInfo =
166
184
  export interface HealthResponse {
167
185
  status: string;
168
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. */
169
189
  ping: string;
170
190
  capabilities?: HealthCapabilities;
171
191
  /**
@@ -211,6 +231,12 @@ export interface HealthCapabilities {
211
231
  * on such a daemon.
212
232
  */
213
233
  seatChipOnCreate?: boolean;
234
+ /**
235
+ * `AgentSession.chat_id` is served on every run launched from a chat, terminal ones included
236
+ * (C1; crew ≥ the release carrying api-types 0.71.0). ABSENT on a daemon before the field — read
237
+ * as `false`: group such a daemon's runs one session per run.
238
+ */
239
+ runChatId?: boolean;
214
240
  }
215
241
 
216
242
  /** One `GET /health.warnings[]` entry (additive; wicked-core#411 / wicked-crew#497). */
@@ -289,12 +315,18 @@ export interface AgentSession {
289
315
  *
290
316
  * - `'delivered'` — a PR was opened for this run (by the deliver phase, or post-hoc via
291
317
  * `POST /runs/:id/deliver`); `deliverUrl` carries the PR URL.
318
+ * - `'pushed'` — (api-types 0.69.0, N1) the run's branch was PUSHED and that is the whole
319
+ * delivery: no pull request could be opened because the origin is not a GitHub host gh can
320
+ * resolve (a local path, SSH, GitLab, ADO, Gitea…). `deliverBranch` + `deliverRemote` say
321
+ * what is where. Nothing is left to lift, so no post-hoc deliver is offered (`POST
322
+ * /runs/:id/deliver` answers 409); opening the pull/merge request is the forge's, by hand.
292
323
  * - `'stranded'` — reviewable work nobody lifted, reached two ways: a COMPLETED repo-scoped
293
324
  * run with no recorded PR whose worktree still exists on disk AND carries work (derived
294
325
  * honestly for OLD runs too — records written before this field existed strand the same
295
- * way); OR (crew#418) a run whose deliver phase hit a LIFT collision — a rebase conflict or
296
- * non-fast-forward push — which the daemon reinterprets from the engine's `failed` to
297
- * `completed`+`stranded`: its work is committed on the `wicked/<id>` branch (the engine
326
+ * way); OR (crew#418) a run whose deliver phase hit a LIFT collision — a rebase conflict
327
+ * (a push the remote REFUSED no longer strands: since 0.69.0 it parks the run at the
328
+ * engine's deliver-refusal gate, N4) — which the daemon reinterprets from the engine's
329
+ * `failed` to `completed`+`stranded`: its work is committed on the `wicked/<id>` branch (the engine
298
330
  * reaps the now-clean worktree, but never the branch), recoverable via `POST /runs/:id/
299
331
  * deliver`, which stands a throwaway worktree back up from that branch to lift it.
300
332
  * - `'vacuous'` — a COMPLETED repo-scoped run whose surviving worktree carries NO
@@ -317,7 +349,7 @@ export interface AgentSession {
317
349
  * FAILURE of a deliver phase is not spelled here; it is `units[].status === 'rejected'`
318
350
  * plus `denial_reason` (crew#318's message), both already on the list wire.
319
351
  */
320
- delivery?: 'delivered' | 'stranded' | 'vacuous' | 'none';
352
+ delivery?: 'delivered' | 'pushed' | 'stranded' | 'vacuous' | 'none';
321
353
  /**
322
354
  * The delivered PR's URL (crew#393; api-types 0.18.0) — present exactly when
323
355
  * `delivery === 'delivered'`; absent otherwise (absence is the one spelling, never `null`).
@@ -328,6 +360,13 @@ export interface AgentSession {
328
360
  * idempotent against the existing branch) and `'none'` after it is cleaned up.
329
361
  */
330
362
  deliverUrl?: string;
363
+ /** The pushed run branch (api-types 0.69.0, N1) — present exactly when `delivery === 'pushed'`. */
364
+ deliverBranch?: string;
365
+ /**
366
+ * The remote the branch was pushed to (api-types 0.69.0, N1), with any URL userinfo removed —
367
+ * present exactly when `delivery === 'pushed'`. May be a filesystem path (a local bare origin).
368
+ */
369
+ deliverRemote?: string;
331
370
  /**
332
371
  * The campaign this AD-HOC run was attached to at launch (`LaunchRunBody.campaignId`;
333
372
  * wicked-studio#27, api-types 0.19.0) — daemon-joined at DTO assembly on `GET /runs` and
@@ -419,6 +458,15 @@ export interface AgentSession {
419
458
  /** Wave 6 (F-7R2-006): the seats BENCHED for this run — never convened, never a failover or
420
459
  * judge target. Absent (never `[]`) when none / on an older engine. */
421
460
  benched_seats?: BenchedSeat[];
461
+ /**
462
+ * The run's APPROVED INTENT AMENDMENTS, in the order a human decided them (wicked-core#555;
463
+ * additive, engine ≥ the core-ts release after 0.7.30) — the `amend_intent` arm of a gate
464
+ * ({@link GateDecision.action}). Each amendment is appended to every unit at or after the
465
+ * gate's cursor, so the acceptance list each LATER EVALUATOR is handed changed with the
466
+ * decision. Render them beside the run's original intent: this is what was withdrawn or
467
+ * changed, and when. ABSENT (never `[]`) on an unamended run and on an older engine.
468
+ */
469
+ intent_amendments?: IntentAmendment[];
422
470
  /** crew#661: the declared skills this run's phases ran WITHOUT (the published skills snapshot did not
423
471
  * hold them when the launching subsystem armed) — the run proceeded DEGRADED, and says so here.
424
472
  * ABSENT (never `[]`) when the run launched fully armed, and on a daemon before this field. */
@@ -450,6 +498,85 @@ export interface AgentSession {
450
498
  * this field.
451
499
  */
452
500
  chat_grounded?: boolean;
501
+ /**
502
+ * The chat this run was launched from (`LaunchRunBody.chatId`; C1, api-types 0.71.0) — on EVERY
503
+ * run, terminal ones included, and after a daemon restart (read back from the `run.launched`
504
+ * trail entry). ABSENT — never `null` — when the run was not launched from a chat, or on a daemon
505
+ * before the field: check `GET /health.capabilities.runChatId` before grouping runs by chat.
506
+ */
507
+ chat_id?: string;
508
+ /**
509
+ * The engine's plan state (wicked-core `TeamPlanState`, DES-TEAMING-002 §8.4-§8.6; api-types
510
+ * 0.46.0 types the engine field) — present on a run launched from a PLAN or a PRESET, ABSENT on
511
+ * every other run (a registered def, the free-text planner) and on a team run before its launch
512
+ * is scored. Engine-computed; typed here only as far as a skin reads it. Read the run's kind from
513
+ * {@link AgentSession.run_identity}, which is resolved from this.
514
+ */
515
+ team_plan?: TeamPlanState;
516
+ /**
517
+ * What this run IS (seam X2; api-types 0.46.0) — daemon-resolved at DTO assembly on BOTH
518
+ * `GET /runs` and `GET /runs/:id` from the engine's own record (the plan state above, else the
519
+ * launch's recorded `sessionStarted`), never from the run's phase sequence. Always present on a
520
+ * 0.46.0+ daemon; ABSENT from an older one. `system` replaces a skin's own list of system
521
+ * workflow ids.
522
+ */
523
+ run_identity?: RunIdentity;
524
+ }
525
+
526
+ /**
527
+ * The fields of the engine's `TeamPlanState` a skin reads (the engine sends more — every field is
528
+ * engine-computed and additive).
529
+ */
530
+ export interface TeamPlanState {
531
+ /** The last plan rev assigned (accepted or held). `0` = none yet. */
532
+ rev: number;
533
+ /** The last ACCEPTED plan rev. `0` = no plan accepted yet. */
534
+ accepted_rev: number;
535
+ /** The preset the launch named. ABSENT on a user plan. */
536
+ preset?: string;
537
+ /**
538
+ * (wicked-core#633, DES-TEAMING-002 X1; api-types 0.47.0) PRESENT while the run's PA is scoping
539
+ * its launch plan — a creator plan or preset that declared no `touch` set. Until the `pa-scope`
540
+ * unit's boundary decides the plan, the run's only unit is `pa-scope` (plan rev 1): what the run
541
+ * will contain is not known yet. ABSENT once the plan is scoped (or when none needed scoping).
542
+ */
543
+ scope?: TeamPlanScopeHold;
544
+ }
545
+
546
+ /** The launch plan the PA is scoping (the engine's `ScopeHold`). */
547
+ export interface TeamPlanScopeHold {
548
+ /** The launch plan as authored (the preset's steps or the user's plan), without `pa-scope`. */
549
+ plan: { steps: TeamPlanStep[] } & Record<string, unknown>;
550
+ /** The run has no repo: the PA rates the risk (`RISK`) instead of declaring a touch set. */
551
+ unbound: boolean;
552
+ /** The scope step's `SCOPE` / `RISK` lines once its turn folds; ABSENT until then. */
553
+ answer?: { ord: number; attempt: number; by: string; lines: string };
554
+ }
555
+
556
+ /**
557
+ * What a run is (seam X2; api-types 0.46.0), resolved by the daemon from the engine's record.
558
+ *
559
+ * - `'preset'` — launched from a preset; `name` is the preset.
560
+ * - `'user_plan'` — launched from a user-composed plan (the engine's per-run `<run>:plan-<rev>`
561
+ * def); `name` is `null`.
562
+ * - `'workflow'` — launched on a registered workflow def; `name` is its id (a crew-composed
563
+ * per-run delivery/floor def reads as its base).
564
+ * - `'free_text'` — no workflow and no plan: the free-text planner; `name` is `null`.
565
+ * - `'unknown'` — the daemon has no record of what the launch named (a run predating the
566
+ * engine's event log, or an engine without the log read); `name` is `null`.
567
+ */
568
+ export interface RunIdentity {
569
+ kind: 'preset' | 'user_plan' | 'workflow' | 'free_text' | 'unknown';
570
+ /** The preset or workflow name — `null` unless `kind` is `'preset'` or `'workflow'`. */
571
+ name: string | null;
572
+ /** `kind === 'user_plan'`, spelled out. */
573
+ user_plan: boolean;
574
+ /**
575
+ * A machine-owned run (chat, onboarding, the interactive document and video seams, …): skins keep
576
+ * it off delivery surfaces. Keyed by `name` against the daemon's ONE list — the same list that
577
+ * stamps `WorkflowDef.is_system` on `GET /workflows`.
578
+ */
579
+ system: boolean;
453
580
  }
454
581
 
455
582
  /** An ordered unit of work within a run (`WorkUnit`). */
@@ -494,6 +621,34 @@ export interface WorkUnit {
494
621
  * planned before the field existed, and on an older engine.
495
622
  */
496
623
  base_skill_ref?: string | null;
624
+ /**
625
+ * The phase-catalog entry this unit instantiates (`build`, `review`, …) when it was planned from
626
+ * a catalog-composed plan — a preset or a user plan (wicked-core `WorkUnit.catalog`,
627
+ * DES-TEAMING-002 T3; api-types 0.46.0 types the engine field). ABSENT on every other unit.
628
+ */
629
+ catalog?: string;
630
+ /** TRUE on a unit of a TEAM run (planned from the per-run plan def); ABSENT otherwise. Engine field. */
631
+ team_run?: boolean;
632
+ /**
633
+ * TRUE when this unit's phase declared `requires_capture_report` (wicked-core#535 / BC-80) —
634
+ * carried from the def at plan time. ABSENT on every other unit and on an older engine.
635
+ */
636
+ requires_capture_report?: boolean;
637
+ /**
638
+ * What the capture phase REPORTED, read from its output marker by the gate fold
639
+ * (wicked-core#535 / BC-80): `derived` learnings, `submitted` proposals, `failed` submissions.
640
+ * Present once a capture unit emitted a well-formed marker — whether it passed or denied, so the
641
+ * counts are readable beside the denial. ABSENT on every other unit, on a capture unit whose
642
+ * marker was MISSING (that denies; the denial is the record), and on an older engine.
643
+ */
644
+ capture_report?: CaptureReport;
645
+ }
646
+
647
+ /** The counts a capture phase reported (`WorkUnit.capture_report`; wicked-core#535). */
648
+ export interface CaptureReport {
649
+ derived: number;
650
+ submitted: number;
651
+ failed: number;
497
652
  }
498
653
 
499
654
  /** A run plus its ordered units (`SessionView`) — the shape `GET /runs` returns. */
@@ -626,9 +781,29 @@ export interface RosterSeat {
626
781
  * while every ballot failed. Absent = the probe decided (and since 0.36.0 the probe itself needs
627
782
  * a credential-SHAPED file, never mere presence).
628
783
  */
629
- auth_source?: 'seat-stderr';
630
- /** Present with `auth_source: 'seat-stderr'`: the seat's own words, bounded. */
784
+ auth_source?: 'seat-stderr' | 'probe';
785
+ /** Present with `auth_source: 'seat-stderr'`, and with `probe` when it read signed out: the
786
+ * seat's own words, bounded. */
631
787
  auth_evidence?: string;
788
+ /**
789
+ * Present with `auth_source: 'probe'` (crew#630): ISO-8601 of the seat's own auth-status check
790
+ * (`claude auth status`, `codex login status`) under the seat's configuration home, which then
791
+ * decided `auth` and `signed_in` over the credential file. The daemon re-checks in the
792
+ * background (a signed-in answer after 5 minutes, any other after 30 s). A probed seat whose
793
+ * check has not answered yet reads `unknown`, never `signed_in` off its file.
794
+ */
795
+ probed_at?: string;
796
+ /**
797
+ * How far `auth` was VERIFIED (crew#645): `live` — the seat answered an authenticated request, or
798
+ * refused one for want of a credential (a status command reads the stored login, so an expired
799
+ * OAuth login still reads signed in there; the daemon makes one short live request when it says
800
+ * signed in); `status` — only the status command answered (the live request could not tell);
801
+ * `unverified` — nothing asked the seat (it has no status command, or its check has not answered
802
+ * or could not tell). Optional on the wire: a daemon before it sends none.
803
+ */
804
+ login_check?: 'live' | 'status' | 'unverified';
805
+ /** Present unless `login_check` is `live`: why, in the operator's words ("login unverified — …"). */
806
+ login_note?: string;
632
807
  /** Present when `auth` is `not_required`: the free tier the seat answers on. */
633
808
  free_tier?: string;
634
809
  /**
@@ -659,9 +834,33 @@ export interface RosterSeat {
659
834
  * a reader must tolerate absence today.
660
835
  */
661
836
  council_bench?: RosterSeatCouncilBench;
837
+ /**
838
+ * (core#581) Present on a seat whose ACP config pins `verified_version`, once the daemon's
839
+ * probe has answered: the build the seat's input-governance admission was proven against and
840
+ * what the binary reports now. `matched: false` with `governanceClaimed: true` means the engine
841
+ * fails the seat's ACP governance closed at every spawn.
842
+ */
843
+ version_pin?: SeatVersionPin;
662
844
  [k: string]: unknown;
663
845
  }
664
846
 
847
+ /**
848
+ * One pinned seat's `--version` reading (core#581): `RosterSeat.version_pin` and
849
+ * `AcpCliDiagnostics.versionPin`. Read the way the engine's spawn-time check reads it: the pin
850
+ * matches only the first line of `--version`, trimmed; `observed` is the first non-empty line,
851
+ * `null` when the binary did not answer.
852
+ */
853
+ export interface SeatVersionPin {
854
+ cli: string;
855
+ binary: string;
856
+ pinned: string;
857
+ observed: string | null;
858
+ matched: boolean;
859
+ governanceClaimed: boolean;
860
+ /** The operator-facing reason when a governed seat's pin does not hold; else `null`. */
861
+ disclosure: string | null;
862
+ }
863
+
665
864
  /** `RosterSeat.council_bench` (api-types 0.35.0).
666
865
  * @deprecated ABSENT from crew 0.7.36 (see {@link RosterSeat.council_bench}); removed one minor later. */
667
866
  export interface RosterSeatCouncilBench {
@@ -679,6 +878,62 @@ export interface RosterSeatCouncilBench {
679
878
  window_ms: number;
680
879
  }
681
880
 
881
+ /**
882
+ * `GET /roster/record?days=N` (api-types 0.52.0): each seat's record over the last N days (default 7,
883
+ * max 30), folded from the runs' durable event logs (studio's weekly 1:1 per agent). Nothing new is
884
+ * stored; the window is by event time. A unit's seat is `unitDistributed.cli` (moved by
885
+ * `unitReassigned.newCli`), else the unit row's `assigned_cli`. 503 when the engine has no event-log
886
+ * read binding (a missing binding is not an empty week).
887
+ */
888
+ export interface SeatRecordResponse {
889
+ days: number;
890
+ /** Window start, unix MILLIS. */
891
+ since: number;
892
+ /** Window end (the read time), unix MILLIS. */
893
+ until: number;
894
+ /** Runs whose event log was read. */
895
+ runsRead: number;
896
+ /** True when the daemon's per-read run cap cut the read (the oldest runs in the window were skipped). */
897
+ truncated: boolean;
898
+ /** Seats with any record in the window, by key. A roster seat absent here had no units and no bench. */
899
+ seats: SeatRecord[];
900
+ }
901
+
902
+ /** One seat's week (`SeatRecordResponse.seats[]`). */
903
+ export interface SeatRecord {
904
+ /** The roster key. */
905
+ cli: string;
906
+ /** Distinct units the seat was dispatched on in the window. */
907
+ units: number;
908
+ /** Units of this seat with a gate evaluation in the window. */
909
+ gated: number;
910
+ /** Of `gated`: the FIRST evaluation passed (`gateEvaluated.combined` and no `denial`). */
911
+ firstPass: number;
912
+ /** Units the seat ran first that were dispatched again (a retry or a send-back) or given a `unitReworkAmended`. */
913
+ rework: number;
914
+ /** Distinct units that went quiet (`workerStalled`, engine or daemon watchdog) while the seat ran them. */
915
+ stalls: number;
916
+ /** Runs in the window whose `benched_seats` names the seat. */
917
+ benched: number;
918
+ /** Bench reason → runs. */
919
+ benchReasons: Record<string, number>;
920
+ /** Summed `cliUsage.costUsd` where a price was known; `null` when no usage frame carried one (never `0` for "unknown"). */
921
+ costUsd: number | null;
922
+ /** Usage frames that carried a price. */
923
+ costedUsage: number;
924
+ /** Phase (`phase_ref`, else `stage`) → the same counts, so a move can name the phase. */
925
+ byPhase: Record<string, SeatPhaseRecord>;
926
+ }
927
+
928
+ /** One phase's slice of a {@link SeatRecord}. */
929
+ export interface SeatPhaseRecord {
930
+ units: number;
931
+ gated: number;
932
+ firstPass: number;
933
+ rework: number;
934
+ stalls: number;
935
+ }
936
+
682
937
  /** A seat's auth reading (`RosterSeat.auth`; api-types 0.35.0). */
683
938
  export type SeatAuth = 'signed_in' | 'signed_out' | 'not_required' | 'unknown';
684
939
 
@@ -774,8 +1029,39 @@ export interface GateDecision {
774
1029
  * `unitDispatched{attempt: last + 1}`); requires `approve: false` — a disagreement between
775
1030
  * `action` and `approve` answers 400, a gate with no creator phase before it 409. An older
776
1031
  * daemon's strict schema rejects the key with a 400 — omit it against such servers.
1032
+ *
1033
+ * The ESCALATION arms (additive; wicked-core#469 / #467, engine ≥ the core-ts release after
1034
+ * 0.7.30): all require `approve: true` and take no `amend` / `amendScope` / `plan` (a 400
1035
+ * otherwise). `'extend'` | `'targeted'` | `'accept_partial'` answer the escalation gate of a
1036
+ * repo-checks floor that did not finish (`gateEscalated.denialSource: 'repo_checks_timeout'`):
1037
+ * the engine re-runs the floor on the tree as it stands — the seat does not run again — with
1038
+ * every check under 2× its bound, with the repo's declared `test_targeted` in place of the full
1039
+ * test set (no targeted command ⇒ the test set is waived), or with the checks that did not
1040
+ * finish waived; the re-run goes through the ordinary gate and waived checks are named in
1041
+ * `gateEvaluated.floorNote`. `'accept_suggestion'` answers an evaluator's worktree-guard denial
1042
+ * whose edit was restored and pinned (`gateEscalated.suggestionRef`): the engine applies the
1043
+ * pinned edit and rewinds to the creator (`unitReworkAmended{scope: 'accept_suggestion'}`).
1044
+ * The engine refuses each at any other gate — a 409 — and the gate stays open.
1045
+ *
1046
+ * `'amend_intent'` (additive; wicked-core#555, engine ≥ the core-ts release after 0.7.30)
1047
+ * approves the gate AND AMENDS THE RUN'S INTENT: `amend` carries what the acceptance list now
1048
+ * says (required, non-empty), `approve: true`, and no `amendScope` — the scope IS every unit at
1049
+ * or after the cursor, which is what makes it reach the LATER EVALUATOR. Before it, nothing
1050
+ * amended the list an evaluator is handed, so a mid-run descope could only end in a relaunch.
1051
+ * The engine appends the text to those units' descriptions, records it on the run
1052
+ * (`AgentSession.intent_amendments`) and emits `intentAmended`. Refused at a plan or team gate,
1053
+ * and with empty text — a 409 with the gate left open.
777
1054
  */
778
- action?: 'approve' | 'request_changes' | 'reject';
1055
+ action?:
1056
+ | 'approve'
1057
+ | 'request_changes'
1058
+ | 'reject'
1059
+ | 'edit_plan'
1060
+ | 'extend'
1061
+ | 'targeted'
1062
+ | 'accept_partial'
1063
+ | 'accept_suggestion'
1064
+ | 'amend_intent';
779
1065
  /**
780
1066
  * Where an approve's `amend` lands (api-types 0.38.0, additive; same release as `action`).
781
1067
  * Absent = `'cursor'` (today: the gated unit's description). `'creator'` — the first creator
@@ -784,6 +1070,38 @@ export interface GateDecision {
784
1070
  * Identical text already present on the target is not appended twice.
785
1071
  */
786
1072
  amendScope?: 'cursor' | 'creator';
1073
+ /**
1074
+ * Approve a `plan_approval` gate WITH AN EDIT (DES-TEAMING-002 §8.6, seam T3; additive; needs an
1075
+ * engine whose addon carries `Core.supportsPlanLaunch`). The edited plan: the engine publishes
1076
+ * `plan.proposed{by:"human", kind:"edit"}`, floor-fills it (phases below the floor are ADDED,
1077
+ * never refused) and accepts it as the next `plan_rev` — or refuses it (`plan.refused`) and
1078
+ * re-opens the gate under a new `gate_id`, answering `awaiting_human`. Requires `approve: true`
1079
+ * and `action` omitted or `'edit_plan'`; `amend` / `amendScope` do not apply. A plain approve
1080
+ * (no `plan`) releases the held plan as it is; `approve: false` rejects (cancels).
1081
+ */
1082
+ plan?: LaunchPlan;
1083
+ /**
1084
+ * The gate this decision answers — the unit `ord` its open gate paused before, as `GET
1085
+ * /runs/:id/gate` served it (api-types 0.44.0, additive). When present the daemon compares it to
1086
+ * the run's open gate and answers **409** `{ error, code: 'gate_changed', openOrd }` on a
1087
+ * mismatch, confirming nothing: the gate was answered elsewhere or replaced while the decision
1088
+ * waited (a skin's undo window). When the daemon cannot tell which gate is open it answers
1089
+ * **409** `{ error, code: 'gate_unknown' }` — refresh the gate and decide again. Absent = today's
1090
+ * behaviour (no gate check).
1091
+ */
1092
+ ord?: number;
1093
+ }
1094
+
1095
+ /**
1096
+ * A USER-COMPOSED plan (DES-TEAMING-002 §8.4, seam T3): ordered `steps[]` over the phase catalog,
1097
+ * the predicted `touch` set (the intent score's input: a plan with a creator step and no `touch`
1098
+ * scores 100, "no declared scope") and an optional MANUAL-mode floor `override` (refused in auto
1099
+ * mode). A step's `id` defaults to its catalog id. The ENGINE validates every key.
1100
+ */
1101
+ export interface LaunchPlan {
1102
+ steps: Array<{ catalog: string; id?: string | undefined; [k: string]: unknown }>;
1103
+ touch?: string[];
1104
+ override?: TeamPlanOverride;
787
1105
  }
788
1106
 
789
1107
  /**
@@ -1100,6 +1418,22 @@ export type WorkerStallEscalatedFrame = {
1100
1418
  escalations?: number;
1101
1419
  /** outcome `failed`: bounded excerpt of what the recovery call threw. */
1102
1420
  error?: string;
1421
+ /**
1422
+ * Why the watchdog did not do a plain reassign (crew#638 / #581 / #580; additive, absent on older
1423
+ * daemons): `tool_unit` — the cursor is the engine's own command, nothing to fail over to;
1424
+ * `evaluating` — the worker returned and its gate evaluation is in flight, a reassign would start
1425
+ * a second creator; `evaluator_distinct` — every other seat is one evaluator ≠ creator forbids
1426
+ * (see `avoided`), so the unit was left for a human; `no_output` (outcome `exhausted`) — the
1427
+ * reassigned seat produced nothing past a startup banner.
1428
+ */
1429
+ reason?: 'tool_unit' | 'evaluating' | 'evaluator_distinct' | 'no_output';
1430
+ /**
1431
+ * The seats the failover pick excluded under evaluator ≠ creator (crew#638; additive): a
1432
+ * creator cursor avoids the run's evaluator seats, an evaluator cursor the seats that built its
1433
+ * work, an `evaluator_distinct`-routed unit the seat it was routed away from. Present on an `ok`
1434
+ * reassign that skipped one and on a `reason: 'evaluator_distinct'` notify; absent otherwise.
1435
+ */
1436
+ avoided?: string[];
1103
1437
  };
1104
1438
 
1105
1439
  export interface RecordedEvent extends CoreEvent {
@@ -1998,10 +2332,12 @@ export interface UnitReworkAmendedEvent {
1998
2332
  * 0.7.27): `'cursor'` — the gated unit, today's approve-with-steer (`GateDecision.amendScope`
1999
2333
  * absent or `'cursor'`); `'creator'` — the first creator phase at/after the cursor
2000
2334
  * (`amendScope: 'creator'`); `'request_changes'` — the creator phase the gate rewound to, and
2001
- * `amendment` then carries the evaluator's full findings + the operator's note. ABSENT on an
2002
- * engine before the field — read as `'cursor'`.
2335
+ * `amendment` then carries the evaluator's full findings + the operator's note;
2336
+ * `'accept_suggestion'` (wicked-core#467) — the same rewind after the operator adopted the
2337
+ * evaluator's pinned edit, and `amendment` names it. ABSENT on an engine before the field — read
2338
+ * as `'cursor'`.
2003
2339
  */
2004
- scope?: 'cursor' | 'creator' | 'request_changes';
2340
+ scope?: 'cursor' | 'creator' | 'request_changes' | 'accept_suggestion';
2005
2341
  }
2006
2342
 
2007
2343
  /** P2 — a worker's ApplyStepResult arrived and output is ready to be gated. Fires before GateDecided.
@@ -2119,6 +2455,16 @@ export interface UnitDistributedEvent {
2119
2455
  degraded_reason?: string | null;
2120
2456
  }
2121
2457
 
2458
+ /** One APPROVED INTENT AMENDMENT (`AgentSession.intent_amendments`; wicked-core#555). */
2459
+ export interface IntentAmendment {
2460
+ /** The human's own words — what the run's acceptance list now says. */
2461
+ text: string;
2462
+ /** The gate's cursor unit `ord`: the first unit the amendment reached. */
2463
+ ord: number;
2464
+ /** When it was decided, unix millis. */
2465
+ at: number;
2466
+ }
2467
+
2122
2468
  /** One seat the wave-6 engine BENCHED for a run (`AgentSession.benched_seats`, F-7R2-006): never
2123
2469
  * convened, never a failover or judge target, named in `unitDistributed.degradedReason`. */
2124
2470
  export interface BenchedSeat {
@@ -2618,6 +2964,30 @@ export interface SkillsManifestResponse {
2618
2964
  * `recorded.reasons` is `null` for a row written before per-reason portability (0.34.0). */
2619
2965
  drift?: SnapshotRowDrift[];
2620
2966
  } | null;
2967
+ /**
2968
+ * The wicked-garden plugin INSTALLED on this host right now (wicked-studio#388) — so a surface
2969
+ * can say when the daemon's baseline, and therefore every snapshot published from it, is behind
2970
+ * the operator's install. `null` = no plugin is installed (the unseeded case the 503 covers);
2971
+ * absent on a daemon older than the release carrying it. Compare `installed.baseline` with
2972
+ * `manifest.baseline`: different = Refresh baseline, then Publish.
2973
+ */
2974
+ installed?: InstalledPlugin | null;
2975
+ }
2976
+
2977
+ /** `SkillsManifestResponse.installed` — the live plugin's identity (wicked-studio#388). */
2978
+ export interface InstalledPlugin {
2979
+ source: { kind: SkillSourceKind; path: string; plugin_version: string };
2980
+ /** HEAD sha for a `checkout` source; `null` otherwise (or when git could not answer). */
2981
+ git_sha: string | null;
2982
+ /**
2983
+ * The content hash of the INSTALLED bundle, comparable with `SkillManifest.baseline` — the same
2984
+ * identity `POST /skills/refresh-baseline` decides on, never the version string (two installs of
2985
+ * one version with different bytes are two baselines). `null` when the bundle could not be read,
2986
+ * and then `unreadable` says why: a comparison crew could not make is stated, not reported as
2987
+ * agreement.
2988
+ */
2989
+ baseline: string | null;
2990
+ unreadable: string | null;
2621
2991
  }
2622
2992
 
2623
2993
  /** The identity of a portability-rule table (api-types 0.36.0, F-083): its version and the
@@ -3247,6 +3617,14 @@ export interface TestingReconResponse {
3247
3617
  /** Present only when the fan was campaign-registered with a `projectId` and filing a sibling
3248
3618
  * into the project failed — the runs are LIVE; re-attach via POST /projects/:id/members. */
3249
3619
  projectAttachError?: string;
3620
+ /**
3621
+ * `true` on a **202**: the engine was still launching the fan when the daemon answered (crew#471 —
3622
+ * it plans every node's units before it replies, which held the POST for minutes). The `runIds`
3623
+ * are final (deterministic node ids) and can be followed now; the trail entries and project
3624
+ * filing land when the engine answers, and a late refusal is audited as `campaign.launch_failed`.
3625
+ * Absent on a 201.
3626
+ */
3627
+ launching?: true;
3250
3628
  }
3251
3629
 
3252
3630
  // ── The governed test-authoring launch (wave 6; api-types 0.36.0) ──────────────────────────────
@@ -3421,6 +3799,21 @@ export interface LaunchRunBody {
3421
3799
  repoRef?: string;
3422
3800
  /** Built-in workflow id (`feature` | `bug` | `migration`); omit for free-text single-unit mode. */
3423
3801
  workflow?: string;
3802
+ /**
3803
+ * A USER-COMPOSED plan (DES-TEAMING-002 §8.4, seam T3; additive; needs an engine whose addon
3804
+ * carries `Core.supportsPlanLaunch` — an older one is refused with a 501, never run unplanned).
3805
+ * The ENGINE publishes `plan.proposed{by:"human"}` before any unit runs, scores and floor-fills
3806
+ * it, and holds it at a `plan_approval` gate (`awaiting_human{gate_kind:"plan_approval"}`) when
3807
+ * the approval matrix says so: always in manual mode, and in auto mode when high risk (band
3808
+ * 70-100 or destructive). Mutually exclusive with `workflow` (a plan or a preset).
3809
+ * A creator plan with NO `touch` set — and every creator preset — is scoped by the run's PA
3810
+ * first (wicked-core#633, X1): rev 1 is the read-only `pa-scope` step alone (unit ord 1), the
3811
+ * scored plan is rev 2 proposed by the PA seat (`plan.proposed{by:<PA seat>}`), and its
3812
+ * `plan_approval` gate, when one opens, is at ord 2. Declare `touch` to be scored at launch.
3813
+ * `deliver: "pr"` with a plan (api-types 0.42.0, T8) hands the engine the deliver step, which it
3814
+ * appends to the plan and puts in the floor; `deliver` omitted with a plan means `"none"`.
3815
+ */
3816
+ plan?: LaunchPlan;
3424
3817
  /**
3425
3818
  * The project to file this run into (DES-PROJECT-001 §2.2). The `crew.run` membership is
3426
3819
  * attached atomically with the launch record; an unknown or archived project fails the launch
@@ -3532,6 +3925,29 @@ export interface LaunchRunBody {
3532
3925
  actor?: string;
3533
3926
  }
3534
3927
 
3928
+ /**
3929
+ * One issue a launch's intent linked (`#N`, `owner/repo#N`, an issue URL) and whether the daemon
3930
+ * could read it (crew#627, additive). A governed worker cannot read GitHub, so for a workflow launch
3931
+ * the daemon reads each linked issue under its own identity (`gh issue view`, at most 5) and appends
3932
+ * the title, state, body and comments to the problem the engine plans from, inside a marked block.
3933
+ * `resolved: false` names why (`error`) — shown, never skipped.
3934
+ */
3935
+ export interface LinkedIssue {
3936
+ ref: string;
3937
+ resolved: boolean;
3938
+ /** Present when `resolved`. */
3939
+ title?: string;
3940
+ /** Present when not `resolved`: why, in one line. */
3941
+ error?: string;
3942
+ }
3943
+
3944
+ /** The `POST /runs` 201 body. `linkedIssues` (crew#627, additive) is present when the intent of a
3945
+ * workflow launch named at least one issue; absent otherwise and on an older daemon. */
3946
+ export interface LaunchRunResponse {
3947
+ runId: string;
3948
+ linkedIssues?: LinkedIssue[];
3949
+ }
3950
+
3535
3951
  /**
3536
3952
  * Body for `PUT /runs/:id/guidance` (DES-UX-002 §7.2, CREW-UX-7; api-types 0.9.0) — upsert the
3537
3953
  * run's ONE durable operator guidance note. `text: ''` CLEARS the note (the DTO field returns
@@ -3663,12 +4079,20 @@ export interface PhaseDef {
3663
4079
  * run `cmd` directly. */
3664
4080
  executor?: PhaseExecutor;
3665
4081
  /** Per-phase agent instructions folded into the unit prompt by the engine (core PhaseDef.instructions,
3666
- * Option<String>). Absent = no extra instruction. Mirrors of core drop-ins that carry it (e.g.
3667
- * survey-repo) MUST reproduce it verbatim or the runtime def diverges from core's — FINDING-011. */
4082
+ * Option<String>). Absent = no extra instruction. Mirrors of core drop-ins that carry it MUST
4083
+ * reproduce it verbatim or the runtime def diverges from core's — FINDING-011. */
3668
4084
  instructions?: string | null;
3669
4085
  gate_type: GateType | null;
3670
4086
  gate: GateSpec;
3671
4087
  executes_code: boolean;
4088
+ /**
4089
+ * The phase must REPORT what it captured (wicked-core#535 / BC-80; additive, engine ≥ the
4090
+ * release carrying it — an older engine's strict def parser refuses the key). Its output must
4091
+ * carry `wicked-capture-report {"derived": N, "submitted": M, "failed": K}`; a missing marker, a
4092
+ * failed submission and proposals derived but never submitted each deny the unit into the human
4093
+ * gate. An honest `0/0/0` passes. Absent/false ⇒ not parsed (every phase but `capture`).
4094
+ */
4095
+ requires_capture_report?: boolean;
3672
4096
  verified_evidence: boolean;
3673
4097
  required_deliverables: string[];
3674
4098
  depends_on: string[];
@@ -3695,6 +4119,29 @@ export interface WorkflowDef {
3695
4119
  base_skill_ref?: string | null;
3696
4120
  }
3697
4121
 
4122
+ /** A drop-in workflow definition the ENGINE refused, with the engine's own reason (wicked-crew#718). */
4123
+ export interface RefusedWorkflow {
4124
+ /** The def's id, as its file named it. */
4125
+ id: string;
4126
+ /**
4127
+ * wicked-core's refusal, verbatim — e.g. `gate evaluates nothing: write-a-note — the phase
4128
+ * declares executes_code but pins no validator and has no human gate, so its gate would approve
4129
+ * with nothing checked.`
4130
+ */
4131
+ reason: string;
4132
+ }
4133
+
4134
+ /**
4135
+ * `GET /workflows` (wicked-crew#718). `workflows` is what the ENGINE accepted — the only defs a
4136
+ * launch can name. `unavailable` is the drop-ins it refused, with its reason, so a def that cannot
4137
+ * be launched is NAMED rather than offered and then 400'd `unknown workflow`. Absent on a daemon
4138
+ * older than the release carrying it (read it as `[]`).
4139
+ */
4140
+ export interface WorkflowsResponse {
4141
+ workflows: WorkflowDef[];
4142
+ unavailable?: RefusedWorkflow[];
4143
+ }
4144
+
3698
4145
  /** Top-level requirements_graph.json artifact (schema 1.0.0). */
3699
4146
  export interface DomainGraph {
3700
4147
  metadata: { schema_version: string; migration_mode: string; source?: string };
@@ -3768,6 +4215,24 @@ export interface CodeGraphData {
3768
4215
  totals?: { nodes: number; edges: number; files: number };
3769
4216
  }
3770
4217
 
4218
+ /**
4219
+ * `GET /api/v1/repos/:id/deliver-target` (R3) — where a delivering launch on this repo would push,
4220
+ * read by the same origin preflight the deliver gate card uses (crew#730). `sentence` is the gate's
4221
+ * own target sentence for a run not yet started ("the run branch"); show it verbatim.
4222
+ * - `github` — a github.com origin; `githubRepo` is its `owner/repo` on github.com or
4223
+ * ssh.github.com, and `null` on another `*.github.com` host (gist, api);
4224
+ * - `local` — a filesystem path or `file://` URL: no pull request can be opened against it;
4225
+ * - `other` — another host: a pull request opens only if gh resolves it as a GitHub host;
4226
+ * - `none` — the checkout has no `origin` remote: the push will fail;
4227
+ * - `unknown` — the origin could not be read; the sentence claims nothing either way.
4228
+ */
4229
+ export interface DeliverTargetResponse {
4230
+ repo: string;
4231
+ origin: 'github' | 'local' | 'other' | 'none' | 'unknown';
4232
+ githubRepo: string | null;
4233
+ sentence: string;
4234
+ }
4235
+
3771
4236
  /**
3772
4237
  * `GET /repos/:id/graph` → 200 (api-types 0.35.0, F-2R2-005). `graph: null` = the repo's code
3773
4238
  * graph has not been built (nothing at its registered graph path) — and `reason` says WHY, so a
@@ -3800,6 +4265,12 @@ export interface GitContributor {
3800
4265
  export interface SystemSettings {
3801
4266
  /** Max nodes returned by wicked-estate graph-view (default 150). */
3802
4267
  graphNodeLimit: number;
4268
+ /**
4269
+ * The watch registry's operator settings (TR-W5a, api-types 0.73.0): only `enabled` and
4270
+ * `threshold` of a shipped entry, and the LLM lane switch. A `watch` patch needs a human actor
4271
+ * (a worker's bearer is refused 403); the daemon stamps who changed what and when.
4272
+ */
4273
+ watch?: WatchSettings;
3803
4274
  /**
3804
4275
  * Base directory for the ENGINE-OWNED worker CLI config homes (the claude worker home is
3805
4276
  * `<root>/claude`). The daemon applies it as env `WICKED_WORKER_HOME` at boot and on every
@@ -3847,6 +4318,18 @@ export interface SystemSettings {
3847
4318
  * `base_skill_refused`); the finding is an `error`.
3848
4319
  */
3849
4320
  baseSkillPolicy?: 'warn' | 'require';
4321
+ /**
4322
+ * The DELIVER IDENTITY (crew#549; additive): the GitHub login the deliver phase must push as.
4323
+ * The deliver script reads `gh api user` AND asks git which credential it would use for the
4324
+ * remote's host, and REFUSES — before anything is staged, committed or pushed — when either
4325
+ * disagrees with this login (or, with nothing configured, when those two disagree with each
4326
+ * other). `''`/absent ⇒ the `GH_ACCOUNT` env var decides, and unset there means the phase
4327
+ * pushes as whatever login gh holds, said aloud on the gate card.
4328
+ *
4329
+ * A LOGIN ONLY — never a token. The credential itself stays in gh's keyring or in the daemon's
4330
+ * `GH_TOKEN`; `GET /settings` returns this login and no secret ever.
4331
+ */
4332
+ deliverIdentityLogin?: string;
3850
4333
  /**
3851
4334
  * The stall watchdog's DETECTION threshold (crew#287; api-types 0.18.0 — previously a
3852
4335
  * daemon-local extension): minutes a run in `executing` may go without ANY engine event on the
@@ -3992,6 +4475,60 @@ export interface ElicitationResponse {
3992
4475
  content?: { response: string };
3993
4476
  }
3994
4477
 
4478
+ // ── Presets (DES-TEAMING-002 §8.4, seam C2) — saved phase selections ──────────
4479
+
4480
+ /**
4481
+ * One plan step: the catalog entry it instantiates (`understand`, `build`, …), its phase id,
4482
+ * and the step fields the catalog lets a step set (`gate`, `validator_pin`, `depends_on`, …).
4483
+ * The engine validates it (`plan::compose`); unknown keys are refused there.
4484
+ */
4485
+ export interface PresetStep {
4486
+ catalog: string;
4487
+ id: string;
4488
+ [k: string]: unknown;
4489
+ }
4490
+
4491
+ /**
4492
+ * A preset (`GET /presets`, `PUT /presets/:name`): a named phase selection stored in the core
4493
+ * store. A launch's `workflow` names one (`POST /runs {workflow}`); a project-scoped preset
4494
+ * shadows a global one of the same name for that project. Built-ins carry
4495
+ * `created_by: "builtin"` and cannot be deleted.
4496
+ */
4497
+ export interface Preset {
4498
+ name: string;
4499
+ /** `"global"` or `"project:<id>"`. */
4500
+ scope: string;
4501
+ steps: PresetStep[];
4502
+ /** `"builtin"` for a seeded built-in; otherwise the writing surface. */
4503
+ created_by: string;
4504
+ /** Unix millis. */
4505
+ updated_at: number;
4506
+ /**
4507
+ * A machine-owned preset (`chat`, `onboarding`, …): keep it off delivery surfaces and the work-mode
4508
+ * selector. The preset's half of the one name-keyed classification that stamps
4509
+ * `WorkflowDef.is_system` and `run_identity.system` (DES-TEAMING-002 §11.3). The daemon stamps it
4510
+ * on every preset it serves (api-types 0.48.0); absent from an older daemon.
4511
+ */
4512
+ system?: boolean;
4513
+ [k: string]: unknown;
4514
+ }
4515
+
4516
+ /** `PUT /presets/:name` body. `projectId` absent ⇒ a global preset. */
4517
+ export interface PutPresetBody {
4518
+ steps: PresetStep[];
4519
+ projectId?: string;
4520
+ }
4521
+
4522
+ /** `GET /presets` (`?projectId=`) — the presets a launch in that project sees, by name. */
4523
+ export interface PresetListResponse {
4524
+ presets: Preset[];
4525
+ }
4526
+
4527
+ /** `GET /presets/:name`, `PUT /presets/:name`. */
4528
+ export interface PresetResponse {
4529
+ preset: Preset;
4530
+ }
4531
+
3995
4532
  // ── Projects (DES-PROJECT-001) — the experience-plane keystone ─────────────────
3996
4533
 
3997
4534
  /** Project lifecycle: `active ⇄ archived`, no hard delete (ADR §1.3). */
@@ -4627,6 +5164,60 @@ export type ChatSeatRefusedFrame = {
4627
5164
  project_id?: string;
4628
5165
  };
4629
5166
 
5167
+ // ── Chat citations (crew#561, F-RC1-117; api-types 0.67.0) ───────────────────
5168
+ //
5169
+ // A seat's answer cites paths, `path:line` / `path:symbol` refs and commit SHAs, and nothing used
5170
+ // to check them: on the RC1 Phase 6 re-run 2 of 26 cited SHAs existed in no repo and three line
5171
+ // refs were off, rendered as plain text a reader could not tell apart from the real ones. The
5172
+ // daemon verifies each citation against the very read roots it handed the seats and publishes the
5173
+ // verdicts; the seat's TEXT is never edited — a skin MARKS it from these items.
5174
+
5175
+ /** What class of thing a citation is. */
5176
+ export type ChatCitationKind = 'path' | 'line' | 'symbol' | 'sha';
5177
+
5178
+ /**
5179
+ * The verdict on one citation.
5180
+ * - `verified` — it exists, exactly as cited;
5181
+ * - `corrected` — it exists, but not where the reply said (`resolved` is the real place);
5182
+ * - `unverified` — it is not in the chat's scope (the fabricated-SHA case);
5183
+ * - `unchecked` — the bounded pass did not reach it. NOT a claim either way.
5184
+ */
5185
+ export type ChatCitationStatus = 'verified' | 'corrected' | 'unverified' | 'unchecked';
5186
+
5187
+ /** One citation as verified. `raw` is the token EXACTLY as it appears in the reply, so a skin can
5188
+ * find it in the rendered text without re-parsing. */
5189
+ export interface ChatCitationItem {
5190
+ raw: string;
5191
+ kind: ChatCitationKind;
5192
+ status: ChatCitationStatus;
5193
+ /** Where the thing actually is: `alpha/src/foo.ts:2069` for a corrected line ref, the repo name
5194
+ * for a SHA, the defining line for a symbol. Absent when there is nothing to add. */
5195
+ resolved?: string;
5196
+ /** Why, in one short phrase — a skin's hover text. */
5197
+ note?: string;
5198
+ }
5199
+
5200
+ /**
5201
+ * DAEMON-SYNTHETIC (like {@link ChatSeatRefusedFrame}): ONE frame per terminal `chatReply` of a
5202
+ * chat that has read roots, broadcast AFTER that reply (verification never delays an answer) and
5203
+ * stamped with the reply's own `turn_id`. ABSENT — never empty — when the reply cites nothing, when
5204
+ * the chat has no read roots to verify against, and for a not-ok reply (a refusal or eviction line
5205
+ * cites nothing a reader would paste). The counts match `items`; `unverifiable` counts `unverified`
5206
+ * only, so `unchecked` is never read as a fabrication.
5207
+ */
5208
+ export type ChatCitationsFrame = {
5209
+ type: 'chatCitations';
5210
+ chat: string;
5211
+ cliKey: string;
5212
+ turn_id?: string;
5213
+ verified: number;
5214
+ unverifiable: number;
5215
+ corrected: number;
5216
+ unchecked: number;
5217
+ items: ChatCitationItem[];
5218
+ project_id?: string;
5219
+ };
5220
+
4630
5221
  /** One live chat on `GET /chats` (FINDING-027 gap 4; scope fields with crew#502). */
4631
5222
  export interface ChatSummary {
4632
5223
  chatId: string;
@@ -4670,7 +5261,25 @@ export interface ChatUsage {
4670
5261
  */
4671
5262
  export type ChatTranscriptRecord =
4672
5263
  | { at: number; turnId: string; kind: 'user'; text: string; seats: string[] }
4673
- | { at: number; turnId: string; kind: 'seat'; cliKey: string; text: string; ok: boolean; usage: ChatUsage | null };
5264
+ | { at: number; turnId: string; kind: 'seat'; cliKey: string; text: string; ok: boolean; usage: ChatUsage | null }
5265
+ /**
5266
+ * The citation verdicts of the `seat` record with the same `turnId` + `cliKey` (api-types 0.68.0,
5267
+ * crew#561) — a SEPARATE record because the transcript is append-only and verification finishes
5268
+ * after the reply is stored. A reader folds it onto that reply; a skin that does not know this
5269
+ * kind ignores it (the `kind` discriminant is the extension point). Present only for a reply that
5270
+ * cited something; the live frame is {@link ChatCitationsFrame}, with the same fields.
5271
+ */
5272
+ | {
5273
+ at: number;
5274
+ turnId: string;
5275
+ kind: 'citations';
5276
+ cliKey: string;
5277
+ verified: number;
5278
+ unverifiable: number;
5279
+ corrected: number;
5280
+ unchecked: number;
5281
+ items: ChatCitationItem[];
5282
+ };
4674
5283
 
4675
5284
  /** `GET /chats/:id` → 200. */
4676
5285
  export interface ChatDetailResponse {
@@ -5045,8 +5654,12 @@ export interface Campaign {
5045
5654
  * `delivery === 'delivered'`.
5046
5655
  */
5047
5656
  export interface CampaignNodeDelivery {
5048
- delivery: 'delivered' | 'stranded' | 'vacuous' | 'none';
5657
+ delivery: 'delivered' | 'pushed' | 'stranded' | 'vacuous' | 'none';
5049
5658
  deliverUrl?: string;
5659
+ /** Present exactly when `delivery === 'pushed'` (api-types 0.69.0, N1). */
5660
+ deliverBranch?: string;
5661
+ /** Present exactly when `delivery === 'pushed'`; userinfo removed. */
5662
+ deliverRemote?: string;
5050
5663
  }
5051
5664
 
5052
5665
  /**
@@ -5059,8 +5672,12 @@ export interface CampaignNodeDelivery {
5059
5672
  export interface AttachedRunView {
5060
5673
  runId: string;
5061
5674
  status: SessionStatus;
5062
- delivery: 'delivered' | 'stranded' | 'vacuous' | 'none';
5675
+ delivery: 'delivered' | 'pushed' | 'stranded' | 'vacuous' | 'none';
5063
5676
  deliverUrl?: string;
5677
+ /** Present exactly when `delivery === 'pushed'` (api-types 0.69.0, N1). */
5678
+ deliverBranch?: string;
5679
+ /** Present exactly when `delivery === 'pushed'`; userinfo removed. */
5680
+ deliverRemote?: string;
5064
5681
  }
5065
5682
 
5066
5683
  /**
@@ -5248,6 +5865,9 @@ export interface AcpCliDiagnostics {
5248
5865
  lastStartedTs: number | null;
5249
5866
  /** Epoch ms of the newest `acpFallback`, or `null` when none recorded. */
5250
5867
  lastFallbackTs: number | null;
5868
+ /** (core#581) The seat's ACP version pin, probed now — present only for a pinned seat, which
5869
+ * lists here even with no ACP events yet (zero counts). */
5870
+ versionPin?: SeatVersionPin;
5251
5871
  }
5252
5872
 
5253
5873
  /** The ACP fold of `GET /diagnostics` — keyed by `cliKey` (`claude`, `pi`, `codex`, …). */
@@ -5364,6 +5984,36 @@ export interface DiagnosticsGovernance {
5364
5984
  findings: DiagnosticsGovernanceFinding[];
5365
5985
  }
5366
5986
 
5987
+ /** `POST /governance/deadletters/replay` body (api-types 0.51.0): replay THIS daemon's dead-letter
5988
+ * outbox into its own governance store. `dryRun: true` folds the outbox and moves nothing. */
5989
+ export interface GovernanceReplayBody {
5990
+ dryRun?: boolean;
5991
+ }
5992
+
5993
+ /** `POST /governance/deadletters/replay` answer (api-types 0.51.0) — the `governance replay` CLI's
5994
+ * outcome. A dry run carries `fold` (what the outbox holds) and `blocker`: why a real replay could
5995
+ * not run on this daemon (in-memory store, an engine without the binding), `null` when it can. A
5996
+ * real replay reports what landed (`replayed`), what an earlier replay had already landed
5997
+ * (`alreadyPresent`, `null` on an engine that does not say) and what did not (`failed` — those
5998
+ * entries are back on the live outbox and stay dead letters). 409: no governance store / a replay
5999
+ * already running / a blocker; 501: the engine cannot replay. */
6000
+ export interface GovernanceReplayOutcome {
6001
+ outbox: string;
6002
+ store: { path: string; source: DiagnosticsGovernanceStoreSource };
6003
+ /** Where a real replay archived the drained outbox; `null` on a dry run. */
6004
+ archive: string | null;
6005
+ read: number;
6006
+ replayed: number;
6007
+ alreadyPresent: number | null;
6008
+ failed: number;
6009
+ dryRun: boolean;
6010
+ /** The same-event conflation caveat the CLI prints, or `null`. */
6011
+ note: string | null;
6012
+ /** Dry run only: the outbox fold (the `/diagnostics` dead-letter block minus the legacy pointer). */
6013
+ fold?: Omit<DiagnosticsGovernanceDeadletters, 'legacyOutbox'>;
6014
+ blocker: string | null;
6015
+ }
6016
+
5367
6017
  /** The skills seam's state as `GET /diagnostics` reports it (skills keystone, api-types 0.28.0). */
5368
6018
  export type DiagnosticsSkillsState = 'published' | 'fallback' | 'blocked' | 'config-error' | 'disabled';
5369
6019
 
@@ -5670,9 +6320,219 @@ export interface PolicyLandingResult {
5670
6320
  * predating the policy→steering landing (forward-additive, DES-MEM-FACETED-001 §5.2).
5671
6321
  */
5672
6322
  export type ApproveProposalResponse =
5673
- | { outcome: 'promoted'; active_id: string }
6323
+ | { outcome: 'promoted'; active_id: string; edited?: ProposalEdit }
5674
6324
  | { outcome: 'handed_off'; payload: unknown; landing?: PolicyLandingResult };
5675
6325
 
6326
+ /**
6327
+ * `POST /proposals/:id/approve` body (api-types 0.49.0) — present only for an accept WITH an edit;
6328
+ * no body (or `{}`) is the plain approve. estate has no proposal edit, so the daemon submits the
6329
+ * edited copy to the same queue (`payload.edited_from = <id>`), approves it, then rejects the
6330
+ * original. Memory proposals only (400 otherwise; 404 when `id` is not pending; 409 while another
6331
+ * accept of the same `id` is in flight).
6332
+ */
6333
+ export interface ApproveProposalBody {
6334
+ /** The edited memory text (replaces `payload.content`). */
6335
+ content?: string;
6336
+ /** `"pattern"`: the copy drops its `project`/`repo` facets, so it may be recalled on ANY project
6337
+ * (the human act that lets a pattern cross projects; refused on anything but a captured memory, `payload.capture: "memory"`).
6338
+ * `"project"`: it stays scoped to its project — gaining the proposing run's project as its
6339
+ * `project` facet when the worker filed none (400 when no project files that run). An accept
6340
+ * that changes nothing the store keeps approves the original as filed (no copy, no `edited`). */
6341
+ reach?: 'project' | 'pattern';
6342
+ }
6343
+
6344
+ /**
6345
+ * `POST /proposals` body (api-types 0.54.0) — a skin files ONE preference into the review queue
6346
+ * (studio's takes: "prefers takes like X", filed when a person picks one of two candidate outputs).
6347
+ * The daemon files it as `kind_type: "memory"` with `payload {content, tier: "semantic",
6348
+ * capture: "preference", source?}`; the kind is never the caller's (unknown keys are a 400).
6349
+ * Nothing is learned until the person accepts it on `/proposals`.
6350
+ */
6351
+ export interface FileProposalBody {
6352
+ /** The preference in words (1–4000 chars, trimmed). */
6353
+ content: string;
6354
+ /** Becomes the `project` facet, so the preference is recalled on that project only. */
6355
+ project?: string;
6356
+ /** Where the pick was made, e.g. `doc:<docId>@v3` (kept on the payload). */
6357
+ source?: string;
6358
+ }
6359
+
6360
+ /** `POST /proposals` → 201: the pending proposal's id. */
6361
+ export interface FileProposalResponse {
6362
+ id: string;
6363
+ }
6364
+
6365
+ /** An accept-with-edit's trail: the original proposal (now rejected) and the approved copy. */
6366
+ export interface ProposalEdit {
6367
+ from: string;
6368
+ to: string;
6369
+ /** The copy was approved but rejecting the original failed: it is still pending, for the person
6370
+ * to reject. Absent when the original was rejected. */
6371
+ originalPending?: true;
6372
+ }
6373
+
6374
+ // ── Capture (Studio OS behaviour 8, api-types 0.49.0) ─────────────────────────────
6375
+
6376
+ /** One dropped text file (a transcript, meeting notes). */
6377
+ export interface CaptureTextFile {
6378
+ name: string;
6379
+ text: string;
6380
+ }
6381
+
6382
+ /** One dropped photo, read by a vision-capable seat. `mediaType` is image/png, image/jpeg,
6383
+ * image/gif or image/webp. */
6384
+ export interface CaptureImageFile {
6385
+ name: string;
6386
+ mediaType: string;
6387
+ dataBase64: string;
6388
+ }
6389
+
6390
+ /**
6391
+ * `POST /projects/:id/capture` body: notes and/or files (at least one). The daemon lands them in a
6392
+ * per-run inbox and launches a repo-less run filed to the project (through `POST /runs`, so its
6393
+ * refusals are that route's). The run's output is proposals in the `/proposals` queue — each with
6394
+ * `kind_type: "memory"` (or `"policy:<type>"` for a rule), `payload.capture` = `intent` |
6395
+ * `decision` | `memory` | `rule`, `provenance.run_id` = the returned `runId`, and `facets.project`
6396
+ * unless the team marked a memory `payload.reach: "pattern"` (the user decides on accept).
6397
+ */
6398
+ export interface CaptureBody {
6399
+ notes?: string;
6400
+ files?: Array<CaptureTextFile | CaptureImageFile>;
6401
+ }
6402
+
6403
+ /** `POST /projects/:id/capture` → 201. */
6404
+ export interface CaptureResponse {
6405
+ runId: string;
6406
+ }
6407
+
6408
+ // ── The Demo experience (wicked-studio#373, api-types 0.61.0) ────────────────────────────────
6409
+
6410
+ /**
6411
+ * `POST /projects/:id/demo` body: the app to film, who the demo is for and what to show. The daemon
6412
+ * mints the run's demo root, writes the brief there and launches the built-in `demo` preset (the
6413
+ * wicked-garden demo skill's plan → record → review) through `POST /runs`, so its refusals are that
6414
+ * route's. The demo root is the run's only extra write root; a caller never names it.
6415
+ */
6416
+ export interface DemoLaunchBody {
6417
+ /** The running app (http or https). The recorder is read-only against it. */
6418
+ url: string;
6419
+ audience: string;
6420
+ show: string;
6421
+ /** The roster to seat the team from, as `POST /runs` takes it. Omit for the daemon's roster. */
6422
+ clisJson?: string;
6423
+ }
6424
+
6425
+ /** `POST /projects/:id/demo` → 201. */
6426
+ export interface DemoLaunchResponse {
6427
+ runId: string;
6428
+ }
6429
+
6430
+ /** `PUT /runs/:id/demo/script` body: the presenter's edit of `script.md` (plan gate only; ≤ 256 KB). */
6431
+ export interface DemoScriptBody {
6432
+ content: string;
6433
+ }
6434
+
6435
+ /**
6436
+ * Where a demo run stands. `preparing` covers the PA's scope step and any phase the floor added before
6437
+ * `plan`; `team_gate` is the team plan's approval gate (`plan_approval`: the run holds a plan rev past
6438
+ * 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`:
6439
+ * approve, or `action: 'request_changes'` with the note as `amend` — at the plan gate it re-runs `plan`,
6440
+ * at the review gate it rewinds to `record`). The review gate is the one after `review` passed, or the
6441
+ * engine's escalation gate when the review did not pass (`review.rejected`).
6442
+ */
6443
+ export type DemoStage =
6444
+ | 'preparing'
6445
+ | 'team_gate'
6446
+ | 'planning'
6447
+ | 'plan_gate'
6448
+ | 'recording'
6449
+ | 'reviewing'
6450
+ | 'review_gate'
6451
+ | 'done'
6452
+ | 'failed';
6453
+
6454
+ /** One chapter of the plan's `chapters.json`, with whether its segment has been recorded. */
6455
+ export interface DemoChapter {
6456
+ /** `NN-slug` — the segment key record.mjs re-records by. */
6457
+ key: string;
6458
+ title: string;
6459
+ blurb: string;
6460
+ tags: string[];
6461
+ resets: string[];
6462
+ recorded: boolean;
6463
+ }
6464
+
6465
+ /** A chapter marker of the stitched MP4 (record.mjs's `chapters.md`). */
6466
+ export interface DemoMarker {
6467
+ /** `m:ss`. */
6468
+ at: string;
6469
+ sec: number;
6470
+ title: string;
6471
+ }
6472
+
6473
+ /** One finding of the reviewer's verdict block. */
6474
+ export interface DemoFinding {
6475
+ at: string;
6476
+ chapter: string;
6477
+ issue: string;
6478
+ verdict: 're-encode' | 're-record' | 'fix-app';
6479
+ }
6480
+
6481
+ /** `GET /runs/:id/demo` → 200 (404 for a run that is not a demo run). */
6482
+ export interface DemoView {
6483
+ runId: string;
6484
+ /** From the brief the daemon wrote. */
6485
+ url: string | null;
6486
+ audience: string | null;
6487
+ stage: DemoStage;
6488
+ /** `script.md` — the presenter script (null until `plan` writes it). */
6489
+ script: string | null;
6490
+ chapters: DemoChapter[];
6491
+ markers: DemoMarker[];
6492
+ /** Root-relative paths for `GET /runs/:id/demo/file?path=`. */
6493
+ sheets: Array<{ name: 'chapters' | 'joins' | 'end'; path: string }>;
6494
+ video: { path: string; bytes: number } | null;
6495
+ /** From the recorder's `recording.json`: true only when every stitched segment was recorded read-only;
6496
+ * null before a stitch (or from a recorder without the guard). */
6497
+ recording: { readOnly: boolean | null };
6498
+ /**
6499
+ * The reviewer's last output and its parsed verdict block (empty until `review` has finished).
6500
+ * `rejected`: the engine judged the review NOT PASS, so the review gate is its escalation gate — a
6501
+ * failed review cannot be accepted: approve re-runs the reviewer, `request_changes` sends the note back
6502
+ * to `record`, reject cancels the run.
6503
+ */
6504
+ review: { verdict: 'accept' | 'changes' | null; findings: DemoFinding[]; text: string | null; rejected: boolean };
6505
+ /** The seats that recorded and reviewed — evaluator ≠ creator puts them apart. */
6506
+ seats: { recorder: string | null; reviewer: string | null };
6507
+ /** The script says which data is synthetic. */
6508
+ syntheticLabelled: boolean;
6509
+ }
6510
+
6511
+ /** What `POST /runs/:id/demo/export` makes (EP-C3; api-types 0.72.0). The MP4 itself needs no export: download `video.path`. */
6512
+ export type DemoExportFormat = 'gif' | 'poster';
6513
+
6514
+ /**
6515
+ * `POST /runs/:id/demo/export` body (EP-C3; api-types 0.72.0). `gif` encodes the stitched MP4 into
6516
+ * `demo-video/demo.gif` (two-pass palette, 10 fps, 720 px wide); `poster` writes one frame to
6517
+ * `demo-video/poster.jpg`. The daemon encodes with ffmpeg off its event loop and a timeout (GIF 180 s,
6518
+ * poster 30 s), only at the review gate or once the run has ended. Refusals: 400 a bad body, 404 not a demo run, 409 no stitched MP4 yet / the run is still recording / `demo-video/` changed mid-encode, 422 no frame
6519
+ * at `atSec`, 502 ffmpeg failed, 503 no ffmpeg on the daemon host (`hint` says how to install it),
6520
+ * 504 the encode ran past its timeout. A second identical request while one encodes joins it.
6521
+ */
6522
+ export interface DemoExportBody {
6523
+ format: DemoExportFormat;
6524
+ /** Poster only: the frame, in seconds from the start (0 … 86400). Default: the first chapter marker + 1 s, or 1 s with no markers. */
6525
+ atSec?: number;
6526
+ }
6527
+
6528
+ /** `POST /runs/:id/demo/export` → 200: the file, ready for `GET /runs/:id/demo/file?path=`. */
6529
+ export interface DemoExportResponse {
6530
+ format: DemoExportFormat;
6531
+ /** `demo-video/demo.gif` or `demo-video/poster.jpg` (root-relative). */
6532
+ path: string;
6533
+ bytes: number;
6534
+ }
6535
+
5676
6536
  /** `POST /proposals/:id/reject` → 200. */
5677
6537
  export interface RejectProposalResponse {
5678
6538
  ok: true;
@@ -5774,16 +6634,1703 @@ export interface MemoryCoverageResponse {
5774
6634
  }
5775
6635
 
5776
6636
  /**
5777
- * `POST /memory/retire` body. Retire is SUBTREE-scoped, never per-id: estate `memory.erase`
5778
- * hard-deletes EVERY memory whose scope equals or descends from `scope_prefix`. `scope_prefix` is
5779
- * required and must be non-empty (estate refuses a total wipe) — the surface must show the operator
5780
- * the whole subtree it will remove, not a single row.
6637
+ * `POST /memory/retire` body. This retire is SUBTREE-scoped: estate `memory.erase` hard-deletes
6638
+ * EVERY memory whose scope equals or descends from `scope_prefix`. `scope_prefix` is required and
6639
+ * must be non-empty (estate refuses a total wipe) — the surface must show the operator the whole
6640
+ * subtree it will remove. To remove ONE row, use {@link RetireMemoryItemBody}.
5781
6641
  */
5782
6642
  export interface RetireMemoryBody {
5783
6643
  scope_prefix: string;
5784
6644
  }
5785
6645
 
5786
- /** `POST /memory/retire` → 200. The number of memories the subtree wipe deleted. */
5787
- export interface RetireMemoryResponse {
6646
+ /**
6647
+ * `POST /memory/retire-item` body (studio#206; api-types 0.60.0): exactly ONE memory, by the
6648
+ * `memory_id` `GET /memory` listed. 404 when no memory has that id; 501 `estate_upgrade_required`
6649
+ * when the estate predates erase-by-id (wicked-estate#188) — nothing is deleted then.
6650
+ */
6651
+ export interface RetireMemoryItemBody {
6652
+ memory_id: string;
6653
+ }
6654
+
6655
+ /** `POST /memory/retire` / `POST /memory/retire-item` → 200. How many memories were deleted
6656
+ * (always 1 for `retire-item`). */
6657
+ export interface RetireMemoryResponse {
5788
6658
  erased: number;
5789
6659
  }
6660
+
6661
+ // ── Team events on the bus (DES-TEAMING-002 §6, wicked-core T1; api-types 0.41.0) ──────────────
6662
+ //
6663
+ // The `wicked.team.*` facts wicked-core publishes on the cross-product bus (`domain`
6664
+ // `wicked-core`, `subdomain` `core.team`), one owner per type (DES-002 §7: E engine, S supervisor,
6665
+ // R attempt runner). Crew publishes none of them: it relays and reads. Mirrors wicked-core
6666
+ // `src/team/events.rs`; `packages/crew/tests/team-events.test.ts` pins every type against the
6667
+ // engine's own round-trip fixtures by value. Payload keys are snake_case and every key is
6668
+ // present (an absent value is `null`), except a plan step's optional fields, which a plan omits
6669
+ // when unset. The embedded {@link TeamLedger} keeps DES-001 §7's camelCase.
6670
+
6671
+ /** The 25 `wicked.team.<noun>.<verb>` event types. */
6672
+ export type TeamEventType =
6673
+ | 'wicked.team.path.started'
6674
+ | 'wicked.team.path.scored'
6675
+ | 'wicked.team.plan.proposed'
6676
+ | 'wicked.team.plan.revised'
6677
+ | 'wicked.team.plan.accepted'
6678
+ | 'wicked.team.plan.refused'
6679
+ | 'wicked.team.member.joined'
6680
+ | 'wicked.team.member.left'
6681
+ | 'wicked.team.step.claimed'
6682
+ | 'wicked.team.checkpoint.reached'
6683
+ | 'wicked.team.finding.raised'
6684
+ | 'wicked.team.advice.delivered'
6685
+ | 'wicked.team.advice.answered'
6686
+ | 'wicked.team.help.requested'
6687
+ | 'wicked.team.help.answered'
6688
+ | 'wicked.team.change.requested'
6689
+ | 'wicked.team.step.completed'
6690
+ | 'wicked.team.step.reviewed'
6691
+ | 'wicked.team.finding.settled'
6692
+ | 'wicked.team.council.called'
6693
+ | 'wicked.team.council.ruled'
6694
+ | 'wicked.team.ledger.folded'
6695
+ | 'wicked.team.gate.opened'
6696
+ | 'wicked.team.gate.decided'
6697
+ | 'wicked.team.path.ended';
6698
+
6699
+ /** The fields every team payload carries. */
6700
+ export interface TeamEnvelope {
6701
+ run_id: string;
6702
+ ord: number | null;
6703
+ attempt: number | null;
6704
+ /**
6705
+ * Who acted: a seated CLI instance (the PA, a member or the authoring monitor, e.g.
6706
+ * `'claude#1'`, `'claude#2'`, `'codex'`), `'engine'`, `'human'` or `'council:<task id>'`.
6707
+ */
6708
+ by: string;
6709
+ /** Epoch milliseconds at the producer. */
6710
+ at: number;
6711
+ /** The row this one answers, e.g. `'finding.raised#4'`. */
6712
+ re: string | null;
6713
+ }
6714
+
6715
+ /** A plan step: a catalog entry plus the step fields a plan may set (omitted when unset). */
6716
+ export interface TeamPlanStep {
6717
+ catalog: string;
6718
+ id: string;
6719
+ instructions?: string;
6720
+ owner?: 'pa' | 'team' | (string & {});
6721
+ depends_on?: string[];
6722
+ /** A raised gate, as the workflow's externally tagged `GateSpec`. */
6723
+ gate?: unknown;
6724
+ added_by?: 'plan' | 'floor' | (string & {});
6725
+ floor_reason?: string;
6726
+ late?: boolean;
6727
+ }
6728
+
6729
+ export interface TeamPlanOverride {
6730
+ remove: string[];
6731
+ reason: string;
6732
+ }
6733
+
6734
+ export type TeamPathStartedPayload = TeamEnvelope & {
6735
+ cli: string;
6736
+ selection: 'chosen' | 'random' | (string & {});
6737
+ roster: string[];
6738
+ request: string;
6739
+ workflow: string | null;
6740
+ plan: boolean;
6741
+ };
6742
+
6743
+ export type TeamPathScoredPayload = TeamEnvelope & {
6744
+ /** `'intent:<proposal_id>'` | `'diff:<ord>:<attempt>:<rescore_seq>'`. */
6745
+ score_source: string;
6746
+ basis: 'intent' | 'diff' | (string & {});
6747
+ score: number;
6748
+ deterministic: number;
6749
+ reasons: string[];
6750
+ model: { add: number; rationale: string } | null;
6751
+ signals: {
6752
+ changed_symbols: number;
6753
+ dependents: number;
6754
+ products: number;
6755
+ contract_change: boolean;
6756
+ test_gap: number;
6757
+ critical: boolean;
6758
+ destructive: boolean;
6759
+ truncated: boolean;
6760
+ } | null;
6761
+ plan: { monitors: number; depth: string; post_hoc_reviewer: boolean; post_hoc_other_cli: boolean };
6762
+ tree: string | null;
6763
+ };
6764
+
6765
+ export type TeamPlanProposedPayload = TeamEnvelope & {
6766
+ proposal_id: string;
6767
+ base_rev: number | null;
6768
+ kind: 'initial' | 'change' | 'edit' | (string & {});
6769
+ preset: string | null;
6770
+ steps: TeamPlanStep[];
6771
+ monitors: { asked: number };
6772
+ asks: string[];
6773
+ touch: string[];
6774
+ override: TeamPlanOverride | null;
6775
+ rationale: string;
6776
+ };
6777
+
6778
+ export type TeamPlanRevisedPayload = TeamEnvelope & {
6779
+ plan_rev: number;
6780
+ proposal_id: string | null;
6781
+ reason: 'floor_raised' | 'pa_added' | 'member_request' | (string & {});
6782
+ from_band: string;
6783
+ to_band: string;
6784
+ high_risk: boolean;
6785
+ added: TeamPlanStep[];
6786
+ };
6787
+
6788
+ export type TeamPlanAcceptedPayload = TeamEnvelope & {
6789
+ plan_rev: number;
6790
+ workflow_id: string;
6791
+ band: string;
6792
+ high_risk: boolean;
6793
+ mode: 'auto' | 'manual' | (string & {});
6794
+ steps: TeamPlanStep[];
6795
+ override: TeamPlanOverride | null;
6796
+ proposal_id: string | null;
6797
+ };
6798
+
6799
+ export type TeamPlanRefusedPayload = TeamEnvelope & {
6800
+ proposal_id: string;
6801
+ base_rev: number | null;
6802
+ reason: string;
6803
+ };
6804
+
6805
+ export type TeamMemberJoinedPayload = TeamEnvelope & {
6806
+ member_id: string;
6807
+ open_seq: number;
6808
+ seat: string;
6809
+ role: 'monitor' | (string & {});
6810
+ status: 'attached' | 'failed' | (string & {});
6811
+ reason: string;
6812
+ error: string | null;
6813
+ };
6814
+
6815
+ export type TeamMemberLeftPayload = TeamEnvelope & {
6816
+ member_id: string;
6817
+ open_seq: number;
6818
+ seat: string;
6819
+ status: 'completed' | 'budget_exhausted' | 'failed' | 'timed_out' | (string & {});
6820
+ batches: number;
6821
+ error: string | null;
6822
+ };
6823
+
6824
+ export type TeamStepClaimedPayload = TeamEnvelope & {
6825
+ step_id: string;
6826
+ role: string;
6827
+ kind: string;
6828
+ phase: string;
6829
+ criterion: string;
6830
+ baseline_tree: string | null;
6831
+ repo: { workdir: string; git_dir: string } | null;
6832
+ code_graph_db: string | null;
6833
+ };
6834
+
6835
+ export type TeamCheckpointReachedPayload = TeamEnvelope & {
6836
+ seq: number;
6837
+ tool_call_id: string;
6838
+ kind: string;
6839
+ title: string;
6840
+ status: 'completed' | 'failed' | (string & {});
6841
+ paths: string[];
6842
+ };
6843
+
6844
+ export type TeamFindingRaisedPayload = TeamEnvelope & {
6845
+ raise_seq: number;
6846
+ finding_id: string;
6847
+ member_id: string;
6848
+ line_key: string | null;
6849
+ anchor: string | null;
6850
+ anchor_source: 'graph' | 'hunk' | 'none' | (string & {}) | null;
6851
+ severity: 'high' | 'medium' | (string & {});
6852
+ path: string;
6853
+ line: number;
6854
+ evidence: string;
6855
+ claim: string;
6856
+ suggestion: string | null;
6857
+ tree: string;
6858
+ in_diff: boolean;
6859
+ corroborated_by: string[];
6860
+ };
6861
+
6862
+ export type TeamAdviceDeliveredPayload = TeamEnvelope & {
6863
+ raise_seq: number;
6864
+ finding_id: string;
6865
+ /** The steer id | `'boundary:<step_id>:<attempt>'` | `'end:<attempt>'`. */
6866
+ delivery_id: string;
6867
+ steer_id: string | null;
6868
+ channel: 'acp_steering' | 'boundary' | 'none' | (string & {});
6869
+ outcome: 'injected' | 'turn_ended' | 'refused' | 'not_delivered' | (string & {});
6870
+ detail: string | null;
6871
+ };
6872
+
6873
+ export type TeamAdviceAnsweredPayload = TeamEnvelope & {
6874
+ raise_seq: number;
6875
+ /** `'<step_id>:<attempt>'`. */
6876
+ answered_in: string;
6877
+ finding_id: string;
6878
+ disposition: 'accepted' | 'declined' | (string & {});
6879
+ reason: string;
6880
+ };
6881
+
6882
+ export type TeamHelpRequestedPayload = TeamEnvelope & {
6883
+ help_id: string;
6884
+ help_seq: number;
6885
+ question: string;
6886
+ context: string;
6887
+ };
6888
+
6889
+ export type TeamHelpAnsweredPayload = TeamEnvelope & {
6890
+ help_id: string;
6891
+ answer_id: string;
6892
+ answer: string;
6893
+ evidence: string[];
6894
+ };
6895
+
6896
+ export type TeamChangeRequestedPayload = TeamEnvelope & {
6897
+ change_id: string;
6898
+ change_seq: number;
6899
+ steps: TeamPlanStep[];
6900
+ reason: string;
6901
+ };
6902
+
6903
+ export type TeamStepCompletedPayload = TeamEnvelope & {
6904
+ step_id: string;
6905
+ status: 'ok' | 'failed' | 'cancelled' | 'elicitation_failed' | 'timed_out' | (string & {});
6906
+ tree: string | null;
6907
+ output_bytes: number;
6908
+ output_ref: string;
6909
+ };
6910
+
6911
+ export type TeamStepReviewedPayload = TeamEnvelope & {
6912
+ step_id: string;
6913
+ verdict: 'accepted' | 'rejected' | (string & {});
6914
+ to: 'member' | 'pa' | (string & {}) | null;
6915
+ reason: string;
6916
+ };
6917
+
6918
+ export type TeamFindingSettledPayload = TeamEnvelope & {
6919
+ raise_seq: number;
6920
+ finding_id: string;
6921
+ status: 'held' | 'withdrawn' | 'superseded' | (string & {});
6922
+ reason: string;
6923
+ final_line: number | null;
6924
+ };
6925
+
6926
+ export type TeamCouncilCalledPayload = TeamEnvelope & {
6927
+ /** `'finding:<raise_seq>'` | `'step:<step_id>:<attempt>'`. */
6928
+ subject: string;
6929
+ finding_id: string | null;
6930
+ trigger: 'unresolved_high' | 'member_step' | (string & {});
6931
+ question: string;
6932
+ positions: { by: string; position: string; reason: string }[];
6933
+ evidence: string;
6934
+ excluded_seats: string[];
6935
+ transcript: number[];
6936
+ };
6937
+
6938
+ export type TeamCouncilRuledPayload = TeamEnvelope & {
6939
+ subject: string;
6940
+ verdict: 'yes' | 'no' | 'no_verdict' | (string & {});
6941
+ reason: 'no_quorum' | 'seats_benched' | 'error' | 'timeout' | 'cap' | (string & {}) | null;
6942
+ task_id: string | null;
6943
+ consensus: boolean;
6944
+ agreement_pct: number;
6945
+ dissent: string[];
6946
+ returned: number;
6947
+ seated: number;
6948
+ };
6949
+
6950
+ /** DES-001 §7 `teamLedger.findings[]` (camelCase). */
6951
+ export interface TeamLedgerFinding {
6952
+ findingId: string;
6953
+ monitorId: string;
6954
+ seat: string;
6955
+ severity: 'high' | 'medium' | (string & {});
6956
+ path: string;
6957
+ line: number;
6958
+ evidence: string;
6959
+ claim: string;
6960
+ suggestion: string | null;
6961
+ tree: string;
6962
+ inDiff: boolean;
6963
+ finalLine: number | null;
6964
+ corroboratedBy: string[];
6965
+ delivery: 'injected' | 'not_delivered' | (string & {});
6966
+ status: 'accepted' | 'declined' | 'withdrawn' | 'unanswered' | 'superseded' | (string & {});
6967
+ workerReason: string | null;
6968
+ monitorReply: { kind: 'hold' | 'withdraw' | (string & {}); reason: string } | null;
6969
+ dispute: {
6970
+ verdict: 'yes' | 'no' | 'no_verdict' | (string & {});
6971
+ agreementPct: number | null;
6972
+ dissent: number | null;
6973
+ seats: string[];
6974
+ reason: string | null;
6975
+ } | null;
6976
+ /**
6977
+ * (T6, api-types 0.42.0) The enclosing location the finding id is minted over (git's funcname
6978
+ * heuristic). Omitted when file-level.
6979
+ */
6980
+ anchor?: string;
6981
+ /** (T6, api-types 0.42.0) The attempt that first raised it, when it was carried into a redriven attempt. Omitted otherwise. */
6982
+ carriedFromAttempt?: number;
6983
+ }
6984
+
6985
+ /**
6986
+ * (T6, DES-002 §8.8; api-types 0.42.0) The PA's review of a member's step, as the attempt that
6987
+ * carried the review recorded it.
6988
+ */
6989
+ export interface TeamStepReview {
6990
+ stepId: string;
6991
+ /** The member attempt the review is about. */
6992
+ reviewedAttempt: number;
6993
+ verdict: 'accepted' | 'rejected' | (string & {});
6994
+ to: 'member' | 'pa' | (string & {}) | null;
6995
+ reason: string;
6996
+ /** On a rejection: `true` the member held (`HOLD`), `false` it accepted, `null` no answer on record. */
6997
+ held: boolean | null;
6998
+ memberReason: string | null;
6999
+ /** The one-off council a hold convened. */
7000
+ dispute: TeamLedgerFinding['dispute'];
7001
+ }
7002
+
7003
+ /** DES-001 §7 `teamLedger` without its envelope: the fold of one attempt's team stream. */
7004
+ export interface TeamLedger {
7005
+ finalPass: 'completed' | 'timed_out' | 'skipped' | 'stream_gap' | (string & {});
7006
+ renderedToJudge: boolean;
7007
+ teamPause: boolean;
7008
+ monitors: {
7009
+ monitorId: string;
7010
+ seat: string;
7011
+ batches: number;
7012
+ status: 'completed' | 'budget_exhausted' | 'failed' | 'timed_out' | (string & {});
7013
+ error: string | null;
7014
+ }[];
7015
+ findings: TeamLedgerFinding[];
7016
+ rejected: { malformed: number; belowBar: number; unconfirmed: number; duplicate: number };
7017
+ /** (T6, api-types 0.42.0) The PA's reviews of member steps this attempt carried. Omitted when none. */
7018
+ stepReviews?: TeamStepReview[];
7019
+ }
7020
+
7021
+ export type TeamLedgerFoldedPayload = TeamEnvelope & {
7022
+ final_pass: 'completed' | 'timed_out' | 'skipped' | 'stream_gap' | (string & {});
7023
+ ledger: TeamLedger;
7024
+ transport: 'bus' | 'none' | (string & {});
7025
+ transcript: {
7026
+ from_event_id: number;
7027
+ to_event_id: number;
7028
+ count: number;
7029
+ truncated: boolean;
7030
+ events: { event_id: number; event_type: string; payload: unknown }[];
7031
+ };
7032
+ };
7033
+
7034
+ /** `gate.opened`'s kind-specific fields, discriminated on `kind`. */
7035
+ export type TeamGateOpenedPayload = TeamEnvelope & { gate_id: string } & (
7036
+ | { kind: 'unit_review'; ledger_ref: string | null; ledger_source: 'folded' | 'synthesized' | 'no_bus' | (string & {}) }
7037
+ | {
7038
+ kind: 'plan_approval';
7039
+ reviewing_ord: number;
7040
+ plan_rev: number;
7041
+ band: string;
7042
+ high_risk: boolean;
7043
+ mode: 'auto' | 'manual' | (string & {});
7044
+ reason: 'manual_mode' | 'high_risk' | 'into_high_risk' | 'override' | (string & {});
7045
+ diff: { from_rev: number | null; added: string[] };
7046
+ }
7047
+ | { kind: 'team_dispute'; finding_ids: string[] }
7048
+ | { kind: 'team_transport'; fact: string; reason: string }
7049
+ );
7050
+
7051
+ export type TeamGateDecidedPayload = TeamEnvelope & {
7052
+ gate_id: string;
7053
+ kind: 'unit_review' | 'plan_approval' | 'team_dispute' | 'team_transport' | (string & {});
7054
+ decision:
7055
+ | 'allow'
7056
+ | 'deny'
7057
+ | 'paused'
7058
+ | 'human_approved'
7059
+ | 'human_amended'
7060
+ | 'human_rejected'
7061
+ | (string & {});
7062
+ combined: boolean | null;
7063
+ team_pause: boolean;
7064
+ unresolved: string[];
7065
+ };
7066
+
7067
+ export type TeamPathEndedPayload = TeamEnvelope & {
7068
+ status: 'completed' | 'failed' | 'cancelled' | (string & {});
7069
+ };
7070
+
7071
+ /** Each team event type's payload. */
7072
+ export interface TeamEventPayloads {
7073
+ 'wicked.team.path.started': TeamPathStartedPayload;
7074
+ 'wicked.team.path.scored': TeamPathScoredPayload;
7075
+ 'wicked.team.plan.proposed': TeamPlanProposedPayload;
7076
+ 'wicked.team.plan.revised': TeamPlanRevisedPayload;
7077
+ 'wicked.team.plan.accepted': TeamPlanAcceptedPayload;
7078
+ 'wicked.team.plan.refused': TeamPlanRefusedPayload;
7079
+ 'wicked.team.member.joined': TeamMemberJoinedPayload;
7080
+ 'wicked.team.member.left': TeamMemberLeftPayload;
7081
+ 'wicked.team.step.claimed': TeamStepClaimedPayload;
7082
+ 'wicked.team.checkpoint.reached': TeamCheckpointReachedPayload;
7083
+ 'wicked.team.finding.raised': TeamFindingRaisedPayload;
7084
+ 'wicked.team.advice.delivered': TeamAdviceDeliveredPayload;
7085
+ 'wicked.team.advice.answered': TeamAdviceAnsweredPayload;
7086
+ 'wicked.team.help.requested': TeamHelpRequestedPayload;
7087
+ 'wicked.team.help.answered': TeamHelpAnsweredPayload;
7088
+ 'wicked.team.change.requested': TeamChangeRequestedPayload;
7089
+ 'wicked.team.step.completed': TeamStepCompletedPayload;
7090
+ 'wicked.team.step.reviewed': TeamStepReviewedPayload;
7091
+ 'wicked.team.finding.settled': TeamFindingSettledPayload;
7092
+ 'wicked.team.council.called': TeamCouncilCalledPayload;
7093
+ 'wicked.team.council.ruled': TeamCouncilRuledPayload;
7094
+ 'wicked.team.ledger.folded': TeamLedgerFoldedPayload;
7095
+ 'wicked.team.gate.opened': TeamGateOpenedPayload;
7096
+ 'wicked.team.gate.decided': TeamGateDecidedPayload;
7097
+ 'wicked.team.path.ended': TeamPathEndedPayload;
7098
+ }
7099
+
7100
+ /** One team bus row, discriminated on `event_type`. */
7101
+ export type TeamBusEvent = {
7102
+ [K in TeamEventType]: { event_type: K; payload: TeamEventPayloads[K] };
7103
+ }[TeamEventType];
7104
+
7105
+ // ── The crew team surface (DES-TEAMING-002 T8; api-types 0.42.0) ─────────────────────────────
7106
+ //
7107
+ // Commands go through the API, facts go on the bus (§4.0). Crew RELAYS the engine's
7108
+ // `wicked.team.*` rows onto `/ws`, READS them back per run, and POSTs commands to the engine. It
7109
+ // publishes no team row.
7110
+
7111
+ /**
7112
+ * A `/ws` frame carrying one `wicked.team.*` bus row, relayed verbatim (the `interactiveEvent`
7113
+ * pattern). `project_id` is set when the run's membership files it, as on every other frame.
7114
+ */
7115
+ export interface TeamEventFrame {
7116
+ type: 'teamEvent';
7117
+ /** The whole bus row as wicked-bus delivers it. */
7118
+ event: TeamBusEvent & { event_id: number; [k: string]: unknown };
7119
+ project_id?: string;
7120
+ }
7121
+
7122
+ // ── The watch registry wire contract (DES-TRIGGER-REGISTRY-001 §4.5; TR-W4; api-types 0.70.0) ──
7123
+ //
7124
+ // The registry is advisory by construction: a watch row can never allow, approve or block. Its
7125
+ // facts are two bus types owned by crew (`wicked.crew.watch_finding.{raised,cleared}`), relayed
7126
+ // onto `/ws` as {@link WatchEventFrame} by a bus tap (never broadcast directly, so the socket and
7127
+ // the record cannot disagree). Types only: the runtime lands in TR-W5a, and a daemon without it
7128
+ // simply never sends these frames or serves these routes.
7129
+
7130
+ /** The two bus types the watch registry publishes. */
7131
+ export type WatchEventType = 'wicked.crew.watch_finding.raised' | 'wicked.crew.watch_finding.cleared';
7132
+
7133
+ /** How a watch row is meant: a contradiction in the work's own evidence, a look-worthy flag, or a candidate rule for the review queue. */
7134
+ export type WatchEmitAs = 'finding' | 'flag' | 'proposal';
7135
+ export type WatchSeverity = 'high' | 'medium' | 'info';
7136
+ /** The Watchtower's kind column. There is no `needs` kind: needs-you stays studio's own fold. */
7137
+ export type WatchKind = 'problem' | 'decision' | 'done' | 'quiet' | 'delivery';
7138
+ /** `p0` is never shed; `p1` is shed after `p2` when the push ring overflows. */
7139
+ export type WatchPriority = 'p0' | 'p1' | 'p2';
7140
+
7141
+ /** Where in the run a watch row points: "Jump in" scrolls to the nearest event at or after `at`. */
7142
+ export interface WatchAnchor {
7143
+ run_id: string;
7144
+ ord: number | null;
7145
+ attempt: number | null;
7146
+ /** Unix millis of the source event. */
7147
+ at: number;
7148
+ }
7149
+
7150
+ /** One piece of evidence a watch row cites. `run_event` names a persisted CoreEvent; `bus_row` a bus row. */
7151
+ export interface WatchEvidenceRef {
7152
+ kind: 'run_event' | 'bus_row' | (string & {});
7153
+ type: string;
7154
+ ord?: number | null;
7155
+ attempt?: number | null;
7156
+ /** For `bus_row`: the cited row's `event_id`. */
7157
+ event_id?: number;
7158
+ }
7159
+
7160
+ /**
7161
+ * The payload of `wicked.crew.watch_finding.raised`. Its first six fields are the team
7162
+ * envelope's (`DES-TEAMING-002` §4.4). `watch_id` is `"w-"` + the deterministic key over
7163
+ * (`run_id | "-"`, `entry_id`, `entry_version`, `subject`), so a replay or a redelivery resolves to
7164
+ * the same row.
7165
+ */
7166
+ export interface WatchFinding {
7167
+ /** `null` for a row no run owns (a registry-internal flag such as `registry-lagging`). */
7168
+ run_id: string | null;
7169
+ ord: number | null;
7170
+ attempt: number | null;
7171
+ /** `"watch:<entry_id>@<entry_version>"`. */
7172
+ by: string;
7173
+ /** Unix millis the row was produced. */
7174
+ at: number;
7175
+ /** What it answers: `"<CoreEvent type>#<ord>:<attempt>"`, or a bus source row's idempotency key. */
7176
+ re: string;
7177
+ watch_id: string;
7178
+ entry_id: string;
7179
+ entry_version: number;
7180
+ /** `"deterministic:<name>"` or `"llm:<prompt-ref>@<n>"`. */
7181
+ check: string;
7182
+ kind: WatchEmitAs;
7183
+ severity: WatchSeverity;
7184
+ watch_kind: WatchKind;
7185
+ /** `"gate"`: render as one line on the open gate's existing row, never a new row. */
7186
+ attach: 'gate' | null;
7187
+ /** The run's project membership, as the relay tags frames; absent when the run is unfiled. */
7188
+ project_id?: string;
7189
+ /** The one plain sentence the Watchtower shows. */
7190
+ sentence: string;
7191
+ /** Entry-specific facts, at most 4 KB, redacted. */
7192
+ facts: Record<string, unknown>;
7193
+ anchor: WatchAnchor | null;
7194
+ evidence: WatchEvidenceRef[];
7195
+ /** Set for an `llm:*` check only. */
7196
+ model: { seat: string; model: string; latency_ms: number } | null;
7197
+ /** How many later rows this roll-up stands for (`0` on an ordinary row). */
7198
+ rolled_up: number;
7199
+ }
7200
+
7201
+ /** The fields every `wicked.crew.watch_finding.cleared` payload carries. */
7202
+ export interface WatchFindingClearedBase {
7203
+ run_id: string | null;
7204
+ ord: number | null;
7205
+ attempt: number | null;
7206
+ by: string;
7207
+ at: number;
7208
+ re: string;
7209
+ /** The raised row this clears. */
7210
+ watch_id: string;
7211
+ entry_id: string;
7212
+ entry_version: number;
7213
+ project_id?: string;
7214
+ }
7215
+
7216
+ /**
7217
+ * The payload of `wicked.crew.watch_finding.cleared`, discriminated on `reason`: the raised row
7218
+ * `watch_id` names is resolved (a later attempt passed, the worker produced output), dismissed by
7219
+ * the operator ("Seen, not a problem": `dismissed_by` says who), or replaced by a roll-up
7220
+ * (`replaced_by` names it). The set is closed on purpose: no clearing reason is allow-like.
7221
+ */
7222
+ export type WatchFindingCleared =
7223
+ | (WatchFindingClearedBase & { reason: 'resolved' })
7224
+ | (WatchFindingClearedBase & { reason: 'dismissed'; dismissed_by: string })
7225
+ | (WatchFindingClearedBase & { reason: 'rolled_up'; replaced_by: string });
7226
+
7227
+ export interface WatchEventPayloads {
7228
+ 'wicked.crew.watch_finding.raised': WatchFinding;
7229
+ 'wicked.crew.watch_finding.cleared': WatchFindingCleared;
7230
+ }
7231
+
7232
+ /** One watch bus row, discriminated on `event_type`. */
7233
+ export type WatchBusEvent = {
7234
+ [K in WatchEventType]: { event_type: K; payload: WatchEventPayloads[K] };
7235
+ }[WatchEventType];
7236
+
7237
+ /**
7238
+ * A `/ws` frame carrying one `wicked.crew.watch_finding.*` bus row, relayed verbatim (the
7239
+ * {@link TeamEventFrame} pattern). `project_id` is set when the run's membership files it.
7240
+ */
7241
+ export interface WatchEventFrame {
7242
+ type: 'watchEvent';
7243
+ /** The whole bus row as wicked-bus delivers it. */
7244
+ event: WatchBusEvent & { event_id: number; [k: string]: unknown };
7245
+ project_id?: string;
7246
+ }
7247
+
7248
+ /**
7249
+ * What one check can see on one run. Studio never renders an empty list as "all clear" when an entry
7250
+ * was `not_checked`; a `not_checked` entry always says why, in plain words ("no declared scope", "the
7251
+ * daemon was down").
7252
+ */
7253
+ export type WatchCoverage =
7254
+ | { entry_id: string; state: 'checked' }
7255
+ | { entry_id: string; state: 'not_checked'; reason: string };
7256
+
7257
+ /** `GET /watch?project=&kind=&run=&since=&limit=`: the feed fold, newest first. */
7258
+ export interface WatchFeedResponse {
7259
+ findings: WatchFinding[];
7260
+ /**
7261
+ * The clearing rows of the findings on this page (a late join folds them as the live
7262
+ * `watch_finding.cleared` frames would: Fixed, Dismissed, rolled up). api-types 0.73.0.
7263
+ */
7264
+ cleared?: WatchFindingCleared[];
7265
+ /** Present with `run=` only. */
7266
+ coverage?: WatchCoverage[];
7267
+ }
7268
+
7269
+ /**
7270
+ * Where an entry's key point comes from. `core`: a CoreEvent from the daemon fan-in. `bus`: a pulled
7271
+ * 4-segment bus type. `watchdog`: a stall-watchdog frame (`workerStalled`, …). `internal`: the
7272
+ * registry's own tick or check-failure report (`registry-lagging`, `registry-check-failed`).
7273
+ * (api-types 0.73.0 adds `watchdog` and `internal`.)
7274
+ */
7275
+ export type WatchEntrySource = 'core' | 'bus' | 'watchdog' | 'internal';
7276
+
7277
+ /** Who changed one operator setting of an entry, and when (stamped by the daemon, never the caller). */
7278
+ export interface WatchSettingStamp {
7279
+ by: string;
7280
+ /** Unix millis. */
7281
+ at: number;
7282
+ }
7283
+
7284
+ /** One entry's operator override in `SystemSettings.watch.entries`. */
7285
+ export interface WatchEntryOverride {
7286
+ enabled?: boolean;
7287
+ /** Merged over the shipped threshold, validated by the entry's check. */
7288
+ threshold?: Record<string, unknown>;
7289
+ /** Daemon-stamped; a caller cannot set these. */
7290
+ enabled_changed?: WatchSettingStamp;
7291
+ threshold_changed?: WatchSettingStamp;
7292
+ }
7293
+
7294
+ /** `SystemSettings.watch`. */
7295
+ export interface WatchSettings {
7296
+ entries?: Record<string, WatchEntryOverride>;
7297
+ /** The LLM lane (off by default; TR-W9). */
7298
+ llm?: boolean;
7299
+ }
7300
+
7301
+ /**
7302
+ * One registry entry (`packages/crew/watch/entries/<id>.json`), as `GET /watch/entries` serves it:
7303
+ * the effective entry (the operator's `enabled` / `threshold` override applied) plus its threshold
7304
+ * in plain words. The schema has no allow-like `emit.as` value.
7305
+ */
7306
+ export interface WatchEntry {
7307
+ /** Stable, `[a-z0-9-]{3,48}`; equals the file name. */
7308
+ id: string;
7309
+ /** Bumped on any semantic change; part of every finding key. */
7310
+ version: number;
7311
+ /** See {@link WatchEntrySource}. A `bus` type is a 4-segment `wicked.<domain>.<noun>.<verb>` type. */
7312
+ on: { source: WatchEntrySource; type: string };
7313
+ /** JSON-path equality, AND of keys; an array means "any of". */
7314
+ filter: Record<string, unknown>;
7315
+ check: string;
7316
+ params: Record<string, unknown>;
7317
+ threshold: Record<string, unknown>;
7318
+ emit: {
7319
+ as: WatchEmitAs;
7320
+ severity: WatchSeverity;
7321
+ watch_kind: WatchKind;
7322
+ attach: 'gate' | null;
7323
+ /** Past `per_run`, one roll-up replaces the rest. */
7324
+ rate: { per_run: number };
7325
+ };
7326
+ priority: WatchPriority;
7327
+ enabled: boolean;
7328
+ /** The threshold in plain words, for "When Studio speaks up". Always set on `GET /watch/entries`. */
7329
+ threshold_text: string;
7330
+ }
7331
+
7332
+ /** Who changed an operator-adjustable entry setting, and when (read from the `settings.updated` audit entry). */
7333
+ export interface WatchEntryChange {
7334
+ id: string;
7335
+ by: string;
7336
+ /** Unix millis. */
7337
+ at: number;
7338
+ }
7339
+
7340
+ /** `GET /watch/health`. Every turned-off entry and every changed threshold is listed with who and when. */
7341
+ export interface WatchHealth {
7342
+ /** The registry is running. `false` ⇒ `reason` says why. */
7343
+ armed: boolean;
7344
+ reason: string | null;
7345
+ sources: {
7346
+ bus: 'ok' | 'lagging' | 'down' | (string & {});
7347
+ core: 'ok' | 'lagging' | 'down' | (string & {});
7348
+ watchdog: 'ok' | 'lagging' | 'down' | (string & {});
7349
+ };
7350
+ queue: { depth: number; hwm: number; shed_by_priority: Record<WatchPriority, number> };
7351
+ tail_lag_ms: number;
7352
+ emit: { failed: number };
7353
+ entries: {
7354
+ loaded: number;
7355
+ refused: Array<{ id: string; reason: string }>;
7356
+ off: WatchEntryChange[];
7357
+ thresholds_changed: WatchEntryChange[];
7358
+ };
7359
+ llm: { enabled: boolean; inflight: number; timeouts: number; skipped_no_seat: number };
7360
+ /** `core_gap: true`: runs that started and ended while the daemon was down were not evaluated this boot. */
7361
+ replay?: { core_gap: boolean };
7362
+ }
7363
+
7364
+ /** One `wicked.team.*` bus row as `GET /runs/:id/team` returns it. */
7365
+ export type TeamRow = TeamBusEvent & {
7366
+ event_id: number;
7367
+ /** wicked-bus `emitted_at` (Unix millis). */
7368
+ emitted_at: number;
7369
+ /**
7370
+ * Set on a `wicked.team.ledger.folded` row the unit's gate did NOT read: a `gate.opened
7371
+ * {kind:"unit_review"}` for the same ord and attempt names another ledger (`ledger_ref` null,
7372
+ * a synthesized ledger). `gate.opened.ledger_ref` is the only authority on what the gate read.
7373
+ */
7374
+ unused?: true;
7375
+ };
7376
+
7377
+ /**
7378
+ * One unit's team state in {@link RunTeamResponse}: the engine's persisted snapshot
7379
+ * (`Core.runTeam`; the last five fields are `null` until the unit's attempt folded) plus the
7380
+ * unit's bus rows.
7381
+ */
7382
+ export interface RunTeamUnit {
7383
+ ord: number;
7384
+ /** `null` until the unit dispatched. */
7385
+ transport: 'bus' | 'none' | (string & {}) | null;
7386
+ reason: string | null;
7387
+ /** Where the gate's ledger came from: folded by the supervisor, synthesized by the worker on the final-pass timeout, or local (no bus). */
7388
+ ledgerSource: 'folded' | 'synthesized' | 'no_bus' | (string & {}) | null;
7389
+ /** The supervisor row the gate read (`"ledger.folded#<ord>:<attempt>"`); `null` for a synthesized or local ledger. */
7390
+ ledgerRef: string | null;
7391
+ finalPass: 'completed' | 'timed_out' | 'skipped' | 'stream_gap' | (string & {}) | null;
7392
+ /** An unresolved HIGH (or an incomplete record) stands without a council YES: the run pauses `team_dispute`. */
7393
+ teamPause: boolean | null;
7394
+ /** How many findings the attempt's ledger holds. */
7395
+ findings: number | null;
7396
+ /** The unit's bus rows (`payload.ord` = this ord), every attempt, ordered by `event_id`. Empty when the bus has none. */
7397
+ rows: TeamRow[];
7398
+ /**
7399
+ * The folded ledger the gate read (the `ledger.folded` row `ledgerRef` names), or `null`: not
7400
+ * folded yet, synthesized or local (read `UnitEvidence.team` on the evidence route), or the bus
7401
+ * no longer has the row (the snapshot fields above still stand).
7402
+ */
7403
+ ledger: TeamLedger | null;
7404
+ }
7405
+
7406
+ /**
7407
+ * `GET /runs/:id/team`. The run's team transport and per-unit team state. A run that is not a team
7408
+ * run (free text, a registered def: bug, chat, onboarding, …) answers `teamed: false`,
7409
+ * `transport: null` and `units: []` — nothing to render, and no outage.
7410
+ */
7411
+ export interface RunTeamResponse {
7412
+ runId: string;
7413
+ /** Whether the run is a team run at all. `false` ⇒ `transport` and `reason` are `null`. */
7414
+ teamed: boolean;
7415
+ /**
7416
+ * For a team run: `"bus"`; `"none"` (the teamed run fell back to no team transport: render the
7417
+ * "team transport unavailable" banner with `reason`); `"pending"` (not decided yet);
7418
+ * `"unavailable"` (this daemon has no bus: the run is paused `team_transport` until the operator
7419
+ * answers it). `null` for a run that is not a team run.
7420
+ */
7421
+ transport: 'bus' | 'none' | 'pending' | 'unavailable' | (string & {}) | null;
7422
+ reason: string | null;
7423
+ /** The first bus event id of the run's stream. */
7424
+ streamFloor: number | null;
7425
+ planRev: number | null;
7426
+ /** The required fact the run waits on (its event type), if any. */
7427
+ pending: string | null;
7428
+ /** The run is terminal and its end is on record (a run that is not a team run: its status is terminal). */
7429
+ ended: boolean;
7430
+ units: RunTeamUnit[];
7431
+ /** The run-level rows (`payload.ord` null: `path.*`, `plan.*`), ordered by `event_id`. */
7432
+ rows: TeamRow[];
7433
+ }
7434
+
7435
+ /**
7436
+ * `POST /runs/:id/plan` body: a plan edit. At a `plan_approval` gate the edit IS the gate answer
7437
+ * (the engine proposes, floor-fills and accepts it, or refuses it and re-opens the gate) and the
7438
+ * route answers `{ status }`. Anywhere else on a live planned run (running, or paused at another
7439
+ * gate) it is a MID-RUN edit (`Core.proposePlan`): `plan.steps` are the steps to ADD, held and
7440
+ * applied at the run's next step boundary — its author approved it, so no gate opens — and the
7441
+ * route answers {@link PlanProposalResponse}. A mid-run edit carries no `touch` / `override`.
7442
+ */
7443
+ export interface EditPlanBody {
7444
+ plan: LaunchPlan;
7445
+ /**
7446
+ * The caller's id for this edit: the engine's idempotency key for a mid-run edit, so a retried
7447
+ * POST carrying the same id proposes once (`duplicate: true`). Omitted ⇒ the daemon mints a
7448
+ * fresh UUID for that POST, so an omitted-id retry is a SECOND edit — send one to retry safely.
7449
+ * A gate edit is keyed on its gate, not on this id.
7450
+ */
7451
+ requestId?: string;
7452
+ }
7453
+
7454
+ /** `POST /runs/:id/plan` at a `plan_approval` gate: the run's status after the gate answer. */
7455
+ export interface EditPlanGateResponse {
7456
+ status: SessionStatus;
7457
+ }
7458
+
7459
+ /**
7460
+ * `POST /runs/:id/plan` mid-run: the engine's `PlanProposal` (`Core.proposePlan`), what the held
7461
+ * edit does to the run — read off the dry run it was validated by, since the author's own edit
7462
+ * opens no gate. Refusals (a plan awaiting approval — edit it at the gate —, a started deliver
7463
+ * step, a finished or unplanned run, an edit the revision refuses) answer 409 with the engine
7464
+ * reason.
7465
+ */
7466
+ export interface PlanProposalResponse {
7467
+ /** The `plan.proposed` id the edit is published under. */
7468
+ proposal_id: string;
7469
+ /** `true`: this `requestId` was already taken — nothing new is held or published. */
7470
+ duplicate: boolean;
7471
+ /** The floor band of the rev the edit makes (`"40-69"`, …); `null` for a duplicate. */
7472
+ band: string | null;
7473
+ /** §8.5's high-risk rule on that rev: `true` = the edit moved the run into high risk. `null` for a duplicate. */
7474
+ high_risk: boolean | null;
7475
+ /** The floor phase types (catalog ids) the edit adds; empty for a duplicate. */
7476
+ floor_added: string[];
7477
+ }
7478
+
7479
+ /** `POST /runs/:id/plan`: a gate answer (`status`) or a mid-run proposal (`proposal_id`). */
7480
+ export type EditPlanResponse = EditPlanGateResponse | PlanProposalResponse;
7481
+
7482
+ /** `POST /team/outbox/replay`: the engine's replay of `<state home>/team-outbox.ndjson`. */
7483
+ export interface TeamOutboxReplayReport {
7484
+ /** `[idempotency key, bus event id]` of every fact that reached the bus, in publish order. */
7485
+ published: Array<[string, number]>;
7486
+ superseded: number;
7487
+ invalid: number;
7488
+ remaining: number;
7489
+ /** `[run id, reason]` per lane that stopped. */
7490
+ failures: Array<[string, string]>;
7491
+ }
7492
+
7493
+ /** One phase type of the engine's catalog (`GET /catalog`, `Core.catalog`), in catalog order —
7494
+ * the row studio's phase picker renders. */
7495
+ export interface CatalogEntry {
7496
+ id: string;
7497
+ kind: StageKindPhase;
7498
+ role: PhaseRole;
7499
+ gate: GateSpec;
7500
+ gate_type: GateType | null;
7501
+ executes_code: boolean;
7502
+ /** `"tool"`: a step of this entry supplies its own command (`deliver`); else `"agent"`. */
7503
+ executor: 'agent' | 'tool';
7504
+ validator_pin: string | null;
7505
+ /** The entry carries a validator pin (a step may not remove or swap it). */
7506
+ pinned: boolean;
7507
+ /** That pin is the evidence floor. */
7508
+ evidence_floor: boolean;
7509
+ /**
7510
+ * The entry declares re-verified evidence: a step of it is an acceptance requirement of the run
7511
+ * that contains it (`test`, `domain_coverage`). ABSENT from an engine before the flag (api-types
7512
+ * 0.46.0) — the daemon then fails a plan run's acceptance closed rather than guess.
7513
+ */
7514
+ verified_evidence?: boolean;
7515
+ skill_ref: string | null;
7516
+ /** The entry's one-line description; `null` when it has none. */
7517
+ description: string | null;
7518
+ }
7519
+
7520
+ /** `GET /catalog`. */
7521
+ export interface CatalogResponse {
7522
+ entries: CatalogEntry[];
7523
+ }
7524
+
7525
+ /** `POST /plans/preview` body: a draft plan, previewed as the `POST /runs {plan}` launch with the
7526
+ * same fields would decide it. */
7527
+ export interface PlanPreviewBody {
7528
+ plan: LaunchPlan;
7529
+ /** Only matters to a preset name, so it does not change a plan's preview. */
7530
+ projectId?: string;
7531
+ /** The launch's gate mode (`humanConfirm` on `POST /runs`); absent = auto. */
7532
+ humanConfirm?: string;
7533
+ /** The registered repo the launch would run on: a behavioural `touch` set is scored against its
7534
+ * code graph at the base the launch would start from (read locally, no fetch). An unregistered
7535
+ * repo is a 400. Absent ⇒ the fail-closed score (`graph: "unavailable"`). */
7536
+ repoRef?: string;
7537
+ /** The launch's delivery (`deliver` on `POST /runs`): `"pr"` hands the engine the launch's
7538
+ * deliver step, so the preview's floor carries `deliver` as the launch's would. Absent or
7539
+ * `"none"` ⇒ no deliver step (a plan launch's own default). */
7540
+ deliver?: 'pr' | 'none';
7541
+ }
7542
+
7543
+ /**
7544
+ * `POST /plans/preview`: the engine's `PlanPreview` (`Core.previewPlan`) — what the launch would
7545
+ * compute (precheck, intent score, floor fill, approval matrix, planning checks), persisting and
7546
+ * publishing nothing. A refusal (the launch's own, an unregistered repo) is a 400.
7547
+ */
7548
+ export interface PlanPreviewResponse {
7549
+ /** The intent score (§8.2), its deterministic part, one reason per contribution. */
7550
+ score: number;
7551
+ deterministic: number;
7552
+ reasons: string[];
7553
+ /** The destructive signal the floor fill reads. */
7554
+ destructive: boolean;
7555
+ /** The floor band the score lands in (`"40-69"`, `"70-100"`, …). */
7556
+ band: string;
7557
+ high_risk: boolean;
7558
+ /** The floor phase types the plan owes, in order. The steps the floor ADDED are the `steps`
7559
+ * with `added_by: "floor"`. */
7560
+ floor: string[];
7561
+ /** The floor override as recorded (manual mode only). */
7562
+ floor_override: TeamPlanOverride | null;
7563
+ /** The floor-filled steps; each carries `added_by`, and `floor_reason` when the floor added it. */
7564
+ steps: TeamPlanStep[];
7565
+ /** The def the run would plan from (`compose` of `steps`). */
7566
+ def: WorkflowDef;
7567
+ /** `true` ⇒ the launch would pause at a `plan_approval` gate before its first unit. */
7568
+ pauses: boolean;
7569
+ pause_reason: 'manual_mode' | 'high_risk' | 'override' | (string & {}) | null;
7570
+ /** `"ready"`: the score read the repo's code graph. `"not_needed"`: a docs-only touch set (or no
7571
+ * creator and no touch set). `"unavailable"`: the fail-closed score (no repo, no or a stale
7572
+ * graph — `reasons` says which). `"pending_pa_scope"` (wicked-core#633, DES-TEAMING-002 X1;
7573
+ * api-types 0.47.0): a creator plan with no `touch` set — the launch runs the PA's read-only
7574
+ * `pa-scope` step first and scores the plan from its answer, so `score`, `band` and the floor
7575
+ * shown are the baseline's with `pa-scope` first, NOT the plan's final risk, and `pauses` is
7576
+ * manual mode's alone (an auto-mode launch may still pause once the PA's scope lands high). */
7577
+ graph: 'ready' | 'not_needed' | 'unavailable' | 'pending_pa_scope';
7578
+ }
7579
+
7580
+ // ── Standing orders (Studio OS behaviour 10; api-types 0.50.0) ─────────────────────────────────
7581
+
7582
+ /**
7583
+ * A standing order's structured rule — parsed from plain words by a seat and CONFIRMED by the
7584
+ * operator before it is stored. `trigger.phase` is the phase the gate reviews (the reviewed unit's
7585
+ * `phase_ref`) or `*`. The invariant: `approve` never answers the deliver gate, a plan approval,
7586
+ * an escalation, a failure or a triage gate, and never a finding (the create route refuses a rule
7587
+ * that approves `deliver`, `plan_approval` or a finding with a 400 `order_refused`).
7588
+ */
7589
+ export interface StandingOrderRule {
7590
+ scope: { kind: 'all' } | { kind: 'project'; projectId: string };
7591
+ /** `band` (api-types 0.53.0, optional): the order matches only a run whose accepted plan landed in
7592
+ * that band (`"0-19"`, …); a run with no scored plan never matches a band-scoped order.
7593
+ * `preset` (api-types 0.56.0, optional): only a run launched from that preset (`team_plan.preset`).
7594
+ * `phase: "plan_approval"` with `action: "approve"` is a TRUST RECEIPT (0.56.0): accepted only
7595
+ * with `band: "0-19"`, a `preset` and a `project` scope, and it answers a run's plan gate only
7596
+ * when that open gate itself reports band 0-19 and no high risk. `deliver` is never approvable. */
7597
+ trigger: { kind: 'gate'; phase: string; band?: string | undefined; preset?: string | undefined } | { kind: 'finding'; severity: 'high' | 'medium' | '*' };
7598
+ action: 'approve' | 'hold' | 'notify';
7599
+ activeWhen: 'away' | 'always';
7600
+ }
7601
+
7602
+ export interface StandingOrder {
7603
+ id: string;
7604
+ /** The operator's own words. */
7605
+ text: string;
7606
+ rule: StandingOrderRule;
7607
+ /** Unix millis. */
7608
+ createdAt: number;
7609
+ }
7610
+
7611
+ /** An outbound message a `notify` order wanted sent. Only ever QUEUED — the daemon sends nothing. */
7612
+ export interface QueuedStandingMessage {
7613
+ id: string;
7614
+ orderId: string;
7615
+ orderText: string;
7616
+ runId: string | null;
7617
+ text: string;
7618
+ /** Unix millis. */
7619
+ at: number;
7620
+ status: 'queued';
7621
+ }
7622
+
7623
+ /** `GET /standing-orders`, and the answer to `PUT /standing-orders/away`. */
7624
+ export interface StandingOrdersState {
7625
+ away: boolean;
7626
+ /** Unix millis the flag turned on; `null` while present. */
7627
+ awaySince: number | null;
7628
+ orders: StandingOrder[];
7629
+ outbox: QueuedStandingMessage[];
7630
+ }
7631
+
7632
+ /** `PUT /standing-orders/away`. */
7633
+ export interface StandingAwayBody {
7634
+ away: boolean;
7635
+ }
7636
+
7637
+ /** `POST /standing-orders/parse` — the plain words one seat reads. */
7638
+ export interface ParseStandingOrderBody {
7639
+ text: string;
7640
+ }
7641
+
7642
+ /** `POST /standing-orders/parse`'s 200: the rule to show back for confirmation. `refused` is set
7643
+ * when storing it would be refused (the invariant), in plain words. A 422 carries the seat's own
7644
+ * `answer` when it could not produce a rule; 409 = no seat can take a turn; 502 = the seat failed. */
7645
+ export interface ParsedStandingOrder {
7646
+ rule: StandingOrderRule;
7647
+ seat: string;
7648
+ refused?: string;
7649
+ }
7650
+
7651
+ /** `POST /standing-orders` — the confirmed rule. 201 `{ order }`. */
7652
+ export interface CreateStandingOrderBody {
7653
+ text: string;
7654
+ rule: StandingOrderRule;
7655
+ }
7656
+
7657
+ // ── Decided-gate history (api-types 0.53.0) ────────────────────────────────────────────────────
7658
+
7659
+ /**
7660
+ * One decided gate — `GET /gates/decided?since=<unix ms>` (newest first, at most 1000). One row per
7661
+ * `gate.decided` audit line, joined to the engine's gate row for the same `(run, ord)` and to the
7662
+ * run. What a skin needs for a seat's track record on a step, and for the preview of a proposed
7663
+ * standing order (`phase` and `orderApprovable` are the order evaluator's own readings).
7664
+ */
7665
+ export interface DecidedGate {
7666
+ runId: string;
7667
+ /** The unit the gate paused before; `null` when the decision named none (a plan edit). */
7668
+ ord: number | null;
7669
+ /** Unix millis. */
7670
+ decidedAt: number;
7671
+ decision: 'approve' | 'request_changes' | 'reject' | 'edit_plan';
7672
+ /** Who decided: a person's actor id, or `standing-order:<id>`. */
7673
+ actor: string;
7674
+ /** A standing order decided it (the actor is `standing-order:<id>`). */
7675
+ byOrder: boolean;
7676
+ /** The engine's gate kind (`def`, `run_level`, `terminal`, `deliver`, `plan_approval`, …); `null` when unknown. */
7677
+ gateKind: string | null;
7678
+ /** The phase an order's trigger matches (`intake`, or the reviewed unit's phase); `null` when unknown. */
7679
+ phase: string | null;
7680
+ projectId: string | null;
7681
+ /** The band the run's accepted plan landed in; `null` for an unscored run. */
7682
+ band: string | null;
7683
+ /** The unit whose work the gate judged (the newest creator before the gate) and its seat; `null` when none. */
7684
+ creator: { seat: string; phase: string | null; ord: number } | null;
7685
+ /** A standing order could have approved this gate (a phase-review gate of a run that lands no doctrine). */
7686
+ orderApprovable: boolean;
7687
+ }
7688
+
7689
+ /** `GET /gates/decided`'s 200. A malformed `since` answers 400. */
7690
+ export interface DecidedGatesResponse {
7691
+ gates: DecidedGate[];
7692
+ }
7693
+
7694
+ // ── Freeze deliveries (api-types 0.55.0) ───────────────────────────────────────────────────────
7695
+
7696
+ /**
7697
+ * `GET|PUT /deliveries/freeze` — the one switch that holds every deliver gate. While `frozen`, an
7698
+ * approve of a gate that would run a deliver unit answers 409 `{code: "deliveries_frozen"}` and
7699
+ * `POST /runs/:id/deliver` refuses the same way; the gate stays open, so unfreezing lets the same
7700
+ * approve through. A run launched with `deliverGate: "auto"` has no gate and is not held.
7701
+ * Audited as `deliveries.frozen {reason?}` / `deliveries.unfrozen`; a restart keeps the freeze.
7702
+ */
7703
+ export interface DeliveryFreezeState {
7704
+ frozen: boolean;
7705
+ /** ISO time it was turned on; `null` while thawed. */
7706
+ since: string | null;
7707
+ /** The actor id that turned it on; `null` while thawed. */
7708
+ by: string | null;
7709
+ reason: string | null;
7710
+ }
7711
+
7712
+ /** `PUT /deliveries/freeze`. `reason` (1–500 chars) is kept only when freezing. */
7713
+ export interface PutDeliveryFreezeBody {
7714
+ frozen: boolean;
7715
+ reason?: string;
7716
+ }
7717
+
7718
+ // ── MCP tools registry (DES-MCP-TOOLS-001 §5, §8, slice S2; api-types 0.58.0) ─────────────────
7719
+
7720
+ /**
7721
+ * How crew reaches an upstream: an MCP server over stdio or streamable HTTP, or a plain REST API
7722
+ * wrapped as tools from its OpenAPI document (`rest`, api-types 0.64.0).
7723
+ */
7724
+ export type McpUpstreamKind = 'mcp-stdio' | 'mcp-http' | 'rest';
7725
+ /**
7726
+ * A tool's class, from its own `tools/list` annotations (never from a carrier): `read` when
7727
+ * `readOnlyHint` is true; else `destructive` unless `destructiveHint` is false; else `write`. A tool
7728
+ * with no annotations, or with neither `readOnlyHint` nor `destructiveHint`, is `write`. An
7729
+ * operator's `classOverride` wins.
7730
+ */
7731
+ export type McpToolClass = 'read' | 'write' | 'destructive';
7732
+ /**
7733
+ * `registered`: the schema the operator saved is the one the server lists. `unregistered`: the
7734
+ * schema changed, or the server added the tool after the save; it is denied until previewed and
7735
+ * saved again. `gone`: the server no longer lists it (kept so old records resolve).
7736
+ */
7737
+ export type McpToolStatus = 'registered' | 'unregistered' | 'gone';
7738
+ /** `failing` after 3 consecutive failed probes; `never` = no probe has succeeded yet. */
7739
+ export type McpHealthState = 'ok' | 'failing' | 'never';
7740
+ /** `none` = the server needs no secret; `missing` = its reference resolves to nothing. */
7741
+ export type McpAuthState = 'none' | 'set' | 'missing';
7742
+
7743
+ /** The spec's tool annotations, only the fields it defines. */
7744
+ export interface McpToolAnnotations {
7745
+ title?: string;
7746
+ readOnlyHint?: boolean;
7747
+ destructiveHint?: boolean;
7748
+ idempotentHint?: boolean;
7749
+ openWorldHint?: boolean;
7750
+ }
7751
+
7752
+ /**
7753
+ * Where a server's secret comes from and where crew injects it. The registry stores this
7754
+ * reference only, never a value. `ref` is `keychain:wicked-mcp/<name>` (written by
7755
+ * `PUT /mcp/servers/:name/secret`) or `env:<NAME>` (the daemon's env). An `mcp-stdio` server gets
7756
+ * the value in env variable `env`; an `mcp-http` or `rest` server in header `header`, after `prefix`.
7757
+ */
7758
+ export interface McpAuthConfig {
7759
+ ref: string;
7760
+ env?: string;
7761
+ header?: string;
7762
+ prefix?: string;
7763
+ }
7764
+
7765
+ /**
7766
+ * `POST /mcp/servers/preview`. `mcp-stdio` takes `command` (+ `args`); `mcp-http` takes `url`.
7767
+ * `rest` takes `url`, the base URL every call of the API is pinned to (its scheme, host and port,
7768
+ * and its path as a prefix), plus its OpenAPI 3 document: `openapiUrl` (fetched by crew) or
7769
+ * `openapi` (the document itself, pasted), never both. `operations` picks which operations become
7770
+ * tools (by `operationId` or tool name); absent = all of them.
7771
+ */
7772
+ export interface McpServerConfigBody {
7773
+ /** 1–63 of `a-z 0-9 _ -`; the `<server>` of the subject `mcp:<server>/<tool>`. */
7774
+ name: string;
7775
+ kind: McpUpstreamKind;
7776
+ command?: string;
7777
+ args?: string[];
7778
+ url?: string;
7779
+ auth?: McpAuthConfig | null;
7780
+ /** `rest` only: where crew fetches the OpenAPI 3 document (JSON or YAML). */
7781
+ openapiUrl?: string;
7782
+ /** `rest` only: the OpenAPI 3 document itself (instead of `openapiUrl`); at most 1 MB as JSON. */
7783
+ openapi?: Record<string, unknown>;
7784
+ /** `rest` only: the operations to wrap, by `operationId` or tool name; absent = all. */
7785
+ operations?: string[];
7786
+ /**
7787
+ * The secret VALUE this server's keychain entry will hold (wicked-crew#719). The preview is
7788
+ * probed with it and it is held with the preview; `POST /mcp/servers` writes it to the OS store
7789
+ * as part of the save, so the secret and the registry row commit together — studio used to write
7790
+ * the keychain itself first, and a failure between the two writes left a keychain entry with no
7791
+ * server, or a server whose secret never landed. Requires `auth.ref` =
7792
+ * `keychain:wicked-mcp/<name>`; `501` when the platform has no OS secret store. It is in no
7793
+ * response, log, audit entry or file.
7794
+ */
7795
+ secret?: string;
7796
+ }
7797
+
7798
+ /**
7799
+ * How a `rest` tool turns its arguments into one HTTP request (api-types 0.64.0). The arguments are
7800
+ * an ALLOWLIST: an argument that no map names is dropped, never sent (its name is disclosed in the
7801
+ * result's `_meta["wicked/rest"].droppedArgs`). The request goes only to the server's base URL: a
7802
+ * request that would leave its scheme, host, port or base path, or a redirect to another host, is
7803
+ * refused before (or instead of) being followed, and recorded as a `guard_error`.
7804
+ */
7805
+ export interface McpRestMapping {
7806
+ method: 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
7807
+ /** The OpenAPI path, e.g. `/issues/{id}`; joined onto the base URL's path. */
7808
+ pathTemplate: string;
7809
+ /** argument → `{variable}` of `pathTemplate`. */
7810
+ pathMap: Record<string, string>;
7811
+ /** argument → query parameter. */
7812
+ queryMap: Record<string, string>;
7813
+ /** argument → request header (never the auth header, `Host`, `Cookie` or a hop-by-hop header). */
7814
+ headerMap: Record<string, string>;
7815
+ /** argument → top-level key of the JSON request body; `null` when the body is one argument or none. */
7816
+ bodyMap: Record<string, string> | null;
7817
+ /** The argument that is the whole JSON request body; `null` when the body is mapped by key or absent. */
7818
+ bodyArg: string | null;
7819
+ /** Every argument that is sent: the union of the maps and `bodyArg`. */
7820
+ argAllowlist: string[];
7821
+ timeoutMs: number;
7822
+ }
7823
+
7824
+ export interface McpHealth {
7825
+ state: McpHealthState;
7826
+ consecutiveFailures: number;
7827
+ checkedAt: string | null;
7828
+ /** The last probe's error, with any secret scrubbed out. */
7829
+ lastError: string | null;
7830
+ }
7831
+
7832
+ export interface McpTool {
7833
+ name: string;
7834
+ /** A `rest` tool's request mapping (api-types 0.64.0); `null` for an MCP server's tool. */
7835
+ rest?: McpRestMapping | null;
7836
+ /** The policy token: `mcp:<server>/<tool>`. */
7837
+ subject: string;
7838
+ description: string | null;
7839
+ annotations: McpToolAnnotations | null;
7840
+ inputSchema: Record<string, unknown> | null;
7841
+ derivedClass: McpToolClass;
7842
+ classOverride: McpToolClass | null;
7843
+ /** `classOverride ?? derivedClass`: what a policy sees. */
7844
+ class: McpToolClass;
7845
+ enabled: boolean;
7846
+ status: McpToolStatus;
7847
+ /** The saved schema's hash; `null` = never saved. */
7848
+ schemaHash: string | null;
7849
+ /** The hash the last probe saw. */
7850
+ observedSchemaHash: string;
7851
+ }
7852
+
7853
+ export interface McpServer {
7854
+ name: string;
7855
+ kind: McpUpstreamKind;
7856
+ command: string | null;
7857
+ args: string[];
7858
+ url: string | null;
7859
+ auth: McpAuthConfig | null;
7860
+ /** `rest` only (api-types 0.64.0): the OpenAPI URL, `null` when the document was pasted. */
7861
+ openapiUrl?: string | null;
7862
+ /** `rest` only: the operations that were picked; `null` = all. */
7863
+ operations?: string[] | null;
7864
+ authState: McpAuthState;
7865
+ enabled: boolean;
7866
+ health: McpHealth;
7867
+ registeredAt: string;
7868
+ updatedAt: string;
7869
+ tools: McpTool[];
7870
+ /** Over the tools that are not `gone`. */
7871
+ counts: { total: number; enabled: number; registered: number; read: number; write: number; destructive: number };
7872
+ }
7873
+
7874
+ /**
7875
+ * A server configured in a CLI's own home, by name only (never its command, URL or env).
7876
+ * `origin: 'worker'` is a server in a seat's worker home, which no wicked-handed server may be.
7877
+ */
7878
+ export interface McpDiscoveredServer {
7879
+ name: string;
7880
+ cli: string;
7881
+ origin: 'operator' | 'worker';
7882
+ /** The config file, relative to `~` or `<worker home>`. */
7883
+ source: string;
7884
+ /** Whether a registered server has the same name. */
7885
+ managed: boolean;
7886
+ }
7887
+
7888
+ /** `GET /mcp/servers`. */
7889
+ export interface McpServersResponse {
7890
+ servers: McpServer[];
7891
+ discovered: McpDiscoveredServer[];
7892
+ }
7893
+
7894
+ /** The difference between the registered tools and a probe, by tool name. */
7895
+ export interface McpToolDiff {
7896
+ added: string[];
7897
+ removed: string[];
7898
+ changed: string[];
7899
+ unchanged: string[];
7900
+ }
7901
+
7902
+ export interface McpPreviewTool {
7903
+ name: string;
7904
+ /** A `rest` tool's request mapping (api-types 0.64.0); `null` for an MCP server's tool. */
7905
+ rest?: McpRestMapping | null;
7906
+ subject: string;
7907
+ description: string | null;
7908
+ annotations: McpToolAnnotations | null;
7909
+ inputSchema: Record<string, unknown> | null;
7910
+ class: McpToolClass;
7911
+ schemaHash: string;
7912
+ }
7913
+
7914
+ /**
7915
+ * `POST /mcp/servers/preview`'s 200. Save it with `POST /mcp/servers {previewHash}` before
7916
+ * `expiresAt`. `diff` compares with the registered server of the same name, `null` when none.
7917
+ * A server that does not answer is a 502 `probe_failed`.
7918
+ */
7919
+ export interface McpPreviewResponse {
7920
+ previewHash: string;
7921
+ expiresAt: string;
7922
+ server: {
7923
+ name: string;
7924
+ kind: McpUpstreamKind;
7925
+ command: string | null;
7926
+ args: string[];
7927
+ url: string | null;
7928
+ auth: McpAuthConfig | null;
7929
+ openapiUrl?: string | null;
7930
+ operations?: string[] | null;
7931
+ };
7932
+ serverInfo: { name: string; version: string } | null;
7933
+ tools: McpPreviewTool[];
7934
+ diff: McpToolDiff | null;
7935
+ /** `rest` only (api-types 0.64.0): operations that could not be wrapped, each with why. */
7936
+ skipped?: string[];
7937
+ /**
7938
+ * Each tool's decision per phase role × seat × mode under the current policies and approvals,
7939
+ * judged as if this preview were saved now (api-types 0.63.0); see `withdrawOnSave`. `null` when
7940
+ * the engine cannot preview.
7941
+ */
7942
+ policies?: McpPolicyPreviewResponse | null;
7943
+ }
7944
+
7945
+ /** `POST /mcp/servers`. No `previewHash`, or one that is unknown or expired, is a 409. */
7946
+ export interface SaveMcpServerBody {
7947
+ previewHash?: string;
7948
+ }
7949
+
7950
+ /** `PATCH /mcp/servers/:name`. */
7951
+ export interface PatchMcpServerBody {
7952
+ enabled: boolean;
7953
+ }
7954
+
7955
+ /** `PATCH /mcp/tools/:subject` (subject URL-encoded). At least one field. */
7956
+ export interface PatchMcpToolBody {
7957
+ enabled?: boolean;
7958
+ classOverride?: McpToolClass | null;
7959
+ }
7960
+
7961
+ /** `POST /mcp/servers/:name/test`'s 200: a failed probe is `ok: false` and counts toward `failing`. */
7962
+ export interface McpServerTestResponse {
7963
+ ok: boolean;
7964
+ error: string | null;
7965
+ /** `null` when the probe failed. */
7966
+ diff: McpToolDiff | null;
7967
+ server: McpServer;
7968
+ }
7969
+
7970
+ /** `PUT /mcp/servers/:name/secret`: one line, 8–8192 characters. */
7971
+ export interface PutMcpSecretBody {
7972
+ value: string;
7973
+ }
7974
+
7975
+ /** The value is never echoed; `ref` goes into the server's `auth.ref`. 501 = no OS secret store. */
7976
+ export interface McpSecretResponse {
7977
+ ref: string;
7978
+ set: true;
7979
+ }
7980
+
7981
+ // ── MCP broker call path (DES-MCP-TOOLS-001 §6, §8 `POST /mcp/call`; slice S3) ─────────────────
7982
+
7983
+ /** `POST /mcp/call`: what the garden shim sends. `token` is the worker's `WICKED_MCP_TOKEN`. */
7984
+ export interface McpCallBody {
7985
+ token: string;
7986
+ /** `mcp:<server>/<tool>`. */
7987
+ subject: string;
7988
+ /** The tool's arguments (a JSON object); absent = `{}`. */
7989
+ args?: Record<string, unknown>;
7990
+ }
7991
+
7992
+ /** How a brokered call ended. Every outcome but `ok` means the result was not handed back. */
7993
+ export type McpCallOutcome =
7994
+ /** The call ran; `result` is the scrubbed upstream result (it may itself be a tool error, `isError`). */
7995
+ | 'ok'
7996
+ /** Refused by a policy or an engine gate before anything ran (403). The worker continues. */
7997
+ | 'denied'
7998
+ /** Waits for the operator's approval (first use of a server, or the run's mode) (409). */
7999
+ | 'pending_approval'
8000
+ /** The call ran, but the output policy withheld its result (403). */
8001
+ | 'withheld'
8002
+ /** The unit's call budget is spent (429). */
8003
+ | 'budget_exhausted'
8004
+ /** The tool failed 5 times in a row and is refused for 60 s (503). */
8005
+ | 'breaker_open'
8006
+ /** The upstream failed (502) or timed out (504). */
8007
+ | 'upstream_error'
8008
+ /**
8009
+ * The call could not be judged, scrubbed or recorded, or (a `rest` tool) its request would have
8010
+ * left the API's pinned host (`status.errorClass` `rest_host_escape`): refused, fail closed (500).
8011
+ */
8012
+ | 'guard_error';
8013
+
8014
+ /** Who decided a call: an engine gate (`engine:mcp-*`), a steering rule, or the broker itself. */
8015
+ export type McpDecisionBy = string;
8016
+
8017
+ /** `POST /mcp/call`'s answer, on every status. Never carries a secret; `result` only on `ok`. */
8018
+ export interface McpCallResponse {
8019
+ outcome: McpCallOutcome;
8020
+ subject: string;
8021
+ /** The call record's span id (`GET /mcp/usage` and the run's Governance panel resolve it). */
8022
+ callId: string | null;
8023
+ /** The scrubbed `tools/call` result — only when `outcome` is `ok`. */
8024
+ result?: Record<string, unknown>;
8025
+ /** The rules or gates that decided (deny, ask, withheld). */
8026
+ ruleIds: string[];
8027
+ /** Why the result is not handed back; absent on `ok`. */
8028
+ reason?: string;
8029
+ /** What the worker can do instead. */
8030
+ remedy?: string;
8031
+ /** `pending_approval` only: the subject the operator is asked to approve. */
8032
+ pending_approval?: string;
8033
+ /** `denied` / `withheld`: the call was refused (the unit continues). */
8034
+ denied?: true;
8035
+ }
8036
+
8037
+ export type McpCallDecision = 'allow' | 'ask' | 'deny' | 'guard_error';
8038
+
8039
+ /**
8040
+ * One call record (`<state home>/mcp/calls.ndjson`), OTel span shaped. Arguments and results are
8041
+ * NEVER captured: only the subject, the decision, timings and the byte count.
8042
+ */
8043
+ export interface McpCallRecord {
8044
+ /** The run id, or `null` when the token resolved to no unit (a guard error before judging). */
8045
+ traceId: string | null;
8046
+ spanId: string;
8047
+ /** The unit, `<ord>:<attempt>`; `null` with `traceId`. */
8048
+ parentSpanId: string | null;
8049
+ name: 'mcp.tool.call';
8050
+ start: string;
8051
+ end: string;
8052
+ ms: number;
8053
+ status: { code: 'ok' | 'error'; errorClass: string | null };
8054
+ decision: { decision: McpCallDecision; by: McpDecisionBy; ruleIds: string[]; claimId: string | null };
8055
+ outcome: McpCallOutcome;
8056
+ attrs: {
8057
+ 'mcp.subject': string;
8058
+ 'mcp.class': McpToolClass | null;
8059
+ 'mcp.kind': McpUpstreamKind | null;
8060
+ 'wicked.seat': string | null;
8061
+ 'wicked.phase': string | null;
8062
+ 'wicked.carrier': 'shim';
8063
+ 'bytes.out': number;
8064
+ retries: number;
8065
+ };
8066
+ }
8067
+
8068
+ /** The `/ws` frame each call record rides (additive CoreEvent contract: skins that don't know it ignore it). */
8069
+ export interface McpCallCompletedFrame {
8070
+ type: 'mcpCallCompleted';
8071
+ record: McpCallRecord;
8072
+ }
8073
+
8074
+ // ── The unit's tool list (DES-MCP-TOOLS-001 §8, slice S4; api-types 0.65.0) ─────────────────────
8075
+
8076
+ /**
8077
+ * `POST /mcp/tools`: what the garden shim's `list` sends. The token travels in the body, never the
8078
+ * query string, so it stays out of request logs.
8079
+ */
8080
+ export interface McpToolsBody {
8081
+ token: string;
8082
+ }
8083
+
8084
+ /** One tool the token's unit may try, judged now with no arguments. A certain deny is left out. */
8085
+ export interface McpVisibleTool {
8086
+ /** `mcp:<server>/<tool>`. */
8087
+ subject: string;
8088
+ class: McpToolClass;
8089
+ /** `allow` = a call runs; `ask` = it waits for the operator's approval. */
8090
+ decision: 'allow' | 'ask';
8091
+ ruleIds: string[];
8092
+ /** Why a call would ask. */
8093
+ reason?: string;
8094
+ /** The tool's description, from the server's `tools/list`. */
8095
+ description: string | null;
8096
+ /** The tool's argument JSON schema, from `tools/list`. */
8097
+ inputSchema: Record<string, unknown> | null;
8098
+ }
8099
+
8100
+ /** `POST /mcp/tools`'s answer: the unit the token is bound to and its tools. */
8101
+ export interface McpToolsResponse {
8102
+ unit: { runId: string; ord: number; attempt: number; phase: string; seat: string };
8103
+ tools: McpVisibleTool[];
8104
+ }
8105
+
8106
+ // ── MCP policies: the preview matrix and approvals (DES-MCP-TOOLS-001 §4.5, §4.7, §8, slice S6; api-types 0.63.0) ──
8107
+
8108
+ /** A policy decision: `ask` = the call waits for the operator's approval (it does not run). */
8109
+ export type McpDecision = 'allow' | 'ask' | 'deny';
8110
+ /**
8111
+ * The phase role a matrix row stands for: `creator` = a creator phase with full write posture;
8112
+ * `evaluator` = an evaluator phase (read-only); `recon` = a neutral phase with a read-only posture.
8113
+ */
8114
+ export type McpPhaseRole = 'creator' | 'evaluator' | 'recon';
8115
+ /** The run's mode (studio's launch autonomy): Gate every step / Gate by risk / Auto. */
8116
+ export type McpRunMode = 'ask' | 'balanced' | 'autonomous';
8117
+
8118
+ /** One matrix cell: what a call from a unit of this shape would get now. Nothing is recorded. */
8119
+ export interface McpPolicyCell {
8120
+ role: McpPhaseRole;
8121
+ seat: string;
8122
+ mode: McpRunMode;
8123
+ phaseId?: string;
8124
+ decision: McpDecision;
8125
+ /** The class the engine judged (the tool's override, else its annotations; none = `write`). */
8126
+ class: McpToolClass;
8127
+ /** The rules that decided: `engine:*` gates first, then steering rule ids (each a Steering row). */
8128
+ ruleIds: string[];
8129
+ obligations: string[];
8130
+ reason: string | null;
8131
+ }
8132
+
8133
+ /** Whether a tool is approved in each ledger, and by which token (`server` = `mcp:<server>`). */
8134
+ export interface McpApprovalState {
8135
+ /** `MCP-FIRST-USE`: the first use of a server asks in every mode until approved. */
8136
+ firstUse: 'server' | 'tool' | null;
8137
+ /** `MCP-POSTURE-WRITE`: in balanced mode a write asks until approved. */
8138
+ write: 'server' | 'tool' | null;
8139
+ }
8140
+
8141
+ export interface McpPolicyPreviewTool {
8142
+ subject: string;
8143
+ server: string;
8144
+ tool: string;
8145
+ class: McpToolClass;
8146
+ status: McpToolStatus;
8147
+ /** The server and the tool are both enabled. */
8148
+ enabled: boolean;
8149
+ /** What the broker sends the engine: enabled and `registered`; otherwise D-5 denies it. */
8150
+ registered: boolean;
8151
+ approval: McpApprovalState;
8152
+ /** One per role × seat × mode, in `roles`, `seats`, `modes` order. */
8153
+ cells: McpPolicyCell[];
8154
+ }
8155
+
8156
+ /** `POST /mcp/policies/preview`. Every field narrows; none = every tool, role, seat and mode. */
8157
+ export interface McpPolicyPreviewBody {
8158
+ /** One tool: `mcp:<server>/<tool>`. */
8159
+ subject?: string;
8160
+ server?: string;
8161
+ phaseRole?: McpPhaseRole;
8162
+ seat?: string;
8163
+ mode?: McpRunMode;
8164
+ /** A workflow phase id a policy may name (`review`, `build`, ...). */
8165
+ phaseId?: string;
8166
+ }
8167
+
8168
+ /** `POST /mcp/policies/preview`'s 200, and `McpPreviewResponse.policies`. */
8169
+ export interface McpPolicyPreviewResponse {
8170
+ roles: McpPhaseRole[];
8171
+ seats: string[];
8172
+ modes: McpRunMode[];
8173
+ phaseId: string | null;
8174
+ /**
8175
+ * Server previews only (`[]` elsewhere): the approvals saving this preview withdraws, because a
8176
+ * saved tool's schema changed. The cells are judged under the CURRENT approvals, so a tool listed
8177
+ * here (or every tool, when `mcp:<server>` is listed) asks again after the save.
8178
+ */
8179
+ withdrawOnSave: string[];
8180
+ tools: McpPolicyPreviewTool[];
8181
+ }
8182
+
8183
+ /** An approved subject and the ledgers that hold it. */
8184
+ export interface McpApproval {
8185
+ subject: string;
8186
+ scope: 'server' | 'tool';
8187
+ firstUse: boolean;
8188
+ write: boolean;
8189
+ }
8190
+
8191
+ /** A registered, enabled tool that would ask in balanced mode, and why. */
8192
+ export interface McpPendingApproval {
8193
+ subject: string;
8194
+ server: string;
8195
+ tool: string;
8196
+ class: McpToolClass;
8197
+ needs: Array<'first-use' | 'write'>;
8198
+ }
8199
+
8200
+ export interface McpLedgerState {
8201
+ id: string;
8202
+ /** `false` = the mcp-defaults pack was not seeded; approvals are refused (503). */
8203
+ present: boolean;
8204
+ /** A retired first-use ledger still asks: the engine reads it either way. */
8205
+ retired: boolean;
8206
+ }
8207
+
8208
+ /** `GET /mcp/approvals`. */
8209
+ export interface McpApprovalsResponse {
8210
+ approved: McpApproval[];
8211
+ pending: McpPendingApproval[];
8212
+ ledgers: { firstUse: McpLedgerState; write: McpLedgerState };
8213
+ }
8214
+
8215
+ /**
8216
+ * `POST /mcp/approvals`, an audited edit of the ledger rules' `excludes`. `mcp:<server>` approves
8217
+ * the server's FIRST USE (`MCP-FIRST-USE`); its write tools still ask in balanced mode.
8218
+ * `mcp:<server>/<tool>` approves that tool's first use and its writes (`MCP-POSTURE-WRITE` too).
8219
+ * An approval never lifts an engine gate or an explicit deny rule.
8220
+ */
8221
+ export interface McpApprovalBody {
8222
+ subject: string;
8223
+ }
8224
+
8225
+ /** `POST /mcp/approvals` and `DELETE /mcp/approvals/:subject`: the ledger rules the edit changed. */
8226
+ export interface McpApprovalResponse {
8227
+ subject: string;
8228
+ approved: boolean;
8229
+ rulesChanged: string[];
8230
+ }
8231
+
8232
+ // ── 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) ──
8233
+
8234
+ /** Every call record in a window, split by the broker's decision. */
8235
+ export interface McpDecisionCounts {
8236
+ allow: number;
8237
+ ask: number;
8238
+ deny: number;
8239
+ guard_error: number;
8240
+ }
8241
+
8242
+ /**
8243
+ * The numbers a set of call records folds to. `ran` counts the calls the broker let through
8244
+ * (`decision: allow`); the error rate and the latency percentiles are over those calls only, since
8245
+ * a denied or asked call never reached an upstream. Percentiles are nearest-rank: the value at
8246
+ * position `ceil(q * n)` of the sorted durations. `null` = no call ran.
8247
+ */
8248
+ export interface McpUsageStats {
8249
+ calls: number;
8250
+ decisions: McpDecisionCounts;
8251
+ ran: number;
8252
+ /** Ran calls whose status is `error` (an upstream failure or a tool error, `isError`). */
8253
+ errors: number;
8254
+ errorRate: number | null;
8255
+ p50Ms: number | null;
8256
+ p95Ms: number | null;
8257
+ p99Ms: number | null;
8258
+ }
8259
+
8260
+ /** One tool's row of the per-tool table. */
8261
+ export interface McpUsageTool extends McpUsageStats {
8262
+ subject: string;
8263
+ server: string;
8264
+ tool: string;
8265
+ /** The class the engine judged on the newest record; `null` = never judged (a guard error). */
8266
+ class: McpToolClass | null;
8267
+ /** The seats that called it, sorted. */
8268
+ seats: string[];
8269
+ lastCall: string;
8270
+ }
8271
+
8272
+ /** One server's totals: its row's "last used" and "calls over the window". */
8273
+ export interface McpUsageServer {
8274
+ server: string;
8275
+ calls: number;
8276
+ decisions: McpDecisionCounts;
8277
+ lastCall: string;
8278
+ }
8279
+
8280
+ /** The drill-down: one tool × seat × run. `runId: null` = calls the broker refused before judging. */
8281
+ export interface McpUsageRun {
8282
+ subject: string;
8283
+ seat: string | null;
8284
+ runId: string | null;
8285
+ calls: number;
8286
+ decisions: McpDecisionCounts;
8287
+ /** Ran calls whose status is `error`, as in {@link McpUsageStats}. */
8288
+ errors: number;
8289
+ lastCall: string;
8290
+ }
8291
+
8292
+ /**
8293
+ * A tool chain: `from` then `to`, two consecutive calls of one unit attempt (by start time), with
8294
+ * different subjects. `count` = how often; `runs` = in how many runs.
8295
+ */
8296
+ export interface McpUsageChain {
8297
+ from: string;
8298
+ to: string;
8299
+ count: number;
8300
+ runs: number;
8301
+ }
8302
+
8303
+ /** One UTC day the window touches (its first day is partial), days with no calls included. */
8304
+ export interface McpUsageDay {
8305
+ day: string;
8306
+ calls: number;
8307
+ decisions: McpDecisionCounts;
8308
+ }
8309
+
8310
+ /**
8311
+ * `GET /mcp/usage?days&subject&seat&decision`. `days` is 1-30 (default 7); the window is the last
8312
+ * `days` × 24 h. Every filter narrows every section, except `chains`: a chain is folded from the
8313
+ * unit's calls under the seat and decision filters, then kept when either end is `subject`.
8314
+ * Records older than 30 days are folded into `calls-daily.ndjson` and are not in any window.
8315
+ */
8316
+ export interface McpUsageResponse {
8317
+ days: number;
8318
+ since: string;
8319
+ until: string;
8320
+ filters: { subject: string | null; seat: string | null; decision: McpCallDecision | null };
8321
+ totals: McpUsageStats;
8322
+ /** Most calls first, then by subject. */
8323
+ tools: McpUsageTool[];
8324
+ /** Most calls first, then by name. */
8325
+ servers: McpUsageServer[];
8326
+ /** Newest first, at most 200. */
8327
+ runs: McpUsageRun[];
8328
+ /** Most frequent first, at most 20. */
8329
+ chains: McpUsageChain[];
8330
+ /** Oldest first. */
8331
+ daily: McpUsageDay[];
8332
+ /** The seats seen in the window before any filter (the seat filter's options), sorted. */
8333
+ seats: string[];
8334
+ /** Lines of `calls.ndjson` that did not parse as a call record. */
8335
+ skipped: number;
8336
+ }