@sema-agent/sdk 20.0.2 → 21.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.
Files changed (41) hide show
  1. package/README.md +83 -1
  2. package/dist/errors.d.ts +7 -1
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +7 -1
  5. package/dist/errors.js.map +1 -1
  6. package/dist/events.d.ts +61 -3
  7. package/dist/events.d.ts.map +1 -1
  8. package/dist/events.js.map +1 -1
  9. package/dist/idempotency.d.ts +4 -0
  10. package/dist/idempotency.d.ts.map +1 -1
  11. package/dist/idempotency.js +4 -0
  12. package/dist/idempotency.js.map +1 -1
  13. package/dist/index.d.ts +5 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/resources/approvals.d.ts +6 -3
  18. package/dist/resources/approvals.d.ts.map +1 -1
  19. package/dist/resources/approvals.js +2 -1
  20. package/dist/resources/approvals.js.map +1 -1
  21. package/dist/resources/assistant.d.ts +1 -1
  22. package/dist/resources/assistant.js +1 -1
  23. package/dist/resources/models.d.ts +3 -1
  24. package/dist/resources/models.d.ts.map +1 -1
  25. package/dist/resources/models.js +3 -1
  26. package/dist/resources/models.js.map +1 -1
  27. package/dist/resources/session-sync.d.ts +1 -1
  28. package/dist/resources/tasks.d.ts +7 -1
  29. package/dist/resources/tasks.d.ts.map +1 -1
  30. package/dist/resources/tasks.js +7 -1
  31. package/dist/resources/tasks.js.map +1 -1
  32. package/dist/resources/tool-approvals.d.ts +15 -1
  33. package/dist/resources/tool-approvals.d.ts.map +1 -1
  34. package/dist/resources/tool-approvals.js +8 -0
  35. package/dist/resources/tool-approvals.js.map +1 -1
  36. package/dist/types.d.ts +184 -65
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +5 -8
  39. package/dist/types.js.map +1 -1
  40. package/openapi.yaml +264 -57
  41. package/package.json +1 -1
package/openapi.yaml CHANGED
@@ -423,7 +423,7 @@ paths:
423
423
  prepare-time refusal code that HAS a status in the server's per-code triage table (`resume_at.*` — `resume_at.not_found` is 404, the rest 422 —, governance codes,
424
424
  `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported` / `rewind_snapshot.unresolvable`, the
425
425
  registered `config.*` codes incl. the three roster-bound ones and — server >= 7.116.0 — `config.invalid_checkpoint_store` (400) and
