@sema-agent/sdk 15.4.0 → 16.0.1

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
@@ -415,8 +415,8 @@ paths:
415
415
  For anything that may take minutes or hit a HITL gate, use `/v1/runs` instead (this call blocks).
416
416
  🔴 PREPARE-TIME REFUSALS ARE TRIAGED ON WORK DONE (server >= 7.104.0 / S-271, one rule for the whole table): a
417
417
  prepare-time refusal code that HAS a status in the server's per-code triage table (`resume_at.*`, governance codes,
418
- `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported`, the registered `config.*`
419
- codes incl. the three roster-bound ones, `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
418
+ `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported` / `rewind_snapshot.unresolvable`, the
419
+ registered `config.*` codes incl. the three roster-bound ones, `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
420
420
  200 with a failed body) answers its 4xx / 5xx ERROR BODY only when the whole submission did ZERO WORK (cumulative
421
421
  `stats.turns === 0` AND `stats.tokens === 0` — the common case: nothing billed, not idempotency-cached). When a
422
422
  `verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
@@ -461,6 +461,10 @@ paths:
461
461
  history off (`fileHistory: "disabled"`) and asks to restore in the same breath; this server's request face
462
462
  refuses that combination earlier (400 `request.field_conflict`), so only a host building a TaskSpec directly can
463
463
  reach this code. `rewind.child_scope_unsupported` / `resume_at.*` also land here.
464
+ `rewind_snapshot.unresolvable` (server 7.114.0 `dist/runs.js` `resumeAtHttpStatus`: a `resumeAt` file restore —
465
+ `restoreFiles` — that finds no file-history boundary at / above the branch point) is in the same per-code table and lands
466
+ here too, on the same ZERO WORK condition (turns 0, tokens 0: nothing ran, nothing billed); a submission that already ran
467
+ turns gets 200 + a failed body carrying the code in `terminal.code` instead.
464
468
  content:
465
469
  application/json:
466
470
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -1708,7 +1712,7 @@ paths:
1708
1712
  '404':
1709
1713
  description: >
1710
1714
  Non-owner / unknown session (no existence oracle) — or, with `?message=`, an out-of-range index, whose
1711
- body additionally carries `total`.
1715
+ 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`).
1712
1716
  content:
1713
1717
  application/json:
1714
1718
  schema:
@@ -1844,7 +1848,12 @@ paths:
1844
1848
  leafId: { type: ["string", "null"], description: "null = session exists but has no entries yet." }
1845
1849
  '304': { description: 'Leaf unchanged since the presented ETag (no body).' }
1846
1850
  '401': { $ref: '#/components/responses/Unauthorized' }
1847
- '404': { $ref: '#/components/responses/NotFound' }
1851
+ '404':
1852
+ description: >
1853
+ 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`).
1854
+ content:
1855
+ application/json:
1856
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1848
1857
  '501': { $ref: '#/components/responses/NotImplemented' }
1849
1858
 
1850
1859
  /v1/sessions/{sessionId}/events:
@@ -1880,7 +1889,12 @@ paths:
1880
1889
  text/event-stream:
1881
1890
  schema: { type: string }
1882
1891
  '401': { $ref: '#/components/responses/Unauthorized' }
1883
- '404': { $ref: '#/components/responses/NotFound' }
1892
+ '404':
1893
+ description: >
1894
+ 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`).
1895
+ content:
1896
+ application/json:
1897
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1884
1898
  '500':
1885
1899
  description: >-
1886
1900
  (declared 2026-07-31) errorCode "internal.error" — `routes/sessions.ts`'s catch around the owner
@@ -2057,7 +2071,17 @@ paths:
2057
2071
  '400': { $ref: '#/components/responses/BadRequest' }
2058
2072
  '401': { $ref: '#/components/responses/Unauthorized' }
2059
2073
  '404': { description: 'No parked checkpoint for this session (nothing to wake), or session not visible to the caller.' }
2060
- '409': { description: 'Pending gate (`wake.gate_pending` — decide it through its own entry) or resume context missing.' }
2074
+ '409':
2075
+ description: >
2076
+ Pending gate (`wake.gate_pending` — decide it through its own entry) or resume context missing; also the resume-leg refusals core
2077
+ raises before the claim (`steering.blocked_by_hook` when the wake carried a `message` and the deployment's `userPromptSubmit`
2078
+ hook crashed / timed out / refused — server >= 7.113.0 adds `retriable: true` there; `checkpoint.config_refused`). Value change on
2079
+ the run row the wake minted for the refused attempt (server >= 7.113.0): it now ends `failed` with a structured terminal carrying
2080
+ `terminal.code: "steering.blocked_by_hook"` and holds no session lock (before 7.113.0 it ended with a code-less terminal and
2081
+ `result: null` — a zero-execution refusal recorded as an ordinary failure); the card itself stays pending.
2082
+ content:
2083
+ application/json:
2084
+ schema: { $ref: '#/components/schemas/ResumeLegConflictError' }
2061
2085
  '422': { description: '`steering.invalid_content` — the message failed the steering content gate.' }
2062
2086
  '429': { $ref: '#/components/responses/RateLimited' }
2063
2087
  '501': { $ref: '#/components/responses/NotImplemented' }
@@ -2280,6 +2304,20 @@ paths:
2280
2304
  '401': { $ref: '#/components/responses/Unauthorized' }
2281
2305
  '404': { $ref: '#/components/responses/NotFound' }
2282
2306
  '409': { $ref: '#/components/responses/Conflict' }
