@sema-agent/sdk 15.3.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml CHANGED
@@ -677,7 +677,8 @@ paths:
677
677
  (non-owner → 404, no existence leak; missing principal → 401). All responses are synchronous ACKs; actual
678
678
  termination is async (same-replica immediate; cross-replica ≈30s via the heartbeat tick). A cancelled run
679
679
  settles to `status` failed + `errorCode` "cancelled" (NO new status — distinguish via errorCode; the UI
680
- shows "cancelled", not an error). A SUSPENDED (or `needs_review`) run IS cancellable here — the server
680
+ shows "cancelled", not an error). From server >= 7.112.0 the cancelled terminal also carries
681
+ `abortCause: "cancel_verb"` (see `TaskResult.abortCause`; `errorCode` "cancelled" is unchanged). A SUSPENDED (or `needs_review`) run IS cancellable here — the server
681
682
  settles its pending checkpoint (CAS `expire`, the reaper's "≈ deny" terminal — deliberately not a
682
683
  resume, since cancel means the run dies) and THEN terminalizes the row, releasing the session claim;
683
684
  the ack is 202. (Corrected 2026-07-31: this paragraph used to say "409 — cancel it by DENYING its
@@ -1707,7 +1708,7 @@ paths:
1707
1708
  '404':
1708
1709
  description: >
1709
1710
  Non-owner / unknown session (no existence oracle) — or, with `?message=`, an out-of-range index, whose
1710
- body additionally carries `total`.
1711
+ body additionally carries `total`. server >= 7.113.0 (S-989; wire VALUE change, no new key): the unknown arm AND the non-owner arm answer `errorCode: "not_found.session"` + `error: "session not found"`, byte-identical (status and body) — one door, one spelling, no existence oracle. server <= 7.112.0 answered the non-owner arm in the RUN spelling (`not_found.run` + the long run-not-found sentence) while the unknown arm already said `not_found.session` — two codes and two sentences on one endpoint WAS the oracle. The SDK lands both arms on `NotFoundError`: branch on the class, never on the code to tell "no such session" from "not yours" (that distinction is exactly what the server refuses to give); the run face `/v1/runs/{taskId}` is unchanged (`not_found.run`).
1711
1712
  content:
1712
1713
  application/json:
1713
1714
  schema:
@@ -1843,7 +1844,12 @@ paths:
1843
1844
  leafId: { type: ["string", "null"], description: "null = session exists but has no entries yet." }
1844
1845
  '304': { description: 'Leaf unchanged since the presented ETag (no body).' }
1845
1846
  '401': { $ref: '#/components/responses/Unauthorized' }
1846
- '404': { $ref: '#/components/responses/NotFound' }
1847
+ '404':
1848
+ description: >
1849
+ Unknown session or another principal's session. server >= 7.113.0 (S-989; wire VALUE change, no new key): the unknown arm AND the non-owner arm answer `errorCode: "not_found.session"` + `error: "session not found"`, byte-identical (status and body) — one door, one spelling, no existence oracle. server <= 7.112.0 answered the non-owner arm in the RUN spelling (`not_found.run` + the long run-not-found sentence) while the unknown arm already said `not_found.session` — two codes and two sentences on one endpoint WAS the oracle. The SDK lands both arms on `NotFoundError`: branch on the class, never on the code to tell "no such session" from "not yours" (that distinction is exactly what the server refuses to give); the run face `/v1/runs/{taskId}` is unchanged (`not_found.run`).
1850
+ content:
1851
+ application/json:
1852
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1847
1853
  '501': { $ref: '#/components/responses/NotImplemented' }
1848
1854
 
1849
1855
  /v1/sessions/{sessionId}/events:
@@ -1879,7 +1885,12 @@ paths:
1879
1885
  text/event-stream:
1880
1886
  schema: { type: string }
1881
1887
  '401': { $ref: '#/components/responses/Unauthorized' }
1882
- '404': { $ref: '#/components/responses/NotFound' }
1888
+ '404':
1889
+ description: >
1890
+ Unknown session or another principal's session (answered BEFORE the SSE `200` headers go out). server >= 7.113.0 (S-989; wire VALUE change, no new key): the unknown arm AND the non-owner arm answer `errorCode: "not_found.session"` + `error: "session not found"`, byte-identical (status and body) — one door, one spelling, no existence oracle. server <= 7.112.0 answered the non-owner arm in the RUN spelling (`not_found.run` + the long run-not-found sentence) while the unknown arm already said `not_found.session` — two codes and two sentences on one endpoint WAS the oracle. The SDK lands both arms on `NotFoundError`: branch on the class, never on the code to tell "no such session" from "not yours" (that distinction is exactly what the server refuses to give); the run face `/v1/runs/{taskId}` is unchanged (`not_found.run`).
1891
+ content:
1892
+ application/json:
1893
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1883
1894
  '500':
1884
1895
  description: >-
1885
1896
  (declared 2026-07-31) errorCode "internal.error" — `routes/sessions.ts`'s catch around the owner
@@ -2056,7 +2067,17 @@ paths:
2056
2067
  '400': { $ref: '#/components/responses/BadRequest' }
2057
2068
  '401': { $ref: '#/components/responses/Unauthorized' }
2058
2069
  '404': { description: 'No parked checkpoint for this session (nothing to wake), or session not visible to the caller.' }
2059
- '409': { description: 'Pending gate (`wake.gate_pending` — decide it through its own entry) or resume context missing.' }
2070
+ '409':
2071
+ description: >
2072
+ Pending gate (`wake.gate_pending` — decide it through its own entry) or resume context missing; also the resume-leg refusals core
2073
+ raises before the claim (`steering.blocked_by_hook` when the wake carried a `message` and the deployment's `userPromptSubmit`
2074
+ hook crashed / timed out / refused — server >= 7.113.0 adds `retriable: true` there; `checkpoint.config_refused`). Value change on
2075
+ the run row the wake minted for the refused attempt (server >= 7.113.0): it now ends `failed` with a structured terminal carrying
2076
+ `terminal.code: "steering.blocked_by_hook"` and holds no session lock (before 7.113.0 it ended with a code-less terminal and
2077
+ `result: null` — a zero-execution refusal recorded as an ordinary failure); the card itself stays pending.
2078
+ content:
2079
+ application/json:
2080
+ schema: { $ref: '#/components/schemas/ResumeLegConflictError' }
2060
2081
  '422': { description: '`steering.invalid_content` — the message failed the steering content gate.' }
2061
2082
  '429': { $ref: '#/components/responses/RateLimited' }
2062
2083
  '501': { $ref: '#/components/responses/NotImplemented' }
@@ -3306,7 +3327,7 @@ paths:
3306
3327
  parked row carries no root-session anchor (refused before the claim, nothing consumed).
3307
3328
  content:
3308
3329
  application/json:
3309
- schema: { $ref: '#/components/schemas/ErrorResponse' }
3330
+ schema: { $ref: '#/components/schemas/ResumeLegConflictError' }
3310
3331
  # 🔴 REMOVED 2026-07-31 — the 410 that never was. `grep -rn "\b410\b" src/` across the whole server
3311
3332
  # is ZERO hits: `approval_stale` ships as a **409** carrying `errorCode: "approval_stale"` (the CAS
3312
3333
  # 409 stays bare → ConflictError, so the two 409s never collide). The `terminal` enum values
@@ -6613,12 +6634,18 @@ components:
6613
6634
 
6614
6635
  RunStatus:
6615
6636
  type: string
6616
- enum: [running, completed, failed, suspended, needs_review]
6637
+ enum: [running, completed, failed, suspended, needs_review, blocked]
6617
6638
  description: >
6618
6639
  `suspended` = HITL (an F4 policy gate or AskUserQuestion is waiting on a human) — NOT an error.
6619
6640
  `needs_review` (design/80 D-0) = durable TERMINAL of a dry-run/shadow review (POST-prediction),
6620
6641
  distinct from the mid-run `suspended` pause. A user-cancelled run is `failed` with `errorCode`
6621
6642
  "cancelled" (cancel is LIVE; `cancelling` is a cancel-ack field, NOT a member of this enum).
6643
+ `blocked` = durable TERMINAL: the run ended without doing the work (`result.terminal.kind: "blocked"` — the agent's
6644
+ `ReportBlocked`, or, server >= 7.113.0, a deployment `userPromptSubmit` hook keeping the prompt from being submitted, see
6645
+ `TerminalBlockedDetail`); a governance verdict, not an error. The server's run-status table has it (`blocked`, an
6646
+ unsuccessful terminal) and the SDK type has listed it since 2.1.4 (whose own note records the server passing core's status words
6647
+ through verbatim from 3.x); this enum was the one place that did not (declared in sdk 16.0.0 when the real-projector pins
6648
+ validated a blocked run row against it).
6622
6649
 