426
- `config.user_invoked_skill_unknown` (400: top-level `userInvokedSkills` named a skill this run does not carry) and — server >= 7.117.0 — `config.image_invalid` (400: an inline image failed the engine's byte admission; `error` starts with `TaskSpec.images: `), `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
426
+ `config.user_invoked_skill_unknown` (400: top-level `userInvokedSkills` named a skill this run does not carry) and — server >= 7.117.0 — `config.image_invalid` (400: an inline image failed the engine's byte admission; `error` starts with `TaskSpec.images: `) and — server >= 7.126.0 — `config.tool_face_control` (400: a name in `allowTools` / `excludeTools` that no tool of this run answers to, `allowTools: ["mcp__*"]`, `excludeTools: ["*"]`, or an `excludeTools` entry that reaches a protocol tool this leg depends on; the engine rejects before the run starts, never silently ignoring the name; `error` is the engine's sentence naming the entry, its list and why), `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
427
427
  200 with a failed body) answers its 4xx / 5xx ERROR BODY only when the whole submission did ZERO WORK (cumulative
428
428
  `stats.turns === 0` AND `stats.tokens === 0` — the common case: nothing billed, not idempotency-cached). When a
429
429
  `verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
@@ -435,6 +435,8 @@ paths:
435
435
  in `result` (a nested `TaskResult`), and `wiringManifest` / `notice` are absent. A same-key duplicate that is still IN FLIGHT in the same process is merged
436
436
  instead and gets the first answer's bytes (no `replay` key). Only a submit that builds a row can be replayed this way (a run store AND a `sessionId`);
437
437
  otherwise a settled key re-runs. A key whose row the caller cannot read answers 409 `conflict.run_exists`.
438
+ 🔴 SAME KEY = SAME BODY (server >= 7.126.0, contract §0.7): resubmitting the same `Idempotency-Key` with a DIFFERENT request body is 409
439
+ `conflict.idempotency_payload_mismatch` — not replayed, not created, the row untouched. To retry, resend the SAME body; for a different request use a NEW key.
438
440
  parameters:
439
441
  - $ref: '#/components/parameters/IdempotencyKey'
440
442
  requestBody:
@@ -483,6 +485,12 @@ paths:
483
485
  or at a row that is not the one this submit's `taskId` names. The sentence is `a run already exists for this submission's taskId or Idempotency-Key`
484
486
  (the code is unchanged; only the sentence moved); nothing about the row is echoed (no cross-tenant oracle). Submit under a NEW key. On
485
487
  `POST /v1/tasks/stream`, once the stream is open the same refusal arrives as the `done` frame's `{status:"failed", errorCode, errorMessage}`.
488
+ `conflict.idempotency_payload_mismatch` (server >= 7.126.0, contract §0.7 "same request = same body"): the submit's `Idempotency-Key` is already bound to a run row you CAN read,
489
+ and the body of the request that bound it differs from this one (normalization: JSON keys recursively sorted, `taskId` removed, headers never count; a value, a nested value, an
490
+ extra key or a reordered array is a different request). Not replayed, not created, the row untouched; the body is `{ error, errorCode }` and echoes neither the key nor the row.
491
+ Resend the SAME body to retry, or use a NEW key for a different request. Only the key's own binding is compared (a hit by `taskId` alone, or a row created without a key, is not
492
+ body-compared). A2A `message/send` never meets it. On `POST /v1/tasks/stream` it is a clean JSON 409 when found at the door; once the stream is open it arrives as the `done` frame's
493
+ `{status:"failed", errorCode:"conflict.idempotency_payload_mismatch", errorMessage}`.
486
494
  content:
487
495
  application/json:
488
496
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -1917,7 +1925,7 @@ paths:
1917
1925
  # (declared 2026-07-31) errorCode "request.id_invalid" — the path sessionId must decode to a
1918
1926
  # canonical uuid of ANY version (`routes/sessions.ts`; server >= 7.125.0 — the ONE shape gate shared with sync / fork / policy;
1919
1927
  # <= 7.124.x accepted only uuidv7 here); a malformed id never reaches the store. Branch on the code, never the sentence
1920
- # (the sentence changed with the gate: "invalid session id (must be a canonical uuid)").
1928
+ # (the sentence changed with the gate, and again when the submit door joined it).
1921
1929
  $ref: '#/components/responses/BadRequest'
1922
1930
  '401': { $ref: '#/components/responses/Unauthorized' }
1923
1931
  '404': { $ref: '#/components/responses/NotFound' }
@@ -4278,9 +4286,10 @@ paths:
4278
4286
  description: >
4279
4287
  READ-ONLY catalog of `@mention`-pickable models. The user selects a model PER-TASK by typing `@<name>` in
4280
4288
  the objective body (the service allowlist-parses it; the client never re-decides). Envelope
4281
- `{ models, default, defaultModel, droppedEntries? }` — see `ModelsResponse` (`default` = the deployment default model id,
4289
+ `{ models, default, defaultModel, droppedEntries?, candidateRejected? }` — see `ModelsResponse` (`default` = the deployment default model id,
4282
4290
  display-only — the internal `default` alias is hidden; `defaultModel` = the same default as `{id, name?}` in one piece;
4283
- `droppedEntries` = server >= 7.123.0, the catalog entries the read leg dropped, in the same generation as `models`).
4291
+ `droppedEntries` = server >= 7.123.0, the catalog entries the read leg dropped, in the same generation as `models`;
4292
+ `candidateRejected` = server >= 7.126.0, present-iff the configuration in force is the fallback one because the candidate judge rejected the latest candidate).
4284
4293
  🔴 NON-SECRET: only name/id/provider/reasoning/vision + the E4/E7 capability fields — NEVER baseUrl/apiKey/
4285
4294
  headers (the service strips them). Fail-safe: on error, render an empty picker (never block).
4286
4295
  responses:
@@ -6933,6 +6942,8 @@ components:
6933
6942
  server >= 7.125.0 also: `conflict.run_exists` — on a submit (`POST /v1/runs`, `/v1/tasks`, `/v1/tasks/stream`), the `Idempotency-Key` /
6934
6943
  `body.taskId` points at a run the caller cannot read, or the two keys of one submit point at different rows. The sentence is
6935
6944
  `a run already exists for this submission's taskId or Idempotency-Key` (code unchanged, sentence new; nothing echoed). Submit again under a new key.
6945
+ server >= 7.126.0 also: `conflict.idempotency_payload_mismatch` — the same `Idempotency-Key` carried with a DIFFERENT request body than the one that bound it (contract §0.7:
6946
+ not replayed, not created, the row untouched; resend the SAME body to retry, a NEW key for a different request).
6936
6947
  content:
6937
6948
  application/json:
6938
6949
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -6996,13 +7007,12 @@ components:
6996
7007
  description: >
6997
7008
  Primary routing axis (one image serves many). Selects model/tools/skills/prompt. OPEN: a stock server's builtin
6998
7009
  table is `code` (factory default) / `default` / `code-review` / `scan` (SDK `KNOWN_SCENARIOS`, checked against the
6999
- published server's `buildScenarios()` table) plus `discuss` — still accepted today, deliberately not in
7000
- `KNOWN_SCENARIOS` because it sits inside core #1056's (7.35.0) retirement sweep (server S-630 ②) — and a deployment may
7001
- register more through the registry. A name
7010
+ published server's `buildScenarios()` table; servers <= 7.125.x also listed `discuss`, retired with the whole team-orchestration surface at
7011
+ 7.126.0, no alias) — and a deployment may register more through the registry. A name
7002
7012
  the caller SENDS that the deployment does not serve is a loud 400 `scenario_unknown` (server >= 5.1.0, the body
7003
7013
  carries the live vocabulary) — the silent fold to `default` exists only on the server-side ASSIGNMENT chain.
7004
7014
  Retired, no alias: `oa` (5.0.0), `autonomous` ([2400] CAPS-OPS-11), `team` (renamed `discuss`, server #273 / C-R14;
7005
- alias withdrawn at #342 — the discuss scenario's body key `team`, which picks the roster template, kept its name).
7015
+ alias withdrawn at #342).
7006
7016
  examples: ['code', 'default', 'code-review', 'scan']
7007
7017
 
7008
7018
  ScenarioDetail:
@@ -7130,8 +7140,8 @@ components:
7130
7140
  name: { type: string, description: 'the `subagent_type` the model passes to Task. Non-empty, ≤64 chars, UNIQUE per request.' }
7131
7141
  whenToUse: { type: string, description: 'when the model should delegate to this agent (shown in the Task tool''s agent list). ≤4096 chars.' }
7132
7142
  whenToUseLean: { type: string, description: 'compact variant of `whenToUse` for lean tool listings. ≤4096 chars.' }
7133
- allowTools: { type: array, items: { type: string }, description: 'allowlist of tool names. Omit ⇒ inherit ALL tools.' }
7134
- denyTools: { type: array, items: { type: string }, description: 'denylist (SUBTRACTIVE — applied after `allowTools`).' }
7143
+ allowTools: { type: array, items: { type: string }, description: 'allowlist of tool names. Omit ⇒ inherit ALL tools. server >= 7.126.0: the names are judged by the ENGINE, one selector shared with the root run''s `allowTools` / `excludeTools`; a name no tool answers to => the run is rejected with the terminal code `config.agent.unknown_tool` (not an HTTP code).' }
7144
+ denyTools: { type: array, items: { type: string }, description: 'denylist (SUBTRACTIVE — applied after `allowTools`). server >= 7.126.0: same engine-judged selector as `allowTools`; `Tool(content)` => the whole tool.' }
7135
7145
  skills:
7136
7146
  type: array
7137
7147
  description: 'skills preloaded into the agent.'
@@ -7174,10 +7184,9 @@ components:
7174
7184
  TaskRequest:
7175
7185
  type: object
7176
7186
  description: >
7177
- Common request body. `[additionalProperties]` mirrors the service passthrough — scenario-specific fields
7178
- (code-review's `repo`/`lenses`/`rounds`, etc.) ride here. NOTE (sdk 9.1.0): `council`/`debate` used to be
7179
- named here as passthrough examples, but the service declares them as TOP-LEVEL typed keys and reads them
7180
- on two seams outside the scenario builder, so they are declared properties below.
7187
+ Common request body. `[additionalProperties]` mirrors the service passthrough — scenario-specific fields (today only `repo`) ride here.
7188
+ server 7.126.0 (hard BREAKING, no alias) took the declared keys `council` / `debate` and the scenario passthrough keys `businessContext` / `lenses` / `rounds` / `team` out of the accepted set with the team-orchestration
7189
+ surface: sending one is 400 `request.body_shape` listing it in `unknownKeys`. The open `additionalProperties` here does NOT catch them — the server's 400 is the notice.
7181
7190
  required: [objective]
7182
7191
  additionalProperties: true
7183
7192
  properties:
@@ -7200,7 +7209,12 @@ components:
7200
7209
  scenario: { $ref: '#/components/schemas/Scenario' }
7201
7210
  sessionId:
7202
7211
  type: string
7203
- description: Continue a conversation (same sessionId across turns). Omit on first turn → service mints one.
7212
+ description: >
7213
+ Continue a conversation (same sessionId across turns). Omit on first turn → service mints one.
7214
+ server >= 7.126.0 (contract section 0.8): a caller-supplied session id has ONE shape law on every door — a canonical uuid of ANY version (lowercase 8-4-4-4-12 hex, version nibble 1-8; a client-preset v4 and the
7215
+ server-minted v7 both pass), the same judge `fork` / `delete` / `policy` / 2c sync use. Anything else is 400 `request.id_invalid` (branch on the CODE, never the sentence). A self-chosen name is not a
7216
+ session id any more (keep your own name-to-uuid mapping); the old "at most 64 characters" cap and its `request.field_invalid` refusal are gone; the empty string is NO LONGER "absent" (it is a non-uuid => 400;
7217
+ omit the key for a new session). Shape-only: it does not check that the session exists. A2A `contextId` rides this key and is judged by the same rule.
7204
7218
  requireExistingSession:
7205
7219
  type: boolean
7206
7220
  description: >
@@ -7306,23 +7320,6 @@ components:
7306
7320
  the 32-entry cap the surplus entries are DROPPED and named too — same disposition as a malformed
7307
7321
  entry, not a 400.
7308
7322
  items: { $ref: '#/components/schemas/A2aServerSpec' }
7309
- council:
7310
- type: boolean
7311
- description: >
7312
- The `code-review` scenario's EXPENSIVE tier — fan the review out to N parallel lenses plus an
7313
- arbiter (`run_council`) instead of the lead reviewing in-line. Read on TWO seams and both matter:
7314
- the scenario builder mounts the council tool, and spec resolution treats it as an EXPLICIT team
7315
- declaration (widening the task's wall-clock tenancy budget and suppressing the value router's
7316
- auto-escalation). Only a literal `true` counts on both seams. On other scenarios the first seam is
7317
- inert while the second still applies.
7318
- debate:
7319
- type: boolean
7320
- description: >
7321
- Run the L2 peer-debate rounds on top of `council`. 🔴 The two seams are ASYMMETRIC — this is not a
7322
- standalone switch: the scenario builder reads it only INSIDE the `council === true` branch (sending
7323
- `debate:true` alone produces no debate rounds), while the explicit-team predicate is
7324
- `council || debate` (sending it alone still widens the budget and suppresses auto-escalation).
7325
- Literal `true` only, same as `council`.
7326
7323
  resumeAt:
7327
7324
  type: string
7328
7325
  description: >
@@ -7801,7 +7798,23 @@ components:
7801
7798
  excludeTools:
7802
7799
  type: array
7803
7800
  items: { type: string }
7804
- description: Tool names to withhold from this task's roster.
7801
+ description: >
7802
+ Tool names to withhold from this task's roster. server >= 7.126.0 (engine 7.49.0, contract G.9e): read with the SAME grammar as `allowTools`; a name that
7803
+ NO tool answers to rejects the run before it starts with `config.tool_face_control` (sync leg 400, no token burned; streamed / background legs: a `failed` terminal with
7804
+ the same code) instead of being silently ignored, and `"*"` is rejected on this list too (unmount-everything is only `excludeAllTools`).
7805
+ allowTools:
7806
+ type: array
7807
+ items: { type: string }
7808
+ description: >
7809
+ (server >= 7.126.0, engine 7.49.0 #1623, contract G.9e) The run's AVAILABILITY SELECTOR, passed to the engine's `TaskSpec.allowTools` verbatim (the server reads nothing:
7810
+ no complement, no entry filtering, `[]` is not folded to absent, not mixed with `settings.permissions.allow`). Available tools = everything this run mounts (caller tools,
7811
+ hands, the engine's own tools, MCP / A2A, scenario and leg tools) ∩ `allowTools` − `excludeTools` (deny wins), computed by the engine once before the run starts.
7812
+ `["*"]` = no list at this layer (same as absent); `[]` = give nothing; an entry is a wire name or alias, `Tool(content)` (the whole tool), `mcp__srv` / `mcp__srv__*` /
7813
+ a tool-segment wildcard (`mcp__*` is rejected on this list). A leg's protocol tools (a delegated child's `ReportBlocked`, …) need no mention. A name that NO tool answers to
7814
+ => `config.tool_face_control` (sync leg 400, no token burned; streamed / background legs: a `failed` terminal with the same code). Wrong shape (not a string array) =>
7815
+ 400 `request.field_invalid`, the same gate as `excludeTools` / `deferTools`. An engine older than 7.49.0 rejects loudly (400 `request.field_invalid` naming `allowTools`) —
7816
+ never silently dropped (dropping a narrowing key widens). No capability bit: probe by key closure (<= 7.125.x answers `400 request.body_shape` with the name in
7817
+ `unknownKeys`). The A2A inbound face (`message/send`) does not carry this key.
7805
7818
  excludeAllTools:
7806
7819
  type: boolean
7807
7820
  enum: [true]
@@ -8040,6 +8053,85 @@ components:
8040
8053
  🔴 On the wire it carries NO `token` — the same withholding rule as the top-level `paused` arm
8041
8054
  (see `PausedCause`); `gate` / `checkpointId` are present as usual.
8042
8055
  Absent on every failure the engine itself assembles.
8056
+ detail:
8057
+ allOf: [{ $ref: '#/components/schemas/TerminalFailureDetail' }]
8058
+ description: >
8059
+ The machine-readable account of the failure beside `code` (server >= 7.110.0 carried the `provider_rejected` arm; server >= 7.126.0 / core 7.49.0 it is a CLOSED union branching on `kind`,
8060
+ three arms — see `TerminalFailureDetail`). Branch on `detail.kind`, never on `message`; new members join with a core minor, so a branch MUST keep a default arm. Present only when the arm that
8061
+ named this result has such a fact to project; absent otherwise (absence is not an "older version" signal). The same object crosses three faces: the synchronous 200 body, the stream's `done.result`,
8062
+ `GET /v1/runs/{taskId}` -> `result`.
8063
+
8064
+ TerminalFailureDetail:
8065
+ description: >
8066
+ `TerminalCause_failed.detail` (contract section 5.0a; core `TerminalFailureDetail`) — a CLOSED union branching on `kind`, three arms as of server 7.126.0. An UNKNOWN `kind` (a newer engine) is passed through
8067
+ by the server with every string leaf redacted, never dropped or reshaped: a consumer treats it as the default arm.
8068
+ oneOf:
8069
+ - { $ref: '#/components/schemas/TerminalProviderRejectedDetail' }
8070
+ - { $ref: '#/components/schemas/TerminalCodedDetail' }
8071
+ - { $ref: '#/components/schemas/TerminalModelOutputInvalidDetail' }
8072
+
8073
+ TerminalProviderRejectedDetail:
8074
+ type: object
8075
+ additionalProperties: false
8076
+ required: [kind, status]
8077
+ description: >
8078
+ The model provider REFUSED the request with a 4xx and that refusal is what named this result. 5xx, connect / stream failures, an in-band error inside a 200, a lock conflict, a request-build guard and every non-provider
8079
+ failure carry no detail. 401 / 403 / 429 carry it too: to tell "the user can edit and resend", read `status` (or `code`), not whether `detail` is present.
8080
+ properties:
8081
+ kind: { const: provider_rejected }
8082
+ status: { type: integer, minimum: 400, maximum: 499, description: 'The refusal''s HTTP status (same value as `apiFailure.status`).' }
8083
+ code: { type: string, description: 'The provider''s own error word (`error.code`, else `error.type`); absent when the provider gave none or it is out of shape (1–128 visible ASCII).' }
8084
+ providerCondition:
8085
+ type: string
8086
+ description: 'server >= 7.126.0 (core 7.49.0 #1454): what the refusal says about the account — the engine''s closed word (`quota_exhausted | throttled | overloaded | unknown | stop_other`), passed through verbatim; read as an OPEN set. The terminal reading of the verdict the call''s retry frames carried (`status.providerCondition`).'
8087
+ providerConditionSource: { type: string, description: 'server >= 7.126.0: the evidence `providerCondition` rests on (`unified_header | provider_code | status`); present exactly when it is; read as an OPEN set.' }
8088
+ providerResetAtMs: { type: number, description: 'server >= 7.126.0: epoch milliseconds when the window resets, carried beside a `quota_exhausted` condition.' }
8089
+ route:
8090
+ allOf: [{ $ref: '#/components/schemas/TerminalRequestRoute' }]
8091
+ description: 'server >= 7.126.0: the route the refused request took, in KNOB words only (never a key value or a URL).'
8092
+
8093
+ TerminalRequestRoute:
8094
+ type: object
8095
+ additionalProperties: false
8096
+ required: [credentialSource, root]
8097
+ description: 'server >= 7.126.0 — `TerminalProviderRejectedDetail.route` (core `RequestRoute`): the route a refused request took, in KNOB words only (never a key value or a URL). All four words are read as OPEN sets.'
8098
+ properties:
8099
+ credentialSource: { type: string, description: 'Where the credential came from: `per-model | deployment-config | absent`.' }
8100
+ carrier: { type: string, description: 'How it rode: `apiKey | header`; absent exactly when `credentialSource` is `absent`.' }
8101
+ carrierBag: { type: string, description: 'Which header bag the auth header came from: `options | model | config`.' }
8102
+ root: { type: string, description: 'Which knob named the request''s root: `model | deployment | lane-default`.' }
8103
+
8104
+ TerminalCodedDetail:
8105
+ type: object
8106
+ additionalProperties: false
8107
+ required: [kind, detail]
8108
+ description: >
8109
+ server >= 7.126.0 — the run threw a coded error (this result''s `terminal.code`) whose throw site also stated a structured `detail` (for example a checkpoint error''s `{ reason }`). Present only when that error carried a
8110
+ plain-object `detail` with at least one projectable member.
8111
+ properties:
8112
+ kind: { const: coded }
8113
+ detail:
8114
+ type: object
8115
+ description: 'The thrown error''s `detail`: a JSON-plain snapshot (strings, finite numbers, booleans, null, arrays / plain objects of those; bounded). A member under a credential name stands as `"[redacted]"`; every string leaf passes the error-sentence redaction (credential shapes masked, paths kept).'
8116
+ additionalProperties: true
8117
+
8118
+ TerminalModelOutputInvalidDetail:
8119
+ type: object
8120
+ additionalProperties: false
8121
+ required: [kind, cause]
8122
+ description: 'server >= 7.126.0 — the model''s final turn failed because its tool calls could not be executed. Same words as the `task.tool_turn_incomplete` notice.'
8123
+ properties:
8124
+ kind: { const: model_output_invalid }
8125
+ cause: { type: string, enum: [truncated, unnamed], description: 'Why the calls were dropped: `truncated` = the arguments were cut before they parsed; `unnamed` = a call with no tool name.' }
8126
+ outputLimit:
8127
+ type: object
8128
+ additionalProperties: false
8129
+ required: [capStop]
8130
+ description: 'Only beside `truncated`: the output-cap facts the stream reported.'
8131
+ properties:
8132
+ capStop: { type: boolean }
8133
+ outputTokens: { type: number }
8134
+ capTokens: { type: number }
8043
8135
 
8044
8136
  TerminalCause_blocked:
8045
8137
  type: object
@@ -8405,14 +8497,15 @@ components:
8405
8497
  The `done.result` of a submit REFUSED after the SSE stream was already open (`POST /v1/tasks/stream`): the status line is gone, so the refusal travels as the
8406
8498
  stream's terminal frame in the triple `{status:"failed", errorCode, errorMessage}` — the same code and sentence the JSON refusal body would have carried
8407
8499
  (server `src/http/routes/tasks.ts`). Two producers: (1) server >= 7.125.0 — the submit's key (or `body.taskId`) turned out, only at row-creation time, to point at a run row
8408
- the caller cannot read or that is not the one this submit names: `errorCode: "conflict.run_exists"` (contract §0.7; the frame carries `replay: true`); (2) a device-placement
8500
+ the caller cannot read or that is not the one this submit names: `errorCode: "conflict.run_exists"` (contract §0.7; the frame carries `replay: true`); server >= 7.126.0 adds
8501
+ `errorCode: "conflict.idempotency_payload_mismatch"` (the same key, found at row-creation time bound by a request with a different body; same projection); (2) a device-placement
8409
8502
  refusal (the placement error body's code and sentence). It is NOT an engine result and NOT `ActiveRunConflictDoneResult` (that one is `conflict.session_active_run` and requires
8410
8503
  `activeTaskId`). Branch on `errorCode`; the sentence is for people.
8411
8504
  additionalProperties: false
8412
8505
  required: [status, errorCode, errorMessage]
8413
8506
  properties:
8414
8507
  status: { const: failed }
8415
- errorCode: { type: string, description: 'Open string: `conflict.run_exists` (server >= 7.125.0) or the device-placement refusal code.' }
8508
+ errorCode: { type: string, description: 'Open string: `conflict.run_exists` (server >= 7.125.0), `conflict.idempotency_payload_mismatch` (server >= 7.126.0) or the device-placement refusal code.' }
8416
8509
  errorMessage: { type: string, description: 'The refusal sentence (display only — never match on it).' }
8417
8510
 
8418
8511
  RunReceipt:
@@ -8446,22 +8539,48 @@ components:
8446
8539
  WorkspaceSyncCapability:
8447
8540
  type: object
8448
8541
  description: >
8449
- server >= 7.125.0 (contract appendix B.1 / B.1a) — the `GET /v1/capabilities` `workspaceSync` segment: EXACTLY two keys, key-for-key the server's `src/workspace-space.ts`
8450
- `WorkspaceSyncCapability`. Both are ALWAYS-PRESENT members of the object (B.1a); the object itself is present-iff (see `Capabilities.workspaceSync`).
8542
+ server >= 7.125.0 (contract appendix B.1 / B.1a) — the `GET /v1/capabilities` `workspaceSync` segment, key-for-key the server's composite member table
8543
+ `workspaceSync: [persist, keep, timeoutMs, cache]`. server >= 7.126.0: the members are PRESENT INDEPENDENTLY, each by its own facility — `persist` / `keep` / `timeoutMs` are
8544
+ present-iff the SNAPSHOT STORE (the k8s lane AND the three `MINIO_*` keys; in 7.125.0 `persist` / `keep` were always-present members of the object and `timeoutMs` did not exist, so a 7.125.x body is `{ persist, keep }` — which this schema still accepts: only `persist` <-> `keep` are bound to each other, and `timeoutMs`, when present, needs both); `cache` is present-iff the BUILD-CACHE VOLUME is on
8545
+ (the k8s lane AND `WORKSPACE_CACHE=volume`; independent of the snapshot store). The object itself is present-iff ANY member is present (see `Capabilities.workspaceSync`), so a deployment with the cache
8546
+ volume on but no snapshot store answers an object with only `cache`: "the object is present" no longer implies `persist` is present. Read cross-run retention as `persist === "space"`.
8451
8547
  additionalProperties: false
8452
- required: [persist, keep]
8548
+ minProperties: 1
8549
+ dependentRequired:
8550
+ persist: [keep]
8551
+ keep: [persist]
8552
+ timeoutMs: [persist, keep]
8453
8553
  properties:
8454
8554
  persist:
8455
8555
  type: string
8456
8556
  enum: [space, off]
8457
8557
  description: >
8458
- The operator knob `WORKSPACE_PERSIST` (default `off` — persistence is opt-in), a TWO-WORD closed set. `space` = the operator switched it on: the k8s `/workspace` persists across runs per session occupant. `off` = the snapshot
8459
- store IS wired but persistence is not switched on (the default, or the operator switched it off): a fresh workspace every run — NOT the same as the whole object being absent (absent = no such facility on this deployment).
8558
+ The operator knob `WORKSPACE_PERSIST`, a TWO-WORD closed set, DEFAULT `space` (server >= 7.126.0; the 7.125.0 default was `off`). `space` = the k8s `/workspace` persists across runs per session occupant. `off` = the snapshot
8559
+ store IS wired but the operator switched persistence off: a fresh workspace every run — NOT the same as the member being absent (absent = no snapshot store on this deployment). Present-iff the snapshot store; moves with `keep`.
8460
8560
  keep:
8461
8561
  type: integer
8462
8562
  minimum: 1
8463
8563
  maximum: 50
8464
- description: 'Snapshots kept per space (the session occupant) (`WORKSPACE_SNAPSHOT_KEEP`, an integer in [1, 50], default 3); older ones are deleted after each successful save.'
8564
+ description: 'Snapshots kept per space (the session occupant) (`WORKSPACE_SNAPSHOT_KEEP`, an integer in [1, 50], default 3); older ones are deleted after each successful save. Present-iff the snapshot store; moves with `persist`.'
8565
+ timeoutMs:
8566
+ type: integer
8567
+ minimum: 30000
8568
+ maximum: 3600000
8569
+ description: 'server >= 7.126.0 — the snapshot time bound (`WORKSPACE_SNAPSHOT_TIMEOUT_MS`, milliseconds, an integer in [30000, 3600000], default 300000): the wall-clock bound of the end-of-run upload and of the restore when the sandbox connects, and the longest the next run waits for the previous run to finish saving. A shell tells the user the "snapshot bound" from it. Present-iff the snapshot store (and, when present, `persist` / `keep` are present too); absent on server 7.125.x.'
8570
+ cache:
8571
+ allOf: [{ $ref: '#/components/schemas/WorkspaceCacheCapability' }]
8572
+ description: >
8573
+ server >= 7.126.0 — the per-space BUILD-CACHE VOLUME (`WORKSPACE_CACHE=volume`): one cache volume per space (session occupant) mounted at `mount` OUTSIDE the workspace (never in the snapshot, disposable), the tool caches
8574
+ (Gradle / Maven / npm / pip / uv / Go; Rust is not covered) are pointed there through pod env. A run that cannot get the volume still starts without it (slower builds, no failure); delegated children / background agents
8575
+ do not mount it. Present-iff the cache volume is on, independent of the snapshot store.
8576
+ WorkspaceCacheCapability:
8577
+ type: object
8578
+ additionalProperties: false
8579
+ required: [mount, size]
8580
+ description: 'server >= 7.126.0 — `Capabilities.workspaceSync.cache`: the per-space build-cache volume (`WORKSPACE_CACHE=volume`), key-for-key the server''s `WorkspaceCacheCapability`.'
8581
+ properties:
8582
+ mount: { type: string, description: 'The absolute path of the cache volume inside the pod (default `/cache`, outside the workspace mount).' }
8583
+ size: { type: string, description: 'The capacity requested per volume, a k8s quantity string (default `20Gi`).' }
8465
8584
  StoreCapability:
8466
8585
  type: object
8467
8586
  description: >
@@ -10312,13 +10431,14 @@ components:
10312
10431
  server >= 7.125.0 (contract appendix B.1 / B.1a) — WORKSPACE KEPT ACROSS RUNS: the k8s lane's `/workspace` is stored in object storage per session OCCUPANT (the session incarnation: delete the session and re-register the same id = a NEW space, starting from an empty directory). Each run's end
10313
10432
  snapshots the workspace into `<prefix>/<incarnation>/space/` (a sub-prefix this lane owns; the park-suspend snapshots live outside it and are never pruned), moves the `space/latest` pointer and keeps only
10314
10433
  the newest `keep`; the next run's start (when it connects the sandbox) restores from `latest`. A replica that cannot read the session occupant neither restores nor saves for that run (an operator line) and does NOT fall back to the session id.
10315
- A shell / center renders "work files kept across turns" only when `persist` is `"space"` (the default is `off`; persistence is opt-in).
10434
+ A shell / center renders "work files kept across turns" only when `persist` is `"space"` (the default is `space` since server 7.126.0; it was `off` in 7.125.0) — read `persist`, not "the object is present".
10316
10435
  🔴 PRESENT-IFF — one of the three per-key exceptions on this surface (with `sealedKey` and `centerWiring`, contract appendix B.1): the facility = the k8s lane AND a snapshot store (the three
10317
- `MINIO_*` keys). ABSENT is a missing key, never `false` / `null` / `{}`, and means one of TWO things with the SAME action: an older server (< 7.125.0, does not know this bit), or a deployment that
10318
- is not the k8s lane / has no snapshot store (the same body's `version` tells them apart) — either way do NOT promise "last run's files are still there"; treat each run's workspace as an empty directory.
10319
- `persist: "off"` means the store is present and the capability is present but nothing is retained today (the default, or the operator switched it off) — different from absent. Covers `/workspace` only (process / memory state does not cross runs).
10320
- FAILURE IS LOUD: failing to read `latest` or the commit marker, a `latest` that points at a missing snapshot object, or a restore that fails midway fails THAT run's sandbox connect (never a silent empty
10321
- directory; a partial directory is not handed to tools and is not snapshotted back); a failed end-of-run snapshot puts the residual key `workspaceSnapshot: "failed"` on the fleet terminal `task` frame
10436
+ `MINIO_*` keys), OR (server >= 7.126.0) the k8s lane AND the build-cache volume on. ABSENT is a missing key, never `false` / `null` / `{}`, and means one of TWO things with the SAME action: an older server (< 7.125.0, does not know this bit), or a deployment that
10437
+ is not the k8s lane / has neither a snapshot store nor the cache volume (the same body's `version` tells them apart) — either way do NOT promise "last run's files are still there"; treat each run's workspace as an empty directory.
10438
+ `persist: "off"` means the store is present and the capability is present but the operator switched persistence off, so nothing is retained today — different from the member being absent. Covers `/workspace` only (process / memory state does not cross runs).
10439
+ FAILURE IS LOUD: failing to read `latest` or the commit marker, or a restore that fails midway (the snapshot is there but the download / unpack / time bound failed — the refusal names two exits: raise `WORKSPACE_SNAPSHOT_TIMEOUT_MS`, or run one turn with
10440
+ `WORKSPACE_PERSIST=off`) fails THAT run's sandbox connect (never a silent empty directory; a partial directory is not handed to tools and is not snapshotted back); a `latest` that points at a missing snapshot object (cleared by a bucket lifecycle rule)
10441
+ is, since server 7.126.0, an operator line plus deleting the dangling pointer, and that run starts from an empty directory (in 7.125.0 it failed the sandbox connect). A failed end-of-run snapshot puts the residual key `workspaceSnapshot: "failed"` on the fleet terminal `task` frame
10322
10442
  (`FleetTaskRow.workspaceSnapshot`), and the next run restores the PREVIOUS `latest`.
10323
10443
  background:
10324
10444
  type: object
@@ -11220,7 +11340,7 @@ components:
11220
11340
  [model, runsActive, tasks, tokensTotal, costUsd, costUsdByModel, costUnknown, unpricedCalls,
11221
11341
  taskDurationAvgSec, brainFirstTokenAvgMs, brainCallAvgMs, cacheHitRateAvg, promptCacheLowHit,
11222
11342
  rateLimited, costQuotaRejected, budgetExceeded, degraded, degenerate, memoryConsolidationOps,
11223
- planCacheRecurrence, cascade, verifications, councilRuns, toolErrors]
11343
+ planCacheRecurrence, cascade, verifications, toolErrors]
11224
11344
  additionalProperties: true
11225
11345
  properties:
11226
11346
  model: { type: string }
@@ -11254,7 +11374,6 @@ components:
11254
11374
  hitRate: { type: [number, 'null'] }
11255
11375
  cascade: { type: object, additionalProperties: { type: integer } }
11256
11376
  verifications: { type: object, additionalProperties: { type: integer } }
11257
- councilRuns: { type: object, additionalProperties: { type: integer } }
11258
11377
  toolErrors: { type: object, additionalProperties: { type: integer } }
11259
11378
  limitCeilingAfterAnswer:
11260
11379
  type: object
@@ -11331,6 +11450,38 @@ components:
11331
11450
  Upgrade window (as it is): an on-disk LKG written by server <= 7.122.x only recorded the dropped entries that had a name AND were
11332
11451
  enabled; while serving from it with the center still unreachable this key lists only those rows (possibly `[]`) and the first pull after the
11333
11452
  center is reachable fills it in. An unknown `reasonCode` word reads as "dropped, reason unknown".
11453
+ candidateRejected:
11454
+ allOf: [{ $ref: '#/components/schemas/CandidateRejected' }]
11455
+ description: >
11456
+ server >= 7.126.0, PRESENT-IFF: the conclusion of the most recent configuration candidate the candidate judge rejected as a whole.
11457
+ Present exactly when the configuration now in force is the fallback one (the on-disk LKG / the previously adopted candidate / env) BECAUSE the judge
11458
+ rejected the latest candidate; a later candidate judged clean, a deployment that never judged a rejection / has no configuration pipeline (pure env), and
11459
+ a dry-run posture all leave it ABSENT. Never minted as `null` / `[]` (unlike the always-present `droppedEntries`). So ABSENT does NOT mean "the
11460
+ configuration is clean" (it is also an earlier server): render the real cause only when the key is present; keep the old behaviour when absent.
11461
+ Same read as `models`: key present => this `models` is the fallback generation (none of the candidate's models are in it). No capability bit
11462
+ (the endpoint is always there; the presence of the key is the presence rule).
11463
+
11464
+ CandidateRejected:
11465
+ type: object
11466
+ additionalProperties: false
11467
+ required: [domains, at, source]
11468
+ description: >
11469
+ `ModelsResponse.candidateRejected` (server >= 7.126.0): the conclusion of the most recent configuration candidate the candidate judge
11470
+ rejected as a whole (one failing domain, or a malformed prompts plane, rejects the WHOLE candidate: no domain of it is applied).
11471
+ ZERO free text: no rejection sentence, no file path, no version number (those stay in the server log).
11472
+ properties:
11473
+ domains:
11474
+ type: array
11475
+ items: { type: string }
11476
+ description: 'The configuration domain names (the contract package''s domain keys, e.g. `mcp` / `models` / `prompts`) that failed the shape judgement; sorted, de-duplicated. An unknown word is rendered as-is.'
11477
+ at:
11478
+ type: string
11479
+ format: date-time
11480
+ description: 'ISO 8601: when THIS process''s judge judged this candidate (re-judging the same candidate with the same failing domains does not refresh it).'
11481
+ source:
11482
+ type: string
11483
+ enum: [local, remote]
11484
+ description: 'Which configuration source the candidate was read from: `local` = the local config directory, `remote` = the center. A shell says "edit your config file" vs "ask the administrator" from it.'
11334
11485
 
11335
11486
  DroppedEntry:
11336
11487
  type: object
@@ -12615,7 +12766,9 @@ components:
12615
12766
  description: 'server >= 7.102.0 (S-755 / cli L-695): WHO raised this ask, read once together with `frame.origin` when the entry is minted. The durable `pending[]` rows of the same response carry it top-level too — one reading for both row families. OPEN set (see AskOrigin).'
12616
12767
  fromSubagent: { type: boolean, enum: [true], description: 'The ask originates from a delegated subagent.' }
12617
12768
  originTaskId: { type: string, description: 'S-52 C2 (server ≥7.52, A-075.87): the delegated child taskId (= the child''s core runSourceTaskId, uuid) — correlates this live ask to the child run row. Minted by the SAME conditional spread as `fromSubagent`: the two keys are always both present or both absent (三键恒等亲证在案).' }
12618
- contentKind: { type: string, description: 'S-62 (server >= 7.6x): content-question kind — `content_ask` when the gated tool is AskUserQuestion (the SAME word as the two durable read faces and core''s summarize face). Absent = an ordinary tool ask. Display/triage only; it NEVER takes part in decision routing.' }
12769
+ approvalCard:
12770
+ allOf: [{ $ref: '#/components/schemas/ToolApprovalCardWord' }]
12771
+ description: 'server >= 7.126.0 (core 7.49.0 #1414): which card this ask renders on — the engine-minted card word, the SAME value as `frame.approvalCard` (the row reads the frame''s own seat, no second read), same layer and shape as the operator queue row''s `approvalCard`. It REPLACES `contentKind` (server <= 7.125.x derived it from the tool name; deleted at 7.126.0, no alias). ABSENT = the engine minted none. Display / triage only; it NEVER takes part in decision routing.'
12619
12772
  frame:
12620
12773
  allOf: [{ $ref: '#/components/schemas/ToolApprovalFrame' }]
12621
12774
  description: >-
@@ -12958,6 +13111,9 @@ components:
12958
13111
  origin:
12959
13112
  allOf: [{ $ref: '#/components/schemas/AskOrigin' }]
12960
13113
  description: 'server >= 7.102.0 (S-755 / cli L-695 / C-R111): WHO raised this ask — the park-row twin seat `PendingAction.tool_approval.origin` read through `readClosedWordSeat(…, "origin")` (core `isAskOrigin`). Same word as the live card frame and the inbox row. Absent = an old row (parked before core 7.5.0) or an out-of-set word — NOT "policy".'
13114
+ approvalCard:
13115
+ allOf: [{ $ref: '#/components/schemas/ToolApprovalCardWord' }]
13116
+ description: 'server >= 7.126.0 (core 7.49.0 #1414): which card this row renders on — the engine-minted `gate.approvalCard` (closed eight-word set; the park-row twin seat reads the same word core''s `summarizeCheckpoint` projects onto `CheckpointSummary.approvalCard`; an out-of-set word reads as absent and is counted), the same word as the live frame / `livePending` row. It REPLACES `contentKind` (deleted at 7.126.0, no alias). ABSENT = the engine minted none: a row parked before engine 7.49.0 (core never derives a card from the tool name: no card = not a question, so an old question row carries no card word and is swept by the SLA at its deadline), a non-approval gate, or an older engine. Display / triage only; NEVER part of resume / gate / CAS; the inbox row does not carry it.'
12961
13117
  ruleOffersAbsence:
12962
13118
  allOf: [{ $ref: '#/components/schemas/RuleOffersAbsence' }]
12963
13119
  description: 'server >= 7.100.0 (S-700): WHY the park row offers no rule — the same narrow read and closed three-word set as the live card frame and `card_json`.'
@@ -13012,11 +13168,6 @@ components:
13012
13168
  (they carry a RiskDescriptor); ABSENT for resource_limit/needs_review/plan_review — UI tolerates undefined.
13013
13169
  spentMicroUsd: { type: number, description: 'cumulative spend on the suspend chain (when a resource ledger is attached).' }
13014
13170
  deadline: { type: integer, description: 'awaiting-human SLA deadline (epoch ms).' }
13015
- contentKind:
13016
- type: string
13017
- description: >
13018
- Open enum (known value `content_ask`) distinguishing WHAT is being asked about, for gates whose payload
13019
- is content rather than a tool call. Treat unknown values as opaque.
13020
13171
  sourceTaskId: { type: string, description: 'The task that produced this checkpoint (when distinct from sessionId).' }
13021
13172
  principal: { type: string, description: 'The submitting principal, when the deployment records one.' }
13022
13173
  createdAt: { type: integer, description: 'Creation time, epoch ms (NOTE — SessionRecord.createdAt is an ISO string).' }
@@ -13030,7 +13181,7 @@ components:
13030
13181
  ASSISTANT-WIRE-CONTRACT §2 — an inbox row (server >=3.4.0 EXPLICIT-projection form): the §1 mirror keys
13031
13182
  of a CheckpointSummary (token stripped; no rich per-tool fields) + two nullable enrichments.
13032
13183
  NARROWING RECORD (2026-07-30, board [2027]->[2029]): rows used to also carry
13033
- toolName/toolCallId/preview/principal/sourceTaskId/contentKind (handler spread the whole projection) and
13184
+ toolName/toolCallId/preview/principal/sourceTaskId (handler spread the whole projection) and
13034
13185
  this spec over-declared decide bindings that never appeared on inbox wire at all. Four client repos were
13035
13186
  structurally zero-consumers, so server 3.4.0 narrowed the wire and this schema follows. For the tool face
13036
13187
  and decide bindings use /v1/approvals (PendingCheckpoint); for task attribution use /v1/assistant/tasks.
@@ -13318,6 +13469,13 @@ components:
13318
13469
  decision: { type: string, enum: [approve, edit, reject] }
13319
13470
  editedPlan: { type: string, description: 'the operator''s revised plan — required iff decision==="edit".' }
13320
13471
  reason: { type: string }
13472
+ decidedBy:
13473
+ allOf: [{ $ref: '#/components/schemas/ApprovalDecidedBy' }]
13474
+ description: >
13475
+ server >= 7.126.0 (engine 7.49.0 gave the plan-review outcome a required decision-fact seat, the same seat as tool approvals) — WHO MADE THIS DECISION, the same closed set and the same single
13476
+ reading as the three tool-approval decision doors: absent => `person`; `policy` is accepted; a word outside the closed set (including `sla_timeout`, which only the server's own deadline sweep can say)
13477
+ => `400 request.field_invalid`; on a direct-door (D-G) deployment ANY value => `400 decided_by_not_in_proof` (the key is not in the decision proof's signed payload). Not echoed in the response. NO capability bit
13478
+ covers this door (`approvalDecidedBy` is >= 7.118.0 and does not cover plan_review): send it by server version; <= 7.125.x answers `400 request.body_shape` and lists it in `unsupportedKeys`.
13321
13479
  permissionModeAfter:
13322
13480
  type: string
13323
13481
  enum: [default, acceptEdits]
@@ -14721,6 +14879,13 @@ components:
14721
14879
  # 真发 `label`(core 展示名,与 tool_end 同待遇),spec 此前漏声明 —— 封闭前必须先补,否则真帧变假红。
14722
14880
  label: { type: string, description: 'core 展示名(缺席 ⇒ 用 toolName 渲染)。' }
14723
14881
  args: {}
14882
+ recovered:
14883
+ type: boolean
14884
+ enum: [true]
14885
+ description: >
14886
+ server >= 7.126.0 (core 7.49.0 `tool_start.recovered`, contract table B `recovered` row, passed through verbatim): present, always the literal `true`, when the engine CLOSED a call whose start
14887
+ frame was never emitted by minting this start frame after the fact (an interrupted run's reconciliation / a resumed process closing a call the dead process had announced), keeping "every
14888
+ `tool_end` is preceded by a `tool_start` of the same id". Absent on the loop's own announced start frames (the vast majority). Does NOT assert whether the tool executed — read the paired `tool_end.notExecuted`.
14724
14889
  eventId: { type: string }
14725
14890
  parentToolCallId: { type: string }
14726
14891
  sourceTaskId: { type: string }
@@ -15250,6 +15415,18 @@ components:
15250
15415
  # 🔴 census 轴一(2026-07-30)回填:`toolEndEventData`(trace/project.ts)真发 `label` 与
15251
15416
  # `structured`,spec 此前两者都漏 —— SDK 的 events.ts 早已声明,漂移只在 spec 这一侧。
15252
15417
  label: { type: string, description: 'core 展示名(同 tool_start.label)。' }
15418
+ notExecuted:
15419
+ type: boolean
15420
+ enum: [true]
15421
+ description: >
15422
+ server >= 7.126.0 (core 7.49.0, contract table B `notExecuted` row, passed through verbatim) — ABSENT IS NEVER "it ran": present, always the literal `true` (never `false`), when the engine has
15423
+ POSITIVE evidence the call was never executed; absent only means the engine has no such evidence (a call interrupted mid-execution, or one closed by the reconciliation on the next process start, does not carry it —
15424
+ telling the model "it did not run" there could invite a replay of a write that already took effect). Read it together with the paired `tool_start.recovered`.
15425
+ wireName:
15426
+ type: string
15427
+ description: >
15428
+ server >= 7.126.0 (core 7.49.0, contract table B `wireName` row, passed through verbatim) — the tool name this call resolved to on the LIVE roster (the name the gate judged). `toolName` stays the model's spelling (an alias /
15429
+ a retired name the roster still answers to). Present ONLY on a refused call (`gate.disposition.kind === "denied"`), and absent when the station that refused it did not know the roster name; per-tool refusal accounting reads this, not `toolName`.
15253
15430
  structured: {} # core 1.203 CC 卡片明细;与 output 同一 UNTRUSTED RAW 待遇(已 redactDeep),形状开放
15254
15431
  isError: { type: boolean }
15255
15432
  output: {} # NON-UNIFORM: string | (TextContent|ImageContent)[] | (truncated) string. Absent ⇒ no body.
@@ -15471,6 +15648,8 @@ components:
15471
15648
  maxRetries: { type: number, description: "retry budget of the lane in force (refused-connect short lane = 2)" }
15472
15649
  retryInMs: { type: number, description: "same number as retryInSec at ms precision (countdown rendering)" }
15473
15650
  errClass: { type: string, description: "connect_refused | transport | rate_limit | server | http | output_cap — retry-wait frames ONLY; never inferred from phase" }
15651
+ providerCondition: { type: string, description: "server >= 7.126.0 (core 7.49.0 #1454, passed through verbatim): what the provider said about the account / capacity — core's closed `PROVIDER_CONDITIONS`; a retry frame carries only the three retryable words `throttled | overloaded | unknown` (`quota_exhausted` / `stop_other` do not retry and ride the terminal `detail.providerCondition`). Presence law as `errorStatus`; read as an OPEN set (keep a default arm). ABSENT = not judged, read the same as `unknown` (back off, do not assert quota). Moves with `providerConditionSource`." }
15652
+ providerConditionSource: { type: string, description: "server >= 7.126.0 (core 7.49.0 #1454): the evidence the `providerCondition` verdict rests on — `unified_header | provider_code | status`; present exactly when `providerCondition` is; read as an OPEN set." }
15474
15653
  eventId: { type: string }
15475
15654
  parentToolCallId: { type: string }
15476
15655
  sourceTaskId: { type: string }
@@ -16230,6 +16409,12 @@ components:
16230
16409
  switch itself became the WORD `"disabled"` instead of `null` (a string is not nullish, so the ordinary
16231
16410
  coalesce is the whole resolution rule and no reader has to remember a null test). A consumer still
16232
16411
  matching `task_store_null` matches nothing.
16412
+ server 7.126.0 (the user-visible whitelist grows 35 -> 37 codes; contract appendix D.3): `task.tool_turn_incomplete` gains an optional `detail.outcome` (core 7.49.1) —
16413
+ `{cause, count, toolCallIds?, outcome?, sessionId, runId}`, `outcome` in {`terminal` (the dropped calls ended the run with `model_output_invalid`), `reissued` (the model was asked to reissue)}, present only when
16414
+ the turn's own message can say it, ABSENT = not said; two NEW codes (core 7.49.0, audience `user`, zero body text): `route.finish_reason_unknown` `{finishReason, sessionId, runId}` — the provider ended the turn
16415
+ with a finish reason the engine does not understand, so the text above may be cut short (`finishReason` = the provider's own word, an OPEN string, passed through verbatim; once per run per word); and
16416
+ `route.stream_end_ignored` `{statement, word, frames, sessionId, runId}` — the provider had already ended the turn and its stream sent more (`statement` in {`ending`, `error`, `content`}, `word` = the late first
16417
+ frame's ending reason / error word / content kind, `frames` = how many frames came after the end; the first ending counts, anything later never entered the turn; once per run per (statement, word)).
16233
16418
  A notice whose `detail.sessionId` is absent is HONESTLY NOT DELIVERED (the server never guesses a session),
16234
16419
  so the frame's absence does NOT mean "did not happen" — the operator-facing structured log always has the
16235
16420
  full family. `detail` is already redacted + size-bounded by the server; still treat it as external text.
@@ -16877,6 +17062,15 @@ components:
16877
17062
  deployment read the bit at all. Present ⇒ every automatic allowance (remembered rules,
16878
17063
  bypassPermissions posture) stands down.
16879
17064
  origin: { $ref: '#/components/schemas/AskOrigin' }
17065
+ approvalCard:
17066
+ allOf: [{ $ref: '#/components/schemas/ToolApprovalCardWord' }]
17067
+ description: >
17068
+ "tool_approval" only, ADDITIVE (server >= 7.126.0 / core 7.49.0 #1414, contract table B `approvalCard` row) — WHICH CARD THIS ASK RENDERS ON: the one word the engine minted at the gate
17069
+ (closed set `ToolApprovalCardWord`, eight words), passed through verbatim after a closed-set narrow read (an out-of-set word reads as absent). The ONLY card-word source — it REPLACES the
17070
+ second card word `contentKind` that server <= 7.125.x derived from the tool name (deleted at 7.126.0, no alias: a consumer reading `contentKind === "content_ask"` reads
17071
+ `approvalCard === "question"`). ABSENT = the engine minted none (a host-built ask / an older engine / a non-approval gate) — never `generic` by default. Display / triage only; it NEVER
17072
+ takes part in resume / gate / CAS. The same word rides the `livePending` row top level (= this frame's own value), the operator queue row and the `/v1/approvals/stream` `pending` /
17073
+ `live_pending` frames; the `card_json` card face and the `approval_request` frame's `card` do NOT carry it (`ApprovalCard` is `.strict()`).
16880
17074
  inputHasBidi:
16881
17075
  type: boolean
16882
17076
  enum: [true]
@@ -18062,7 +18256,17 @@ components:
18062
18256
  "the wait ran out with nobody answering" is said only by the server's own deadline sweep — and the old `human` / `timeout`
18063
18257
  words) ⇒ 400 before the decision. It REPLACES the 7.93.3–7.117.x key `settledBy` (deny-only, the same fact), deleted
18064
18258
  by server 7.118.0 with no alias: sending `settledBy` ⇒ 400 `request.body_shape` on all three doors.
18065
- `plan_review` does not take it (400 `request.body_shape`).
18259
+ `plan_review` (`POST /v1/assistant/tasks/{taskId}/plan_review`): server >= 7.126.0 takes it too, with the same reading and the same refusals; server <= 7.125.x answers 400 `request.body_shape`
18260
+ (listed in `unsupportedKeys`), and `approvalDecidedBy` does not cover that door — send it by server version.
18261
+
18262
+ ToolApprovalCardWord:
18263
+ type: string
18264
+ enum: [file-edit, file-write, notebook-edit, shell, fetch, plan, question, generic]
18265
+ description: >
18266
+ server >= 7.126.0 (core 7.49.0 `TOOL_APPROVAL_CARDS`, verbatim and in order; contract table B `approvalCard` row) — WHICH CARD an approval ask renders on: the one word the engine mints at the gate, the
18267
+ ONLY card-word source (it replaces the tool-name-derived `contentKind`). `question` = a question put to the user (the engine's reserved question tool — not a side-effecting tool; a consumer that read
18268
+ `contentKind === "content_ask"` reads `approvalCard === "question"`); `generic` = a card with no dedicated renderer. CLOSED set; new words join with a core minor, so a switch on it keeps a default arm.
18269
+ At expiry a parked `question` gate is voided and every other approval gate goes to the SLA refusal sweep (the judge is the engine's `facetOf(gate)`).
18066
18270
 
18067
18271
  ApprovalReach:
18068
18272
  type: string
@@ -18741,6 +18945,9 @@ components:
18741
18945
  origin:
18742
18946
  allOf: [{ $ref: '#/components/schemas/AskOrigin' }]
18743
18947
  description: '`pending` / `live_pending` frames (server >= 7.102.0 / S-755) — see PendingCheckpoint.origin / LivePendingRow.origin. OPEN set.'
18948
+ approvalCard:
18949
+ allOf: [{ $ref: '#/components/schemas/ToolApprovalCardWord' }]
18950
+ description: '`pending` / `live_pending` frames (server >= 7.126.0 / core 7.49.0 #1414) — see PendingCheckpoint.approvalCard / LivePendingRow.approvalCard. Replaces `contentKind`.'
18744
18951
  ruleOffersAbsence:
18745
18952
  allOf: [{ $ref: '#/components/schemas/RuleOffersAbsence' }]
18746
18953
  description: '`pending` frames only (server >= 7.100.0 / S-700) — see PendingCheckpoint.ruleOffersAbsence.'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "20.0.2",
3
+ "version": "21.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",