2307
+ '500':
2308
+ description: >-
2309
+ `corrupt` (server >= 7.114.0 / core 7.39.2 on the local file-backed lane; the SQL-backed store already refuses a bad row
2310
+ with the same code): this session's stored rules are PRESENT BUT UNREADABLE (a torn or hand-edited rules file). The door
2311
+ REFUSES instead of answering "no rules" — session rules only ever tighten, so the row IS the restriction and reading a
2312
+ damaged one as empty would silently drop it. DETERMINISTIC: a retry cannot change it (the SDK does not auto-retry this
2313
+ code; it arrives as a bare `APIError` with `errorCode: "corrupt"`); the repair is the operator's — fix the file or
2314
+ delete it (deleting = an explicit "no rules"). `error` names the session, the damage grade (`unreadable` /
2315
+ `unparseable` / `malformed`) and both exits, with NO on-disk path (it reaches the end user; the path goes to the
2316
+ operator's log). A rules file that is ABSENT is not this arm: it answers 200 `{ rules: { rev: 0 } }`.
2317
+ Also `internal.error` (unclassified).
2318
+ content:
2319
+ application/json:
2320
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2283
2321
  '501': { $ref: '#/components/responses/NotImplemented' }
2284
2322
  put:
2285
2323
  tags: [sessions]
@@ -2322,6 +2360,21 @@ paths:
2322
2360
  schema: { $ref: '#/components/schemas/ErrorResponse' }
2323
2361
  '404': { $ref: '#/components/responses/NotFound' }
2324
2362
  '409': { $ref: '#/components/responses/Conflict' }
2363
+ '500':
2364
+ description: >-
2365
+ `corrupt` (server >= 7.114.0 / core 7.39.2 on the local file-backed lane; the SQL-backed store already refuses a bad row
2366
+ with the same code): the read this write makes first finds the session's stored rules PRESENT BUT UNREADABLE (a torn or
2367
+ hand-edited rules file). The door REFUSES instead of treating it as "no rules" — session rules only ever tighten, so
2368
+ the row IS the restriction — and `PUT` never overwrites the damaged bytes (overwriting = losing the evidence): nothing is
2369
+ written. DETERMINISTIC: a retry cannot change it (the SDK does not auto-retry this code; it arrives as a bare `APIError`
2370
+ with `errorCode: "corrupt"`); the repair is the operator's — fix the file or delete it (deleting = an explicit "no
2371
+ rules"). For a damaged rules row `error` names the session, the damage grade and both exits, with NO on-disk path.
2372
+ ⚠️ The same code also comes from the write path's high-water-mark sidecar (`.hwm.json`) being unreadable, and that one
2373
+ sentence STILL names the sidecar's on-disk path (server 7.114.0 discloses it as a known gap: core #1307 / #1309, core
2374
+ 7.41.0). Also `internal.error` (unclassified).
2375
+ content:
2376
+ application/json:
2377
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2325
2378
  '501': { $ref: '#/components/responses/NotImplemented' }
2326
2379
 
2327
2380
  /v1/sessions/{sessionId}/mcp:
@@ -3307,7 +3360,7 @@ paths:
3307
3360
  parked row carries no root-session anchor (refused before the claim, nothing consumed).
3308
3361
  content:
3309
3362
  application/json:
3310
- schema: { $ref: '#/components/schemas/ErrorResponse' }
3363
+ schema: { $ref: '#/components/schemas/ResumeLegConflictError' }
3311
3364
  # 🔴 REMOVED 2026-07-31 — the 410 that never was. `grep -rn "\b410\b" src/` across the whole server
3312
3365
  # is ZERO hits: `approval_stale` ships as a **409** carrying `errorCode: "approval_stale"` (the CAS
3313
3366
  # 409 stays bare → ConflictError, so the two 409s never collide). The `terminal` enum values
@@ -6614,12 +6667,18 @@ components:
6614
6667
 
6615
6668
  RunStatus:
6616
6669
  type: string
6617
- enum: [running, completed, failed, suspended, needs_review]
6670
+ enum: [running, completed, failed, suspended, needs_review, blocked]
6618
6671
  description: >
6619
6672
  `suspended` = HITL (an F4 policy gate or AskUserQuestion is waiting on a human) — NOT an error.
6620
6673
  `needs_review` (design/80 D-0) = durable TERMINAL of a dry-run/shadow review (POST-prediction),
6621
6674
  distinct from the mid-run `suspended` pause. A user-cancelled run is `failed` with `errorCode`
6622
6675
  "cancelled" (cancel is LIVE; `cancelling` is a cancel-ack field, NOT a member of this enum).
6676
+ `blocked` = durable TERMINAL: the run ended without doing the work (`result.terminal.kind: "blocked"` — the agent's
6677
+ `ReportBlocked`, or, server >= 7.113.0, a deployment `userPromptSubmit` hook keeping the prompt from being submitted, see
6678
+ `TerminalBlockedDetail`); a governance verdict, not an error. The server's run-status table has it (`blocked`, an
6679
+ unsuccessful terminal) and the SDK type has listed it since 2.1.4 (whose own note records the server passing core's status words
6680
+ 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
6681
+ validated a blocked run row against it).
6623
6682
 
6624
6683
  TaskStats:
6625
6684
  type: object
@@ -7357,6 +7416,15 @@ components:
7357
7416
  description: >
7358
7417
  Turn-boundary reminder attachments (design/133 + G1). Each flag opts INTO an injection; omitted = off.