6623
6650
  TaskStats:
6624
6651
  type: object
@@ -7356,6 +7383,15 @@ components:
7356
7383
  description: >
7357
7384
  Turn-boundary reminder attachments (design/133 + G1). Each flag opts INTO an injection; omitted = off.
7358
7385
  `changedFiles` may also be an object to cap the listing.
7386
+ 🔴 BREAKING (server >= 7.113.0 / core 7.39.0 #1254; K-44): `planModeReminder` was retired and is no longer declared
7387
+ here. A submit body that carries it — any value, `false` included (the server's gate tests `!== undefined`, not
7388
+ truthiness) — is refused 400 `request.field_invalid` with a sentence naming the retirement; before 7.113.0 it was
7389
+ accepted and the engine refused later, at prepare. The engine now states plan posture itself whenever the
7390
+ permission mode is "plan" (first request, periodic reminders, exit frame): remove the key and send
7391
+ `permissionMode: "plan"`. A stored body that still carries the key resumes with the key dropped (resume replays
7392
+ are not refused). THIS schema stays open (`additionalProperties: true`), so a validator built from it will not
7393
+ reject the key — the refusal is the server's, not this schema's. A server <= 7.112.x still accepts the key; this
7394
+ package no longer declares it (sdk 16.0.0, major).
7359
7395
  additionalProperties: true
7360
7396
  properties:
7361
7397
  todoReminder: { type: boolean }
@@ -7364,7 +7400,6 @@ components:
7364
7400
  oneOf:
7365
7401
  - { type: boolean }
7366
7402
  - { type: object, properties: { maxFiles: { type: integer } } }
7367
- planModeReminder: { type: boolean }
7368
7403
  budgetUsd: { type: boolean }
7369
7404
  backgroundTasks: { type: boolean }
7370
7405
  toolsDelta: { type: boolean }
@@ -7503,11 +7538,42 @@ components:
7503
7538
  TerminalCause_blocked:
7504
7539
  type: object
7505
7540
  additionalProperties: false
7506
- description: 'The agent could not finish and said why (a governance verdict is NOT a failure).'
7541
+ description: >
7542
+ The run ended without doing the work (a governance verdict is NOT a failure). Two sources, told apart by `detail`: with NO
7543
+ `detail` — the agent's own words from its `ReportBlocked` call; with `detail.kind: "prompt_blocked_by_hook"` (server >= 7.113.0 /
7544
+ core #1277; K-43-class closed-object gap) — the engine's sentence about the deployment's `userPromptSubmit` hook, framed as a
7545
+ reminder for the model-facing lane. Terminal: the run is over and nobody is being waited on (contrast `paused`).
7546
+ Same shape on all four faces (the synchronous 200 body, the stream's `done.result`, `GET /v1/runs/{taskId}` -> `result`, the
7547
+ ledger `done` row).
7507
7548
  required: [kind, reason]
7508
7549
  properties:
7509
7550
  kind: { const: blocked }
7510
- reason: { type: string, description: 'Why the agent could not finish.' }
7551
+ reason: { type: string, description: 'Why the run ended without doing the work (the agent''s words, or the engine''s framed hook sentence — branch on `detail?.kind`, never on this prose).' }
7552
+ detail:
7553
+ allOf: [{ $ref: '#/components/schemas/TerminalBlockedDetail' }]
7554
+ description: >
7555
+ server >= 7.113.0 (core #1277) — WHO stopped the run, for the one shape a consumer offers its own way out of: the prompt was
7556
+ NOT submitted (the objective never became a message and the loop never started), so changing it and sending again is
7557
+ meaningful. PRESENT only on that terminal; ABSENT means the agent reported `ReportBlocked` ("I cannot go on"), or a server
7558
+ before 7.113.0.
7559
+
7560
+ TerminalBlockedDetail:
7561
+ type: object
7562
+ additionalProperties: false
7563
+ required: [kind, cause]
7564
+ description: >
7565
+ server >= 7.113.0 (core #1277; K-44) — the machine-readable account of a `blocked` terminal caused by the deployment's
7566
+ `userPromptSubmit` hook. `kind` is closed (`prompt_blocked_by_hook`, one member today). `cause` is the closed four-word set of the hook seat's endings that keep a
7567
+ prompt from being submitted (core `PROMPT_BLOCK_CAUSES`): `block` — the hook answered and refused (its words ride `hookReason`);
7568
+ `crashed` — the hook threw or rejected (the seat is fail-closed: a screen that failed has not cleared the prompt); `timeout` — no
7569
+ answer within the hook's `timeoutMs` bound (fail-closed the same way); `cancelled` — the task was cancelled while the hook was
7570
+ still screening. `hookReason` is the hook's OWN words (present only on cause `block`, and only when the hook gave some): folded to
7571
+ one line, bounded, with the engine's envelope tags defused and passed through the same secret redaction as `reason` — still the
7572
+ hook's text, so render it as untrusted. A word outside the four is read as "the hook seat stopped the prompt, cause not recognised".
7573
+ properties:
7574
+ kind: { const: prompt_blocked_by_hook }
7575
+ cause: { type: string, enum: [block, crashed, timeout, cancelled] }
7576
+ hookReason: { type: string, description: 'The hook''s own refusal words (untrusted free text; present only on `block` and only when stated).' }
7511
7577
 
7512
7578
  PausedCause:
7513
7579
  type: object
@@ -7655,6 +7721,117 @@ components:
7655
7721
  verdict: { type: string, enum: [PASS, FAIL, PARTIAL, unverified] }
7656
7722
  rounds: { type: integer }
7657
7723
  findings: { type: array, items: { type: string } }
7724
+ cancelContext:
7725
+ allOf: [{ $ref: '#/components/schemas/CancelContext' }]
7726
+ description: >
7727
+ server >= 7.57.0 (S-94; contract §5.3; K-43 — named here in sdk 16.0.0, it was only readable through this schema's open
7728
+ index before) — the SCENE SNAPSHOT of a USER CANCEL, a side key beside `abortCause` (both are minted by the same mint
7729
+ point, `mintCancelledResult`, under the same "fill the empty seat only" gate; neither lives inside `terminal`).
7730
+ PRESENT only on a cancelled terminal the server minted; ABSENT on a model's own failure / a normal completion, and
7731
+ ABSENT as a whole when not one of its keys can be proven (the server never mints an empty object — absence reads as "no
7732
+ cancel scene to look at", NOT "nothing was wrong at cancel time"). Landings: `GET /v1/runs/{taskId}` -> `result.cancelContext`
7733
+ (and the same object rides the other cancelled-terminal landings listed on `abortCause`). Shape and per-key reading
7734
+ rules: `CancelContext`.
7735
+
7736
+ abortCause:
7737
+ allOf: [{ $ref: '#/components/schemas/AbortCause' }]
7738
+ description: >
7739
+ server >= 7.112.0 (S-947; cli L-212; contract §5.0d; additive) — the machine-readable CAUSE of a SERVER-INITIATED
7740
+ abort, a side key beside `cancelContext` (it is NOT inside `terminal`: `terminal` is the engine's cause, and one
7741
+ object must not have two minters). PRESENT-IFF this result IS the cancelled terminal the server minted for that
7742
+ abort (`terminal.kind: "failed"` + `terminal.code: "cancelled"`); a server >= 7.112.0 always stamps it on the
7743
+ cancelled terminal it mints (the mint's cause parameter is mandatory; the engine never mints `cancelled` itself).
7744
+ 🔴 It sits BESIDE the cancel code and never replaces it: the row's `errorCode` column / `terminal.code`
7745
+ (`"cancelled"`) stays the "this is a cancel" discriminant, unchanged — this key only answers "who made it stop".
7746
+ ABSENT (not a cancelled terminal the server minted for its own abort): a detach disconnect
7747
+ (`x-detach-on-disconnect: true`, the server does not abort); the engine's own failure; a normal completion;
7748
+ `POST /v1/runs/{taskId}/interrupt` (terminal `completed` + `haltedByUser`); a park (including a #241 grace park);
7749
+ a terminal that RACED the abort (the engine finished first / failed with its own code — "fill the empty seat
7750
+ only": code, snapshot and cause are all left off together); a resume leg cancelled BEFORE its approved action
7751
+ started (the engine rules `resume.reopened_unstarted` and the park reopens — the run was not ended); a
7752
+ preempt (`paused`); an orderly shutdown or a dead-holder reap.
7753
+ 🔴 A cancelled row persisted by a server before 7.112.0 has NO `abortCause` and is never back-filled: a row with
7754
+ `errorCode: "cancelled"` and the key absent = cause NOT RECORDED — do not fold it into any word.
7755
+ Four landings, one value: `GET /v1/runs/{taskId}` -> `result.abortCause` (beside `result.cancelContext`) ·
7756
+ `POST /v1/tasks/stream` -> `done.result.abortCause` · the `POST /v1/tasks` 200 body, at its TOP LEVEL (the body IS
7757
+ the result, contract §5.0c) · `GET /v1/runs/{taskId}/events` -> the `done` row's `result.abortCause`; a cancelled
7758
+ shape with NO `done` row to land on (a disconnect after which no done was read / the leg aborted before its
7759
+ terminal threw) lands on the `failed` row at its TOP LEVEL instead (`Event_failed.abortCause`, beside `errorCode`).
7760
+ Two wire VALUE changes ride the same release (not new keys): a resume leg cancelled AFTER the approved action
7761
+ started now ends `failed` + `errorCode: "cancelled"` + a `cancelContext` snapshot + `abortCause: "cancel_verb"`
7762
+ (before 7.112.0 it ended `failed` with NO code); an interactive `POST /v1/tasks/stream` that read no `done` and
7763
+ was NOT aborted by the server now ends with the honest failure sentence `stream ended before a terminal event`
7764
+ (no code, no `abortCause`; before 7.112.0 it was always minted as a "client disconnected" cancelled terminal).
7765
+
7766
+ AbortCause:
7767
+ type: string
7768
+ enum: [client_disconnected, ask_park_grace_exhausted, cancel_verb]
7769
+ description: >
7770
+ The closed three-word vocabulary of `abortCause` (server >= 7.112.0 / S-947; contract §5.0d) — who initiated a
7771
+ server-side abort. Each word has one real site: `client_disconnected` — a non-detach `POST /v1/tasks/stream` client
7772
+ disconnect while no in-stream ask was pending, aborted at once ("the connection dropped, the run was aborted" — not an
7773
+ engine error); `ask_park_grace_exhausted` — the same disconnect arm but an in-stream ask WAS pending: the server first
7774
+ turns the ask into a park through the window-expiry path and gives the engine a bounded grace (10 s) to finish
7775
+ parking, and the park still had not landed when the grace ran out, so the run was aborted; `cancel_verb` —
7776
+ `POST /v1/runs/{taskId}/cancel` (the same-replica fast path, a cross-replica durable cancel flag polled by the holding
7777
+ replica on any of the four legs — interactive stream / non-streaming / background / resume —, and a parked run the
7778
+ verb finalizes directly; the cause is the same word). First come, first served: a leg is aborted once, and the first
7779
+ landing names the word — a cancel verb arriving after a disconnect, or a disconnect after a cancel, never rewrites it.
7780
+ CLOSED set, additive-only by the server's rule (a new word is a new server version in `wire-changes` AND a new SDK
7781
+ minor, in this enum in the same batch): read an unknown word as "the server aborted this run, cause not recognised".
7782
+
7783
+ CancelContext:
7784
+ type: object
7785
+ # 封闭(K-43,sdk 16.0.0):铸点 `run-cancel-context.js` `buildCancelContext` 是逐键条件 spread 的字面量(lastEventKind / lastEventAgeMs / lastBrainStatus / elapsedMs),
7786
+ # `lastBrainStatus` 经 `turn-activity.js` `pickRunBrainStatus` 投成五键(刻意不含 provider 自由文本 `detail` 与相对量 `retryInMs`)—— 与本 schema 一一对上;
7787
+ # `test/server-7113-wire.test.ts` K3 用真 `recordTurnActivity` + 真 `mintCancelledResult` 的字节逐键核。
7788
+ additionalProperties: false
7789
+ description: >
7790
+ The scene snapshot of a user cancel (server >= 7.57.0 / S-94; contract §5.3; K-43): "how long since this server last heard
7791
+ from this run" — triage material, never a state-machine input. 🔴 It is NOT the model-call phase: the server structurally
7792
+ cannot see first-byte waits or retries (that half lives in the engine's stream layer); `lastEventAgeMs` is NOT "how long the
7793
+ model has been hung" (a long tool call produces no ledger event either). Every key is optional and absence is honest:
7794
+ `lastEventKind` / `lastEventAgeMs` come from a SAME-REPLICA in-process activity registry (a cross-replica cancel or a
7795
+ replica restart cannot read it — absent means "this replica cannot prove it", never fold it to 0: that would render a
7796
+ 8-minute hang as "just alive"); `lastEventKind` is MORE likely to be absent than `lastEventAgeMs` (some activity seats advance
7797
+ the clock without a word), so "age without kind" is a legal reading; `lastBrainStatus` is absent when the run never
7798
+ emitted a `status` frame or the replica cannot read it; `elapsedMs` (run start to cancel, wall clock) is normally present
7799
+ and absent only when the row's `createdAt` cannot be parsed (again never folded to 0). The side key `abortCause` says WHO
7800
+ stopped the run; this object says what the server saw at that moment.
7801
+ properties:
7802
+ lastEventKind:
7803
+ type: string
7804
+ description: 'The type of the last event that entered this run''s durable ledger (`text` / `tool_start` / `status` / ...). Open string.'
7805
+ lastEventAgeMs:
7806
+ type: number
7807
+ minimum: 0
7808
+ description: 'Milliseconds since that last durable event (>= 0).'
7809
+ lastBrainStatus:
7810
+ allOf: [{ $ref: '#/components/schemas/CancelContextBrainStatus' }]
7811
+ description: >
7812
+ The last `status` (BrainStatus) frame at the tail of the run's ledger (an S-94 extension), as a five-key subset — "which
7813
+ retry, waiting for what, which error class". STICKY: later text / tool events do not clear it. Absent when the run never
7814
+ emitted a `status` frame or this replica cannot read it (never `null`).
7815
+ elapsedMs:
7816
+ type: number
7817
+ minimum: 0
7818
+ description: 'Wall-clock milliseconds from the run''s start to this cancel (>= 0).'
7819
+
7820
+ CancelContextBrainStatus:
7821
+ type: object
7822
+ additionalProperties: false
7823
+ required: [phase]
7824
+ description: >
7825
+ The five-key subset of the last BrainStatus frame (`CancelContext.lastBrainStatus`; K-43). `phase` is the snapshot's reason to
7826
+ exist (no phase, no snapshot — never half of one). Deliberately NOT carried: `detail` (provider free text) and `retryInMs` /
7827
+ `retryInSec` (relative values go stale; `retryAtMs` is the cross-process one — a wall-clock instant on the SENDING process's
7828
+ clock, an approximation, never compare it with a local monotonic timer).
7829
+ properties:
7830
+ phase: { type: string }
7831
+ attempt: { type: number }
7832
+ maxRetries: { type: number }
7833
+ retryAtMs: { type: number }
7834
+ errClass: { type: string }
7658
7835
 
7659
7836
  LegacyPlaneTaskResult:
7660
7837
  type: object
@@ -7865,10 +8042,14 @@ components:
7865
8042
  changed shape with it (`result.terminal`, no plane keys): the row's `errorCode` column is now projected
7866
8043
  from `result.terminal` (`src/http/routes/runs.ts` -> `persistedPlaneOf`, which also reads the flat bytes
7867
8044
  7.63.0 and earlier wrote to disk), NOT copied from a `result.errorCode` that no longer exists.
7868
- # 封闭(census 轴一,2026-07-30;A-075.21 回填后 12 键):铸造点 routes/runs.ts 是一个逐键写死的
8045
+ # 封闭(census 轴一,2026-07-30;A-075.21 回填后 12 键;**K-43 / sdk 16.0.0 补 `heldBy` / `cancelRequested` 后 14 键**):铸造点 routes/runs.ts 是一个逐键写死的
7869
8046
  # 字面量(taskId/sessionId/status/msSinceLastActivity/crossSliceUsage/result/supervisorCost/
7870
- # suggestions/errorCode/error/jobId/source),与本 properties 一一对上;只有 taskId/sessionId/status
7871
- # 无条件在场(其余走 `?? undefined` / 条件 spread ⇒ JSON 丢键)⇒ required 维持 3 键。
8047
+ # suggestions/errorCode/error/jobId/source/heldBy/cancelRequested),与本 properties 一一对上;只有 taskId/sessionId/status
8048
+ # 无条件在场(其余走 `?? undefined` / 条件 spread ⇒ JSON 丢键)⇒ required 维持 3 键(`heldBy` / `cancelRequested` 在 server >= 7.73.0 上恒在场,
8049
+ # 但 required 不抬:本包支持底线 3.0.0,老 server 没有这两键)。
8050
+ # 🔴 K-43(既存缺口,与 abortCause 无关,K-41 同病):`heldBy` / `cancelRequested` 自 server 7.73.0(S-230 P-45)就在真 `GET /v1/runs/:id` 上,
8051
+ # 而本封闭对象一直没声明它们 ⇒ 按本 schema 严格校验响应的消费方会得「$ must NOT have additional properties」;门 6 契约套抓不到,
8052
+ # 由 `test/server-7113-wire.test.ts` K3(真 `handleRuns` 字节 × spec)抓。
7872
8053
  # (A-075.21:此前的「恰好 10 键」对 msSinceLastActivity(#245 S1)/crossSliceUsage([3833] S-4)
7873
8054
  # 两条铸键路径结构性失明 —— server test/producer-contract.test.ts 的 S-4 登记钉逐字记着这笔账。)
7874
8055
  additionalProperties: false
@@ -7936,6 +8117,29 @@ components:
7936
8117
  items: { type: string }
7937
8118
  description: Follow-up prompt suggestions produced for this run (absent when none were generated).
7938
8119
  supervisorCost: { $ref: '#/components/schemas/SupervisorCostBreakdown' }
8120
+ heldBy:
8121
+ type: [string, "null"]
8122
+ description: >
8123
+ server >= 7.73.0 (S-230 P-45; contract §5.5 (c); K-43 — the mint has carried it since 7.73.0, this closed schema
8124
+ did not declare it) — the replica HOLDING this run NOW (the row's `instance_id`: written at create, TAKEN OVER by a
8125
+ cross-replica resume claim), so it answers "where is the live leg", not "who created the row". Always present on a
8126
+ server that has it, and an explicit `null` (never a missing key) means no replica holds it. 🔴 `heldBy` becoming
8127
+ `null` OR the run reaching a terminal status = the claim is released (either one); on a terminal row `heldBy` reads
8128
+ as a HISTORICAL fact ("which replica finished it"), not "still occupied". ⚠️ The one exception is the stale fold: a
8129
+ `running` row older than the stale window is shown with `status: "failed"` + `error: "run stalled (instance lost?)"`
8130
+ — that is the READ's judgement; the ledger row is still `running` and its claim is held until the reaper's next beat,
8131
+ and `heldBy` still names the LOST replica (the one useful coordinate when debugging). A deployment-internal replica
8132
+ identity, not a user-readable location. Absent on a server < 7.73.0.
8133
+ cancelRequested:
8134
+ type: boolean
8135
+ description: >
8136
+ server >= 7.73.0 (S-230 P-45; contract §5.5 (c); K-43) — the durable cancel flag (cross-replica; the holding replica
8137
+ polls it on its heartbeat beat). ⚠️ It is a pending cancel: a run that has already been cancelled is spoken by `status` +
8138
+ `errorCode: "cancelled"` (a park-arm cancel never goes through this flag — it finalizes the row directly), and a resume
8139
+ claim CLEARS the flag (a cancel that landed on a suspend must not kill the run again after it resumes). So never read
8140
+ `cancelRequested` flipping back to `false` as "the cancel completed" — wait for a terminal `status` + `errorCode:
8141
+ "cancelled"`. The `cancelling` 202 ack of `POST /v1/runs/{taskId}/cancel` carries the same key
8142
+ (`CancelAck.cancelRequested`). Absent on a server < 7.73.0.
7939
8143
 
7940
8144
  SupervisorCostBreakdown:
7941
8145
  type: object
@@ -7973,14 +8177,23 @@ components:
7973
8177
  description: >
7974
8178
  Synchronous ack for `POST /v1/runs/:id/cancel`. `status` is "cancelling" (accepted) or a terminal status
7975
8179
  (idempotent no-op). NOTE: "cancelling" is NOT a RunStatus enum member — it only appears here.
7976
- # 封闭(census 轴一,2026-07-30):cancel 面的 7 个 sendJson 铸造点(routes/runs.ts
7977
- # 495/505/512)键集的并集恰好 = {taskId, status, note?, errorCode?};taskId/status 无条件在场。
8180
+ # 封闭(census 轴一,2026-07-30):cancel 面的 sendJson 铸造点键集的并集 = {taskId, status, note?, errorCode?, cancelRequested};
8181
+ # taskId/status 无条件在场。🔴 K-43 / sdk 16.0.0 补 `cancelRequested`:自 server 7.73.0(S-230 P-45)每一个 2xx 受理体都带它,
8182
+ # 而本封闭对象一直没声明 ⇒ 严格校验回执的消费方会得「$ must NOT have additional properties」;`test/server-7113-wire.test.ts` K3 抓(真 `handleRuns` 三臂字节 × spec)。
7978
8183
  additionalProperties: false
7979
8184
  required: [taskId, status]
7980
8185
  properties:
7981
8186
  taskId: { type: string }
7982
8187
  status: { type: string }
7983
8188
  note: { type: string }
8189
+ cancelRequested:
8190
+ type: boolean
8191
+ description: >
8192
+ server >= 7.73.0 (S-230 P-45; contract §5.5 (c); K-43) — the same predicate as `RunRecord.cancelRequested` (the durable
8193
+ cancel flag), present on every 2xx ack the cancel verb mints: `true` on the `status: "cancelling"` branch (the CAS just
8194
+ won — the flag is on the row and the holding replica's next beat reads it); `false` on the park-arm terminal branch (a
8195
+ parked run is finalized directly, no flag was needed); on the idempotent no-op branches and the concurrent-terminalized
8196
+ branch it reports the row's own flag value (`false` in the contract's account, §5.5 (c)). Absent on a server < 7.73.0.
7984
8197
  errorCode:
7985
8198
  type: [string, "null"]
7986
8199
  description: >
@@ -12235,7 +12448,9 @@ components:
12235
12448
  description: >-
12236
12449
  server >=7.110.0 (S-928 user-input acceptance ledger): user inputs this session LOST to a crash —
12237
12450
  the server had answered 200 `applied` (interrupt{text}: 202) but the process carrying that run died
12238
- before the run settled; whether the run consumed them is NOT known (see `engine_notice`
12451
+ before the engine confirmed consuming them (server >= 7.113.0 / core 7.39.0 #1273: an acceptance the engine confirmed
12452
+ consuming is settled `consumed` and is no longer listed here — the <= 7.112.x reading "whether the run consumed
12453
+ them is not known" narrowed to this; it still does NOT say "never consumed"; see `engine_notice`
12239
12454
  `task.user_steer_undrained{cause:"crashed"}`). Converged rows only, one row per (taskId, messageId),
12240
12455
  newest 500. From server >=7.111.0 (S-966) the rows include SUBAGENT steers (recorded under the run on the
12241
12456
  receipt; same row shape, `kind` is still only `steer`; `messageId` = the subagent steer receipt's).
@@ -13020,7 +13235,9 @@ components:
13020
13235
  On 400 `request.field_invalid` for a `settings.permissions` bucket (server >= 7.106.0 / S-904): which bucket —
13021
13236
  `settings.permissions.allow` | `settings.permissions.deny` | `settings.permissions.ask`. (`rules.*` content refusals
13022
13237
  carry their own `field`.) Resume legs carry the same group on 409 `resume_blocked_by_policy`
13023
- (`blockedBy: "request.field_invalid"`). SDK: `err.extra.field`.
13238
+ (`blockedBy: "request.field_invalid"`). Also on 409 `checkpoint.config_refused` (server >= 7.113.0): which part of the
13239
+ row's configuration was refused — `taskConfig` (change the configuration, then decide the SAME card again) or
13240
+ `inheritedGate` (the row's recorded mode seed is bad — the run can only be cancelled). SDK: `err.extra.field`.
13024
13241
  count:
13025
13242
  type: integer
13026
13243
  description: 'S-904 `reason: over_cap` only — how many entries the bucket had (server >= 7.106.0). SDK: `err.extra.count`.'
@@ -13039,6 +13256,38 @@ components:
13039
13256
  (S-607); before that it was absent when the original refusal carried no code. SDK:
13040
13257
  `ResumeBlockedByPolicyError.blockedBy` (`undefined` when the key is absent or not a string — never defaulted).
13041
13258
 
13259
+ ResumeLegConflictError:
13260
+ description: >
13261
+ The 409 body of a RESUME LEG that core refused BEFORE the claim (`POST /v1/approvals/{sessionId}/decide`,
13262
+ `POST /v1/sessions/{sessionId}/wake`): `ErrorResponse` plus the one key those 409s may add, `retriable`. The key is declared HERE and
13263
+ not on the shared `ErrorResponse` on purpose (codex r1 [medium], sdk 16.0.0): `ErrorResponse` is the body of every error
13264
+ response in this document, and declaring `retriable` there would offer a retry signal on 401 / 404 / 422 / 5xx bodies where the
13265
+ server never mints it. (`ErrorResponse` stays open, so a validator cannot REJECT the key elsewhere; the confinement is that only
13266
+ these 409 responses reference this schema, and a test pins that count.)
13267
+ allOf:
13268
+ - $ref: '#/components/schemas/ErrorResponse'
13269
+ - type: object
13270
+ properties:
13271
+ retriable:
13272
+ type: boolean
13273
+ enum: [true]
13274
+ description: >-
13275
+ Only ever `true` (the server mints it from one conditional spread, `...(retriable ? { retriable: true } : {})`, in the
13276
+ one place that builds the 409 for a core `CheckpointError`; the only 404 that place can build, `checkpoint.not_found`, is
13277
+ never retriable) — absent otherwise, and absence means "the server did not say", NOT "not retriable". (The 200-status
13278
+ `{ status, errorCode?, retriable? }` bodies of the same legs carry the same key under their own schemas,
13279
+ `SessionWakeResult` and `ApprovalDecisionResult`.)
13280
+ Reading, one reading for the whole family: `retriable: true` = the pending card is STILL pending (core refused BEFORE the
13281
+ claim — the run row stays `suspended` and the card is untouched), NOT "a verbatim retry will succeed". server >= 7.113.0
13282
+ added the key to `checkpoint.walltime_axis_retired` (a pending row whose ledger carries the retired cross-slice walltime
13283
+ quota; a >= 7.39 engine refuses that row EVERY time, so the way out is `POST /v1/runs/{taskId}/cancel` first, then a new
13284
+ task on the same session — or finishing the run on a worker with the previous engine; before 7.113.0 this code ended the
13285
+ run `failed` with the session lock released and an orphaned card), to `steering.blocked_by_hook` on the `/wake` door (a
13286
+ wake carrying a `message` hit the deployment's `userPromptSubmit` hook crashing / timing out / refusing: the card stays
13287
+ pending and the same card can be woken again once the hook recovers; the live `steer` / `interrupt` doors answer the
13288
+ same code with 422 and no key), and to the new mirror code `checkpoint.config_refused` (409; the body also carries `field`:
13289
+ `taskConfig` | `inheritedGate`). Codes / status / `error` text are unchanged. SDK: `APIError.retriable`.
13290
+
13042
13291
  # ── Streaming event taxonomy (the UX 命脉). Each SSE `data:` line is one JSON-serialized AgentEvent. ──
13043
13292
  # TWO vocabularies (the pinned wire contract Drift 3). ⚠️ The two lines below are the SHAPE-DEFINING
13044
13293
  # contrast (per-token deltas vs turn-aggregated), NOT an enumeration of either leg's arm set — the union
@@ -13362,8 +13611,8 @@ components:
13362
13611
 
13363
13612
  Settlement:
13364
13613
  description: >
13365
- HOW the wait an ask was in ENDED — a DISCRIMINATED union on `kind` (core 7.6.0 `SETTLEMENT_KINDS`,
13366
- 12 words, CLOSED). It is deliberately NOT a free product of `kind` x `who`: a free product can express
13614
+ HOW the wait an ask was in ENDED — a DISCRIMINATED union on `kind` (core `SETTLEMENT_KINDS`: 12 words at 7.6.0,
13615
+ 14 as of core 7.39.0 — `policy_refused` joined at 7.26.2 and `policy_allowed` with the policy party at 7.39.0 / server 7.113.0; CLOSED). It is deliberately NOT a free product of `kind` x `who`: a free product can express
13367
13616
  "human_allowed, ended by the engine's window" — a structurally complete record that contradicts itself.
13368
13617
  `who` says WHICH PARTY ended it and its shape is fixed BY the word; `when` is epoch ms written at the
13369
13618
  settlement site; a person's refusal carries the decider's own `note` when one was attached.
@@ -13374,6 +13623,7 @@ components:
13374
13623
  secret-redacted (and `note` length-bounded) before they reach this wire.
13375
13624
  oneOf:
13376
13625
  - $ref: '#/components/schemas/Settlement_human'
13626
+ - $ref: '#/components/schemas/Settlement_policy'
13377
13627
  - $ref: '#/components/schemas/Settlement_approval_window_expired'
13378
13628
  - $ref: '#/components/schemas/Settlement_denial_limit_window_expired'
13379
13629
  - $ref: '#/components/schemas/Settlement_park_sla_expired'
@@ -13396,6 +13646,34 @@ components:
13396
13646
  when: { type: integer, format: int64, description: 'Epoch ms, written at the settlement site.' }
13397
13647
  note: { type: string, description: 'The refusing decider''s own words, when one was attached. A refusal WITHOUT a note is a bare "no".' }
13398
13648
 
13649
+ Settlement_policy:
13650
+ type: object
13651
+ additionalProperties: false
13652
+ description: >
13653
+ The DEPLOYMENT'S OWN POLICY decided (server >= 7.113.0 / core 7.39.0 — the `policy` party; sdk 16.0.0, found by the
13654
+ lockstep's completeness sweep, not in the release's own post): the approval channel reported that a MACHINE, not a
13655
+ person, answered this ask. A person and a policy are the two DECIDERS — each answers either way and differs only in whose
13656
+ decision the record names (`human_allowed` / `human_refused` name a person; `policy_allowed` / `policy_refused` name the
13657
+ policy). Server 7.113.0 value changes (MIGRATION 7.113.0 "audit face"): on the unattended legs (leader worker / repair
13658
+ leg / `sema` CLI and their resumes) the machine's class-answer allow is now settled `policy_allowed` with
13659
+ `who.approver: "unattended_class_answer"` — it was `human_allowed` / `who.party: "person"` — and every policy refusal
13660
+ (`UNATTENDED_APPROVAL_POLICY=deny`, a run-local refusal without a TTY, a decision with `settledBy: "policy"`) is
13661
+ `policy_refused` with `who.party: "policy"` — it was `who.party: "none"` (still the shape on servers 7.93.3 – 7.112.x, where
13662
+ `policy_refused` lives on `Settlement_fail_closed`; both spellings stay valid here). Readers that bucket by party read the new
13663
+ word; a 7.112.x reader of a 7.113.0 ledger withholds the whole `gate` (it does not know the party). The refusal's own words
13664
+ reach the model as the policy's words and are NOT on this record (no `note` seat).
13665
+ required: [kind, who, when]
13666
+ properties:
13667
+ kind: { type: string, enum: [policy_allowed, policy_refused] }
13668
+ who:
13669
+ type: object
13670
+ additionalProperties: false
13671
+ required: [party]
13672
+ properties:
13673
+ party: { const: policy }
13674
+ approver: { type: string, description: 'The seat that answered, secret-redacted (e.g. `unattended_class_answer`). Absent = the channel reported no name.' }
13675
+ when: { type: integer, format: int64, description: 'Epoch ms, written at the settlement site.' }
13676
+
13399
13677
  Settlement_approval_window_expired:
13400
13678
  type: object
13401
13679
  additionalProperties: false
@@ -13467,7 +13745,9 @@ components:
13467
13745
  thing — a person''s "no" is the user''s direction, a policy''s is a capability of this run; distinct
13468
13746
  from `no_approver` because a channel WAS wired and answered. This family carries NO `note` seat: the
13469
13747
  note seat belongs to the two human words, whose ABSENCE is a control-flow fact) · `task_aborted`
13470
- (the wait''s abort signal ended it).
13748
+ (the wait''s abort signal ended it). 🔴 server >= 7.113.0 (core 7.39.0): a policy refusal is spelled with `who.party: "policy"`
13749
+ and lives on `Settlement_policy`; `policy_refused` + `who.party: "none"` here is the 7.93.3 – 7.112.x shape, kept for the
13750
+ servers inside the support window.
13471
13751
  required: [kind, who, when]
13472
13752
  properties:
13473
13753
  kind:
@@ -13551,9 +13831,13 @@ components:
13551
13831
  unattended leg run under `UNATTENDED_PERMISSION_MODE=dontAsk`).
13552
13832
  AskClass:
13553
13833
  type: string
13554
- enum: [content_question, hook, ancestor, unverifiable_delete, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, operator_forced, write_protection_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, ordinary]
13834
+ enum: [content_question, hook, ancestor, unverifiable_delete, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, operator_forced, shell_floor, write_protection_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, ordinary]
13555
13835
  description: >
13556
- core 7.34.0 `ASK_CLASSES` (twenty words) — which CLASS an ask falls in (the columns of the mode × class arbitration table).
13836
+ core `ASK_CLASSES` (twenty-one words as of core 7.39.0 — 7.34.0 minted twenty; `shell_floor` joined in 7.39.0 #1260 and rides
13837
+ server >= 7.113.0) — which CLASS an ask falls in (the columns of the mode × class arbitration table).
13838
+ `shell_floor` = the operator's `shellGate: "always"` floor on a shell-running seat (the question the `operator_always` mandate
13839
+ word discloses), a class of its own so a set that holds another forced question still holds it; like `operator_forced` it is
13840
+ bypass-IMMUNE (not releasable).
13557
13841
  CLOSED on the gate record (screened with core `isAskClass`). `bypassPermissions` releases only the nine RELEASABLE classes
13558
13842
  (`transcript_write` / `rule_unreadable` / `probe` / `write_protection` / `tool_marks_mcp` / `tool_marks_a2a` /
13559
13843
  `artifact_shared_read` / `mode` / `ordinary`); every other class (deployment-authored `operator_forced`, ask / org rules, hook,
@@ -14090,7 +14374,14 @@ components:
14090
14374
  required: [type, inputId]
14091
14375
  properties:
14092
14376
  type: { const: human_input }
14093
- inputId: { type: string, description: 'Stable per-input id (also the durable steering queue idempotency key).' }
14377
+ inputId:
14378
+ type: string
14379
+ description: >
14380
+ Stable per-input id (also the durable steering queue idempotency key). server >= 7.113.0 (wire VALUE change): for a LIVE steer
14381
+ it is the receipt's `messageId` verbatim — keyless steers included (<= 7.112.0: an engine-minted uuid whenever no
14382
+ `Idempotency-Key` was sent); with a key it is `idem-<sha256[:32]>` on both the live and the parked leg. 🚧 A keyless PARKED
14383
+ steer is the exception (contract §19): the server passes no `inputId` with the parked entry, so this id does not join the
14384
+ receipt's `messageId`.
14094
14385
  sessionSeq: { type: integer, description: 'Leg-scoped monotone sequence of human inputs.' }
14095
14386
  carrier: { type: string, description: 'Which carrier delivered it (steer / objective / a source word). Open on read.' }
14096
14387
  source: { type: string, description: 'objective | steer | next_turn | wake | external | system. Open on read.' }
@@ -16634,6 +16925,8 @@ components:
16634
16925
  type: object
16635
16926
  # 封闭 + 回填 `activeTaskId`(census 轴一,2026-07-30):[1833] G10 —— session 已有活跃 run 的冲突
16636
16927
  # 失败形会带上占用者 taskId(SDK events.ts 早已声明,spec 侧漏)。
16928
+ # server 7.112.0(S-947;sdk 15.4.0 / K-42):+`abortCause`(没有 done 可落的取消形的成因落在本行顶层)。封闭对象不声明它,server 的真 `failed` 行对 15.3.0 spec 就是
16929
+ # 「$ must NOT have additional properties」—— 门 6 契约套抓不到这一件,由 `test/server-7112-wire.test.ts` P2(真 `buildCancelledLedgerRow` × spec)抓。
16637
16930
  additionalProperties: false
16638
16931
  required: [type]
16639
16932
  properties:
@@ -16641,6 +16934,16 @@ components:
16641
16934
  errorCode: { type: string }
16642
16935
  errorMessage: { type: string }
16643
16936
  activeTaskId: { type: string, description: '冲突失败形:当前占着该 session 的 run —— 消费方可直接引导 cancel/attach。' }
16937
+ abortCause:
16938
+ allOf: [{ $ref: '#/components/schemas/AbortCause' }]
16939
+ description: >
16940
+ server >= 7.112.0 (S-947; contract §5.0d) — the cause of a cancelled shape that has NO `done` row to carry
16941
+ `result.abortCause` (a disconnect after which no done was read / the leg was aborted before its terminal threw),
16942
+ landing at this row's TOP LEVEL beside `errorCode` (the cancelled shape's `errorCode` is `"cancelled"`, unchanged).
16943
+ Same closed words and the same present-iff gate as `TaskResult.abortCause`: PRESENT-IFF this row is the cancelled
16944
+ shape the server minted for its own abort. A `failed` row WITHOUT `errorCode` / `abortCause` (only
16945
+ `errorMessage`, e.g. `stream ended before a terminal event`) is an honest failure and is NOT a cancel — never
16946
+ read it as "disconnected". A cancelled row persisted before 7.112.0 has no `abortCause` (cause not recorded).
16644
16947
 
16645
16948
  # ══════════════════════════════════════════════════════════════════════════════════════════════
16646
16949
  # 🔴 census 轴一(2026-07-30)补的**四个未登记臂**。它们不是「将来会有」——**今天就在 wire 上**:
@@ -18015,7 +18318,18 @@ components:
18015
18318
  taskId: { type: string }
18016
18319
  status: { type: string, description: 'The run status the server saw at delivery time (running | suspended | a terminal status).' }
18017
18320
  delivery: { type: string, enum: [applied, queued, parked_for_wake] }
18018
- messageId: { type: string, description: 'Server-minted, stable across all three legs.' }
18321
+ messageId:
18322
+ type: string
18323
+ description: >
18324
+ Server-minted, stable across all three legs (`idem-<sha256[:32]>` when an `Idempotency-Key` was sent, else a fresh uuidv7).
18325
+ server >= 7.113.0 (wire VALUE change, no new key): on the LIVE leg (`delivery: "applied"`) the server ALWAYS hands this
18326
+ same value to the engine as the steer's `inputId` — keyless steers included (<= 7.112.0 passed it only when a key was sent,
18327
+ so a keyless live steer's `human_input.inputId` was an engine-minted uuid that never joined this receipt). So the
18328
+ `human_input` frame of that steer (`delivery: "applied"` / `"queued"`) carries `inputId === messageId`, and the user-input
18329
+ acceptance ledger (`userInputUndrained[].messageId`) is keyed by the same value. The PARKED legs: with a key,
18330
+ `inputId === messageId` as before; 🚧 a KEYLESS parked steer (`delivery: "queued"` / `"parked_for_wake"`) is the documented
18331
+ exception (contract §19) — the server passes no `inputId` with the parked queue entry, so this `messageId` is never that entry's
18332
+ `inputId` and the later `human_input` frame does NOT join it.
18019
18333
  priority: { $ref: '#/components/schemas/SteerPriority' }
18020
18334
  note: { type: string, description: 'Human-readable note on the two parked legs; absent on `applied`.' }
18021
18335
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "15.3.0",
3
+ "version": "16.0.0",
4
4
  "description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",