7359
7418
  `changedFiles` may also be an object to cap the listing.
7419
+ 🔴 BREAKING (server >= 7.113.0 / core 7.39.0 #1254; K-44): `planModeReminder` was retired and is no longer declared
7420
+ here. A submit body that carries it — any value, `false` included (the server's gate tests `!== undefined`, not
7421
+ truthiness) — is refused 400 `request.field_invalid` with a sentence naming the retirement; before 7.113.0 it was
7422
+ accepted and the engine refused later, at prepare. The engine now states plan posture itself whenever the
7423
+ permission mode is "plan" (first request, periodic reminders, exit frame): remove the key and send
7424
+ `permissionMode: "plan"`. A stored body that still carries the key resumes with the key dropped (resume replays
7425
+ are not refused). THIS schema stays open (`additionalProperties: true`), so a validator built from it will not
7426
+ reject the key — the refusal is the server's, not this schema's. A server <= 7.112.x still accepts the key; this
7427
+ package no longer declares it (sdk 16.0.0, major).
7360
7428
  additionalProperties: true
7361
7429
  properties:
7362
7430
  todoReminder: { type: boolean }
@@ -7365,7 +7433,6 @@ components:
7365
7433
  oneOf:
7366
7434
  - { type: boolean }
7367
7435
  - { type: object, properties: { maxFiles: { type: integer } } }
7368
- planModeReminder: { type: boolean }
7369
7436
  budgetUsd: { type: boolean }
7370
7437
  backgroundTasks: { type: boolean }
7371
7438
  toolsDelta: { type: boolean }
@@ -7504,11 +7571,42 @@ components:
7504
7571
  TerminalCause_blocked:
7505
7572
  type: object
7506
7573
  additionalProperties: false
7507
- description: 'The agent could not finish and said why (a governance verdict is NOT a failure).'
7574
+ description: >
7575
+ The run ended without doing the work (a governance verdict is NOT a failure). Two sources, told apart by `detail`: with NO
7576
+ `detail` — the agent's own words from its `ReportBlocked` call; with `detail.kind: "prompt_blocked_by_hook"` (server >= 7.113.0 /
7577
+ core #1277; K-43-class closed-object gap) — the engine's sentence about the deployment's `userPromptSubmit` hook, framed as a
7578
+ reminder for the model-facing lane. Terminal: the run is over and nobody is being waited on (contrast `paused`).
7579
+ Same shape on all four faces (the synchronous 200 body, the stream's `done.result`, `GET /v1/runs/{taskId}` -> `result`, the
7580
+ ledger `done` row).
7508
7581
  required: [kind, reason]
7509
7582
  properties:
7510
7583
  kind: { const: blocked }
7511
- reason: { type: string, description: 'Why the agent could not finish.' }
7584
+ 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).' }
7585
+ detail:
7586
+ allOf: [{ $ref: '#/components/schemas/TerminalBlockedDetail' }]
7587
+ description: >
7588
+ 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
7589
+ NOT submitted (the objective never became a message and the loop never started), so changing it and sending again is
7590
+ meaningful. PRESENT only on that terminal; ABSENT means the agent reported `ReportBlocked` ("I cannot go on"), or a server
7591
+ before 7.113.0.
7592
+
7593
+ TerminalBlockedDetail:
7594
+ type: object
7595
+ additionalProperties: false
7596
+ required: [kind, cause]
7597
+ description: >
7598
+ server >= 7.113.0 (core #1277; K-44) — the machine-readable account of a `blocked` terminal caused by the deployment's
7599
+ `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
7600
+ prompt from being submitted (core `PROMPT_BLOCK_CAUSES`): `block` — the hook answered and refused (its words ride `hookReason`);
7601
+ `crashed` — the hook threw or rejected (the seat is fail-closed: a screen that failed has not cleared the prompt); `timeout` — no
7602
+ answer within the hook's `timeoutMs` bound (fail-closed the same way); `cancelled` — the task was cancelled while the hook was
7603
+ still screening. `hookReason` is the hook's OWN words (present only on cause `block`, and only when the hook gave some): folded to
7604
+ one line, bounded, with the engine's envelope tags defused and passed through the same secret redaction as `reason` — still the
7605
+ 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".
7606
+ properties:
7607
+ kind: { const: prompt_blocked_by_hook }
7608
+ cause: { type: string, enum: [block, crashed, timeout, cancelled] }
7609
+ hookReason: { type: string, description: 'The hook''s own refusal words (untrusted free text; present only on `block` and only when stated).' }
7512
7610
 
7513
7611
  PausedCause:
7514
7612
  type: object
@@ -7656,6 +7754,18 @@ components:
7656
7754
  verdict: { type: string, enum: [PASS, FAIL, PARTIAL, unverified] }
7657
7755
  rounds: { type: integer }
7658
7756
  findings: { type: array, items: { type: string } }
7757
+ cancelContext:
7758
+ allOf: [{ $ref: '#/components/schemas/CancelContext' }]
7759
+ description: >
7760
+ 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
7761
+ index before) — the SCENE SNAPSHOT of a USER CANCEL, a side key beside `abortCause` (both are minted by the same mint
7762
+ point, `mintCancelledResult`, under the same "fill the empty seat only" gate; neither lives inside `terminal`).
7763
+ PRESENT only on a cancelled terminal the server minted; ABSENT on a model's own failure / a normal completion, and
7764
+ ABSENT as a whole when not one of its keys can be proven (the server never mints an empty object — absence reads as "no
7765
+ cancel scene to look at", NOT "nothing was wrong at cancel time"). Landings: `GET /v1/runs/{taskId}` -> `result.cancelContext`
7766
+ (and the same object rides the other cancelled-terminal landings listed on `abortCause`). Shape and per-key reading
7767
+ rules: `CancelContext`.
7768
+
7659
7769
  abortCause:
7660
7770
  allOf: [{ $ref: '#/components/schemas/AbortCause' }]
7661
7771
  description: >
@@ -7703,6 +7813,59 @@ components:
7703
7813
  CLOSED set, additive-only by the server's rule (a new word is a new server version in `wire-changes` AND a new SDK
7704
7814
  minor, in this enum in the same batch): read an unknown word as "the server aborted this run, cause not recognised".
7705
7815
 
7816
+ CancelContext:
7817
+ type: object
7818
+ # 封闭(K-43,sdk 16.0.0):铸点 `run-cancel-context.js` `buildCancelContext` 是逐键条件 spread 的字面量(lastEventKind / lastEventAgeMs / lastBrainStatus / elapsedMs),
7819
+ # `lastBrainStatus` 经 `turn-activity.js` `pickRunBrainStatus` 投成五键(刻意不含 provider 自由文本 `detail` 与相对量 `retryInMs`)—— 与本 schema 一一对上;
7820
+ # `test/server-7113-wire.test.ts` K3 用真 `recordTurnActivity` + 真 `mintCancelledResult` 的字节逐键核。
7821
+ additionalProperties: false
7822
+ description: >
7823
+ 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
7824
+ from this run" — triage material, never a state-machine input. 🔴 It is NOT the model-call phase: the server structurally
7825
+ cannot see first-byte waits or retries (that half lives in the engine's stream layer); `lastEventAgeMs` is NOT "how long the
7826
+ model has been hung" (a long tool call produces no ledger event either). Every key is optional and absence is honest:
7827
+ `lastEventKind` / `lastEventAgeMs` come from a SAME-REPLICA in-process activity registry (a cross-replica cancel or a
7828
+ replica restart cannot read it — absent means "this replica cannot prove it", never fold it to 0: that would render a
7829
+ 8-minute hang as "just alive"); `lastEventKind` is MORE likely to be absent than `lastEventAgeMs` (some activity seats advance
7830
+ the clock without a word), so "age without kind" is a legal reading; `lastBrainStatus` is absent when the run never
7831
+ emitted a `status` frame or the replica cannot read it; `elapsedMs` (run start to cancel, wall clock) is normally present
7832
+ and absent only when the row's `createdAt` cannot be parsed (again never folded to 0). The side key `abortCause` says WHO
7833
+ stopped the run; this object says what the server saw at that moment.
7834
+ properties:
7835
+ lastEventKind:
7836
+ type: string
7837
+ description: 'The type of the last event that entered this run''s durable ledger (`text` / `tool_start` / `status` / ...). Open string.'
7838
+ lastEventAgeMs:
7839
+ type: number
7840
+ minimum: 0
7841
+ description: 'Milliseconds since that last durable event (>= 0).'
7842
+ lastBrainStatus:
7843
+ allOf: [{ $ref: '#/components/schemas/CancelContextBrainStatus' }]
7844
+ description: >
7845
+ The last `status` (BrainStatus) frame at the tail of the run's ledger (an S-94 extension), as a five-key subset — "which
7846
+ retry, waiting for what, which error class". STICKY: later text / tool events do not clear it. Absent when the run never
7847
+ emitted a `status` frame or this replica cannot read it (never `null`).
7848
+ elapsedMs:
7849
+ type: number
7850
+ minimum: 0
7851
+ description: 'Wall-clock milliseconds from the run''s start to this cancel (>= 0).'
7852
+
7853
+ CancelContextBrainStatus:
7854
+ type: object
7855
+ additionalProperties: false
7856
+ required: [phase]
7857
+ description: >
7858
+ The five-key subset of the last BrainStatus frame (`CancelContext.lastBrainStatus`; K-43). `phase` is the snapshot's reason to
7859
+ exist (no phase, no snapshot — never half of one). Deliberately NOT carried: `detail` (provider free text) and `retryInMs` /
7860
+ `retryInSec` (relative values go stale; `retryAtMs` is the cross-process one — a wall-clock instant on the SENDING process's
7861
+ clock, an approximation, never compare it with a local monotonic timer).
7862
+ properties:
7863
+ phase: { type: string }
7864
+ attempt: { type: number }
7865
+ maxRetries: { type: number }
7866
+ retryAtMs: { type: number }
7867
+ errClass: { type: string }
7868
+
7706
7869
  LegacyPlaneTaskResult:
7707
7870
  type: object
7708
7871
  additionalProperties: true
@@ -7912,10 +8075,14 @@ components:
7912
8075
  changed shape with it (`result.terminal`, no plane keys): the row's `errorCode` column is now projected
7913
8076
  from `result.terminal` (`src/http/routes/runs.ts` -> `persistedPlaneOf`, which also reads the flat bytes
7914
8077
  7.63.0 and earlier wrote to disk), NOT copied from a `result.errorCode` that no longer exists.
7915
- # 封闭(census 轴一,2026-07-30;A-075.21 回填后 12 键):铸造点 routes/runs.ts 是一个逐键写死的
8078
+ # 封闭(census 轴一,2026-07-30;A-075.21 回填后 12 键;**K-43 / sdk 16.0.0 补 `heldBy` / `cancelRequested` 后 14 键**):铸造点 routes/runs.ts 是一个逐键写死的
7916
8079
  # 字面量(taskId/sessionId/status/msSinceLastActivity/crossSliceUsage/result/supervisorCost/
7917
- # suggestions/errorCode/error/jobId/source),与本 properties 一一对上;只有 taskId/sessionId/status
7918
- # 无条件在场(其余走 `?? undefined` / 条件 spread ⇒ JSON 丢键)⇒ required 维持 3 键。
8080
+ # suggestions/errorCode/error/jobId/source/heldBy/cancelRequested),与本 properties 一一对上;只有 taskId/sessionId/status
8081
+ # 无条件在场(其余走 `?? undefined` / 条件 spread ⇒ JSON 丢键)⇒ required 维持 3 键(`heldBy` / `cancelRequested` 在 server >= 7.73.0 上恒在场,
8082
+ # 但 required 不抬:本包支持底线 3.0.0,老 server 没有这两键)。
8083
+ # 🔴 K-43(既存缺口,与 abortCause 无关,K-41 同病):`heldBy` / `cancelRequested` 自 server 7.73.0(S-230 P-45)就在真 `GET /v1/runs/:id` 上,
8084
+ # 而本封闭对象一直没声明它们 ⇒ 按本 schema 严格校验响应的消费方会得「$ must NOT have additional properties」;门 6 契约套抓不到,
8085
+ # 由 `test/server-7113-wire.test.ts` K3(真 `handleRuns` 字节 × spec)抓。
7919
8086
  # (A-075.21:此前的「恰好 10 键」对 msSinceLastActivity(#245 S1)/crossSliceUsage([3833] S-4)
7920
8087
  # 两条铸键路径结构性失明 —— server test/producer-contract.test.ts 的 S-4 登记钉逐字记着这笔账。)
7921
8088
  additionalProperties: false
@@ -7983,6 +8150,29 @@ components:
7983
8150
  items: { type: string }
7984
8151
  description: Follow-up prompt suggestions produced for this run (absent when none were generated).
7985
8152
  supervisorCost: { $ref: '#/components/schemas/SupervisorCostBreakdown' }
8153
+ heldBy:
8154
+ type: [string, "null"]
8155
+ description: >
8156
+ 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
8157
+ did not declare it) — the replica HOLDING this run NOW (the row's `instance_id`: written at create, TAKEN OVER by a
8158
+ cross-replica resume claim), so it answers "where is the live leg", not "who created the row". Always present on a
8159
+ server that has it, and an explicit `null` (never a missing key) means no replica holds it. 🔴 `heldBy` becoming
8160
+ `null` OR the run reaching a terminal status = the claim is released (either one); on a terminal row `heldBy` reads
8161
+ as a HISTORICAL fact ("which replica finished it"), not "still occupied". ⚠️ The one exception is the stale fold: a
8162
+ `running` row older than the stale window is shown with `status: "failed"` + `error: "run stalled (instance lost?)"`
8163
+ — that is the READ's judgement; the ledger row is still `running` and its claim is held until the reaper's next beat,
8164
+ and `heldBy` still names the LOST replica (the one useful coordinate when debugging). A deployment-internal replica
8165
+ identity, not a user-readable location. Absent on a server < 7.73.0.
8166
+ cancelRequested:
8167
+ type: boolean
8168
+ description: >
8169
+ server >= 7.73.0 (S-230 P-45; contract §5.5 (c); K-43) — the durable cancel flag (cross-replica; the holding replica
8170
+ polls it on its heartbeat beat). ⚠️ It is a pending cancel: a run that has already been cancelled is spoken by `status` +
8171
+ `errorCode: "cancelled"` (a park-arm cancel never goes through this flag — it finalizes the row directly), and a resume
8172
+ claim CLEARS the flag (a cancel that landed on a suspend must not kill the run again after it resumes). So never read
8173
+ `cancelRequested` flipping back to `false` as "the cancel completed" — wait for a terminal `status` + `errorCode:
8174
+ "cancelled"`. The `cancelling` 202 ack of `POST /v1/runs/{taskId}/cancel` carries the same key
8175
+ (`CancelAck.cancelRequested`). Absent on a server < 7.73.0.
7986
8176
 
7987
8177
  SupervisorCostBreakdown:
7988
8178
  type: object
@@ -8020,14 +8210,23 @@ components:
8020
8210
  description: >
8021
8211
  Synchronous ack for `POST /v1/runs/:id/cancel`. `status` is "cancelling" (accepted) or a terminal status
8022
8212
  (idempotent no-op). NOTE: "cancelling" is NOT a RunStatus enum member — it only appears here.
8023
- # 封闭(census 轴一,2026-07-30):cancel 面的 7 个 sendJson 铸造点(routes/runs.ts
8024
- # 495/505/512)键集的并集恰好 = {taskId, status, note?, errorCode?};taskId/status 无条件在场。
8213
+ # 封闭(census 轴一,2026-07-30):cancel 面的 sendJson 铸造点键集的并集 = {taskId, status, note?, errorCode?, cancelRequested};
8214
+ # taskId/status 无条件在场。🔴 K-43 / sdk 16.0.0 补 `cancelRequested`:自 server 7.73.0(S-230 P-45)每一个 2xx 受理体都带它,
8215
+ # 而本封闭对象一直没声明 ⇒ 严格校验回执的消费方会得「$ must NOT have additional properties」;`test/server-7113-wire.test.ts` K3 抓(真 `handleRuns` 三臂字节 × spec)。
8025
8216
  additionalProperties: false
8026
8217
  required: [taskId, status]
8027
8218
  properties:
8028
8219
  taskId: { type: string }
8029
8220
  status: { type: string }
8030
8221
  note: { type: string }
8222
+ cancelRequested:
8223
+ type: boolean
8224
+ description: >
8225
+ server >= 7.73.0 (S-230 P-45; contract §5.5 (c); K-43) — the same predicate as `RunRecord.cancelRequested` (the durable
8226
+ cancel flag), present on every 2xx ack the cancel verb mints: `true` on the `status: "cancelling"` branch (the CAS just
8227
+ won — the flag is on the row and the holding replica's next beat reads it); `false` on the park-arm terminal branch (a
8228
+ parked run is finalized directly, no flag was needed); on the idempotent no-op branches and the concurrent-terminalized
8229
+ 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.
8031
8230
  errorCode:
8032
8231
  type: [string, "null"]
8033
8232
  description: >
@@ -12282,7 +12481,9 @@ components:
12282
12481
  description: >-
12283
12482
  server >=7.110.0 (S-928 user-input acceptance ledger): user inputs this session LOST to a crash —
12284
12483
  the server had answered 200 `applied` (interrupt{text}: 202) but the process carrying that run died
12285
- before the run settled; whether the run consumed them is NOT known (see `engine_notice`
12484
+ before the engine confirmed consuming them (server >= 7.113.0 / core 7.39.0 #1273: an acceptance the engine confirmed
12485
+ consuming is settled `consumed` and is no longer listed here — the <= 7.112.x reading "whether the run consumed
12486
+ them is not known" narrowed to this; it still does NOT say "never consumed"; see `engine_notice`
12286
12487
  `task.user_steer_undrained{cause:"crashed"}`). Converged rows only, one row per (taskId, messageId),
12287
12488
  newest 500. From server >=7.111.0 (S-966) the rows include SUBAGENT steers (recorded under the run on the
12288
12489
  receipt; same row shape, `kind` is still only `steer`; `messageId` = the subagent steer receipt's).
@@ -13067,7 +13268,9 @@ components:
13067
13268
  On 400 `request.field_invalid` for a `settings.permissions` bucket (server >= 7.106.0 / S-904): which bucket —
13068
13269
  `settings.permissions.allow` | `settings.permissions.deny` | `settings.permissions.ask`. (`rules.*` content refusals
13069
13270
  carry their own `field`.) Resume legs carry the same group on 409 `resume_blocked_by_policy`
13070
- (`blockedBy: "request.field_invalid"`). SDK: `err.extra.field`.
13271
+ (`blockedBy: "request.field_invalid"`). Also on 409 `checkpoint.config_refused` (server >= 7.113.0): which part of the
13272
+ row's configuration was refused — `taskConfig` (change the configuration, then decide the SAME card again) or
13273
+ `inheritedGate` (the row's recorded mode seed is bad — the run can only be cancelled). SDK: `err.extra.field`.
13071
13274
  count:
13072
13275
  type: integer
13073
13276
  description: 'S-904 `reason: over_cap` only — how many entries the bucket had (server >= 7.106.0). SDK: `err.extra.count`.'
@@ -13086,6 +13289,38 @@ components:
13086
13289
  (S-607); before that it was absent when the original refusal carried no code. SDK:
13087
13290
  `ResumeBlockedByPolicyError.blockedBy` (`undefined` when the key is absent or not a string — never defaulted).
13088
13291
 
13292
+ ResumeLegConflictError:
13293
+ description: >
13294
+ The 409 body of a RESUME LEG that core refused BEFORE the claim (`POST /v1/approvals/{sessionId}/decide`,
13295
+ `POST /v1/sessions/{sessionId}/wake`): `ErrorResponse` plus the one key those 409s may add, `retriable`. The key is declared HERE and
13296
+ not on the shared `ErrorResponse` on purpose (codex r1 [medium], sdk 16.0.0): `ErrorResponse` is the body of every error
13297
+ response in this document, and declaring `retriable` there would offer a retry signal on 401 / 404 / 422 / 5xx bodies where the
13298
+ server never mints it. (`ErrorResponse` stays open, so a validator cannot REJECT the key elsewhere; the confinement is that only
13299
+ these 409 responses reference this schema, and a test pins that count.)
13300
+ allOf:
13301
+ - $ref: '#/components/schemas/ErrorResponse'
13302
+ - type: object
13303
+ properties:
13304
+ retriable:
13305
+ type: boolean
13306
+ enum: [true]
13307
+ description: >-
13308
+ Only ever `true` (the server mints it from one conditional spread, `...(retriable ? { retriable: true } : {})`, in the
13309
+ one place that builds the 409 for a core `CheckpointError`; the only 404 that place can build, `checkpoint.not_found`, is
13310
+ never retriable) — absent otherwise, and absence means "the server did not say", NOT "not retriable". (The 200-status
13311
+ `{ status, errorCode?, retriable? }` bodies of the same legs carry the same key under their own schemas,
13312
+ `SessionWakeResult` and `ApprovalDecisionResult`.)
13313
+ Reading, one reading for the whole family: `retriable: true` = the pending card is STILL pending (core refused BEFORE the
13314
+ claim — the run row stays `suspended` and the card is untouched), NOT "a verbatim retry will succeed". server >= 7.113.0
13315
+ added the key to `checkpoint.walltime_axis_retired` (a pending row whose ledger carries the retired cross-slice walltime
13316
+ quota; a >= 7.39 engine refuses that row EVERY time, so the way out is `POST /v1/runs/{taskId}/cancel` first, then a new
13317
+ task on the same session — or finishing the run on a worker with the previous engine; before 7.113.0 this code ended the
13318
+ run `failed` with the session lock released and an orphaned card), to `steering.blocked_by_hook` on the `/wake` door (a
13319
+ wake carrying a `message` hit the deployment's `userPromptSubmit` hook crashing / timing out / refusing: the card stays
13320
+ pending and the same card can be woken again once the hook recovers; the live `steer` / `interrupt` doors answer the
13321
+ same code with 422 and no key), and to the new mirror code `checkpoint.config_refused` (409; the body also carries `field`:
13322
+ `taskConfig` | `inheritedGate`). Codes / status / `error` text are unchanged. SDK: `APIError.retriable`.
13323
+
13089
13324
  # ── Streaming event taxonomy (the UX 命脉). Each SSE `data:` line is one JSON-serialized AgentEvent. ──
13090
13325
  # TWO vocabularies (the pinned wire contract Drift 3). ⚠️ The two lines below are the SHAPE-DEFINING
13091
13326
  # contrast (per-token deltas vs turn-aggregated), NOT an enumeration of either leg's arm set — the union
@@ -13409,8 +13644,8 @@ components:
13409
13644
 
13410
13645
  Settlement:
13411
13646
  description: >
13412
- HOW the wait an ask was in ENDED — a DISCRIMINATED union on `kind` (core 7.6.0 `SETTLEMENT_KINDS`,
13413
- 12 words, CLOSED). It is deliberately NOT a free product of `kind` x `who`: a free product can express
13647
+ HOW the wait an ask was in ENDED — a DISCRIMINATED union on `kind` (core `SETTLEMENT_KINDS`: 12 words at 7.6.0,
13648
+ 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
13414
13649
  "human_allowed, ended by the engine's window" — a structurally complete record that contradicts itself.
13415
13650
  `who` says WHICH PARTY ended it and its shape is fixed BY the word; `when` is epoch ms written at the
13416
13651
  settlement site; a person's refusal carries the decider's own `note` when one was attached.
@@ -13421,6 +13656,7 @@ components:
13421
13656
  secret-redacted (and `note` length-bounded) before they reach this wire.
13422
13657
  oneOf:
13423
13658
  - $ref: '#/components/schemas/Settlement_human'
13659
+ - $ref: '#/components/schemas/Settlement_policy'
13424
13660
  - $ref: '#/components/schemas/Settlement_approval_window_expired'
13425
13661
  - $ref: '#/components/schemas/Settlement_denial_limit_window_expired'
13426
13662
  - $ref: '#/components/schemas/Settlement_park_sla_expired'
@@ -13443,6 +13679,34 @@ components:
13443
13679
  when: { type: integer, format: int64, description: 'Epoch ms, written at the settlement site.' }
13444
13680
  note: { type: string, description: 'The refusing decider''s own words, when one was attached. A refusal WITHOUT a note is a bare "no".' }
13445
13681
 
13682
+ Settlement_policy:
13683
+ type: object
13684
+ additionalProperties: false
13685
+ description: >
13686
+ The DEPLOYMENT'S OWN POLICY decided (server >= 7.113.0 / core 7.39.0 — the `policy` party; sdk 16.0.0, found by the
13687
+ lockstep's completeness sweep, not in the release's own post): the approval channel reported that a MACHINE, not a
13688
+ person, answered this ask. A person and a policy are the two DECIDERS — each answers either way and differs only in whose
13689
+ decision the record names (`human_allowed` / `human_refused` name a person; `policy_allowed` / `policy_refused` name the
13690
+ policy). Server 7.113.0 value changes (MIGRATION 7.113.0 "audit face"): on the unattended legs (leader worker / repair
13691
+ leg / `sema` CLI and their resumes) the machine's class-answer allow is now settled `policy_allowed` with
13692
+ `who.approver: "unattended_class_answer"` — it was `human_allowed` / `who.party: "person"` — and every policy refusal
13693
+ (`UNATTENDED_APPROVAL_POLICY=deny`, a run-local refusal without a TTY, a decision with `settledBy: "policy"`) is
13694
+ `policy_refused` with `who.party: "policy"` — it was `who.party: "none"` (still the shape on servers 7.93.3 – 7.112.x, where
13695
+ `policy_refused` lives on `Settlement_fail_closed`; both spellings stay valid here). Readers that bucket by party read the new
13696
+ 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
13697
+ reach the model as the policy's words and are NOT on this record (no `note` seat).
13698
+ required: [kind, who, when]
13699
+ properties:
13700
+ kind: { type: string, enum: [policy_allowed, policy_refused] }
13701
+ who:
13702
+ type: object
13703
+ additionalProperties: false
13704
+ required: [party]
13705
+ properties:
13706
+ party: { const: policy }
13707
+ approver: { type: string, description: 'The seat that answered, secret-redacted (e.g. `unattended_class_answer`). Absent = the channel reported no name.' }
13708
+ when: { type: integer, format: int64, description: 'Epoch ms, written at the settlement site.' }
13709
+
13446
13710
  Settlement_approval_window_expired:
13447
13711
  type: object
13448
13712
  additionalProperties: false
@@ -13514,7 +13778,9 @@ components:
13514
13778
  thing — a person''s "no" is the user''s direction, a policy''s is a capability of this run; distinct
13515
13779
  from `no_approver` because a channel WAS wired and answered. This family carries NO `note` seat: the
13516
13780
  note seat belongs to the two human words, whose ABSENCE is a control-flow fact) · `task_aborted`
13517
- (the wait''s abort signal ended it).
13781
+ (the wait''s abort signal ended it). 🔴 server >= 7.113.0 (core 7.39.0): a policy refusal is spelled with `who.party: "policy"`
13782
+ and lives on `Settlement_policy`; `policy_refused` + `who.party: "none"` here is the 7.93.3 – 7.112.x shape, kept for the
13783
+ servers inside the support window.
13518
13784
  required: [kind, who, when]
13519
13785
  properties:
13520
13786
  kind:
@@ -13598,9 +13864,13 @@ components:
13598
13864
  unattended leg run under `UNATTENDED_PERMISSION_MODE=dontAsk`).
13599
13865
  AskClass:
13600
13866
  type: string
13601
- 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]
13867
+ 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]
13602
13868
  description: >
13603
- core 7.34.0 `ASK_CLASSES` (twenty words) — which CLASS an ask falls in (the columns of the mode × class arbitration table).
13869
+ 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
13870
+ server >= 7.113.0) — which CLASS an ask falls in (the columns of the mode × class arbitration table).
13871
+ `shell_floor` = the operator's `shellGate: "always"` floor on a shell-running seat (the question the `operator_always` mandate
13872
+ word discloses), a class of its own so a set that holds another forced question still holds it; like `operator_forced` it is
13873
+ bypass-IMMUNE (not releasable).
13604
13874
  CLOSED on the gate record (screened with core `isAskClass`). `bypassPermissions` releases only the nine RELEASABLE classes
13605
13875
  (`transcript_write` / `rule_unreadable` / `probe` / `write_protection` / `tool_marks_mcp` / `tool_marks_a2a` /
13606
13876
  `artifact_shared_read` / `mode` / `ordinary`); every other class (deployment-authored `operator_forced`, ask / org rules, hook,
@@ -14137,7 +14407,14 @@ components:
14137
14407
  required: [type, inputId]
14138
14408
  properties:
14139
14409
  type: { const: human_input }
14140
- inputId: { type: string, description: 'Stable per-input id (also the durable steering queue idempotency key).' }
14410
+ inputId:
14411
+ type: string
14412
+ description: >
14413
+ Stable per-input id (also the durable steering queue idempotency key). server >= 7.113.0 (wire VALUE change): for a LIVE steer
14414
+ it is the receipt's `messageId` verbatim — keyless steers included (<= 7.112.0: an engine-minted uuid whenever no
14415
+ `Idempotency-Key` was sent); with a key it is `idem-<sha256[:32]>` on both the live and the parked leg. 🚧 A keyless PARKED
14416
+ steer is the exception (contract §19): the server passes no `inputId` with the parked entry, so this id does not join the
14417
+ receipt's `messageId`.
14141
14418
  sessionSeq: { type: integer, description: 'Leg-scoped monotone sequence of human inputs.' }
14142
14419
  carrier: { type: string, description: 'Which carrier delivered it (steer / objective / a source word). Open on read.' }
14143
14420
  source: { type: string, description: 'objective | steer | next_turn | wake | external | system. Open on read.' }
@@ -18074,7 +18351,18 @@ components:
18074
18351
  taskId: { type: string }
18075
18352
  status: { type: string, description: 'The run status the server saw at delivery time (running | suspended | a terminal status).' }
18076
18353
  delivery: { type: string, enum: [applied, queued, parked_for_wake] }
18077
- messageId: { type: string, description: 'Server-minted, stable across all three legs.' }
18354
+ messageId:
18355
+ type: string
18356
+ description: >
18357
+ Server-minted, stable across all three legs (`idem-<sha256[:32]>` when an `Idempotency-Key` was sent, else a fresh uuidv7).
18358
+ server >= 7.113.0 (wire VALUE change, no new key): on the LIVE leg (`delivery: "applied"`) the server ALWAYS hands this
18359
+ same value to the engine as the steer's `inputId` — keyless steers included (<= 7.112.0 passed it only when a key was sent,
18360
+ so a keyless live steer's `human_input.inputId` was an engine-minted uuid that never joined this receipt). So the
18361
+ `human_input` frame of that steer (`delivery: "applied"` / `"queued"`) carries `inputId === messageId`, and the user-input
18362
+ acceptance ledger (`userInputUndrained[].messageId`) is keyed by the same value. The PARKED legs: with a key,
18363
+ `inputId === messageId` as before; 🚧 a KEYLESS parked steer (`delivery: "queued"` / `"parked_for_wake"`) is the documented
18364
+ exception (contract §19) — the server passes no `inputId` with the parked queue entry, so this `messageId` is never that entry's
18365
+ `inputId` and the later `human_input` frame does NOT join it.
18078
18366
  priority: { $ref: '#/components/schemas/SteerPriority' }
18079
18367
  note: { type: string, description: 'Human-readable note on the two parked legs; absent on `applied`.' }
18080
18368
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "15.4.0",
3
+ "version": "16.0.1",
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",
@@ -52,6 +52,6 @@
52
52
  "prepublishOnly": "rm -f tsconfig.tsbuildinfo && npm run build && npm run typecheck --workspaces=false --prefix ../.. && npm test --workspaces=false --prefix ../.. && npm run prepack"
53
53
  },
54
54
  "devDependencies": {
55
- "@sema-agent/settings-schema": "^6.4.0"
55
+ "@sema-agent/settings-schema": "^7.0.1"
56
56
  }
57
57
  }