@sema-agent/sdk 17.2.0 → 18.1.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 (48) hide show
  1. package/README.md +163 -0
  2. package/dist/client.d.ts +3 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +4 -0
  5. package/dist/client.js.map +1 -1
  6. package/dist/errors.d.ts +19 -0
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +19 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/events.d.ts +14 -2
  11. package/dist/events.d.ts.map +1 -1
  12. package/dist/events.js.map +1 -1
  13. package/dist/health.d.ts +95 -31
  14. package/dist/health.d.ts.map +1 -1
  15. package/dist/health.js.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/resources/admin.d.ts +65 -0
  21. package/dist/resources/admin.d.ts.map +1 -0
  22. package/dist/resources/admin.js +27 -0
  23. package/dist/resources/admin.js.map +1 -0
  24. package/dist/resources/approvals.d.ts +6 -3
  25. package/dist/resources/approvals.d.ts.map +1 -1
  26. package/dist/resources/approvals.js +4 -2
  27. package/dist/resources/approvals.js.map +1 -1
  28. package/dist/resources/assistant.d.ts +5 -4
  29. package/dist/resources/assistant.d.ts.map +1 -1
  30. package/dist/resources/assistant.js +5 -4
  31. package/dist/resources/assistant.js.map +1 -1
  32. package/dist/resources/leader.d.ts +7 -1
  33. package/dist/resources/leader.d.ts.map +1 -1
  34. package/dist/resources/leader.js +7 -1
  35. package/dist/resources/leader.js.map +1 -1
  36. package/dist/resources/ops.d.ts +2 -1
  37. package/dist/resources/ops.d.ts.map +1 -1
  38. package/dist/resources/ops.js +2 -1
  39. package/dist/resources/ops.js.map +1 -1
  40. package/dist/resources/rules.d.ts +5 -4
  41. package/dist/resources/rules.d.ts.map +1 -1
  42. package/dist/resources/rules.js.map +1 -1
  43. package/dist/settings.d.ts +2 -2
  44. package/dist/types.d.ts +68 -17
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js.map +1 -1
  47. package/openapi.yaml +251 -37
  48. package/package.json +2 -2
package/openapi.yaml CHANGED
@@ -184,6 +184,10 @@ tags:
184
184
  description: v2 leader pipeline (gated; E2B + git push). NOT the default door-B brain.
185
185
  - name: metrics
186
186
  description: Worker health/cost (ops; uses metricsToken, not a principal).
187
+ - name: admin
188
+ description: >
189
+ Operator-only `/v1/admin/*` actions on THIS engine instance. Today: the drain endpoint (server >= 7.121.0 contract
190
+ body `DrainRequest` / receipt `DrainReceipt`, owned by `@sema-agent/settings-schema` `./fleet`).
187
191
 
188
192
  paths:
189
193
 
@@ -3230,7 +3234,9 @@ paths:
3230
3234
  Frames: `meta` ({type,version,mode:"approvals-delta",pollMs}), then `synced` ({type,count}) once the
3231
3235
  first full snapshot has been emitted, then `pending`/`resolved` deltas; plus `heartbeat` ({type}) on the
3232
3236
  keep-alive tick and a terminal `error` ({type,errorCode:"STREAM_MAX_DURATION",…}) when the 15-min cap
3233
- fires. (Corrected 2026-07-31: this list used to name only meta/pending/resolved — a consumer written to
3237
+ fires — or, server >= 7.122.0, `error` with `errorCode:"draining"` at the moment the instance enters drain (same code and
3238
+ sentence as the billable-submit 503 `draining`; the server closes the stream right after the frame, reconnect to the
3239
+ replacement instance). (Corrected 2026-07-31: this list used to name only meta/pending/resolved — a consumer written to
3234
3240
  it silently drops three frame kinds it really receives.) Each data payload mirrors its SSE event name in
3235
3241
  `data.type` — proxy-safe dispatch. Cross-replica by
3236
3242
  construction (polls the SHARED checkpoint table). 15-min cap + heartbeats; a DB blip retries, never
@@ -3731,8 +3737,12 @@ paths:
3731
3737
  `ErrorResponse`-shaped. Codes: "request.body_shape" (bad/missing `decision`, OR an unrecognised
3732
3738
  top-level key — server >= 7.86.0 / S-438 key closure, the body carries `unknownKeys` /
3733
3739
  `unsupportedKeys`) · "request.field_conflict" (`edit` without a non-empty `editedPlan`;
3734
- `editedPlan` present on approve/reject; `permissionModeAfter` sent with `edit`/`reject`;
3735
- `permissionModeAfter` on a task that was not submitted with `permissionMode:"plan"`) ·
3740
+ `editedPlan` present on approve/reject; `permissionModeAfter` sent with `reject`;
3741
+ `permissionModeAfter` on a task whose stored permission mode is not `plan`) — match these two by CODE;
3742
+ server >= 7.122.0: `approve` and `edit` both carry the plan out of plan mode and both take the key, and a
3743
+ run's mode word has ONE source, the stored submit body (an operator `AUTONOMY=plan` task is stored as `plan`
3744
+ and takes the key); <= 7.121.x only `approve` took it and the second conflict read "not submitted with
3745
+ `permissionMode:"plan"`" ·
3736
3746
  "request.field_invalid" (non-string `reason`; `permissionModeAfter` outside the two-word closed
3737
3747
  set — zero widening arm) · "request.path_malformed" (a bad percent-encoded path segment —
3738
3748
  server >= 7.86.0 / S-430 turned that family from 500 `internal.error` into a 400 across 16 doors).
@@ -4664,7 +4674,8 @@ paths:
4664
4674
  (`LEADER_ENABLED` off) the answer depends on the server: server >= 7.116.0 mounts the route unconditionally and answers
4665
4675
  **501 `capability.leader_endpoint_required`** (new code; under multi-tenant the identity gate's 401 still comes first);
4666
4676
  <= 7.115.x did not register the route at all → the generic **404** `not_found.route`. Detect "leader unavailable" by
4667
- `errorCode` (or the `capabilities.leader` bit, which reads the same fact as the gate), not by the status alone.
4677
+ `errorCode` (or the `capabilities.leader` bit — server >= 7.122.0 also reads false on a deployment whose operator runs `AUTONOMY=plan`, where a submit answers
4678
+ 422 `request.autonomy_lane_refused` instead; see `Capabilities.leader`), not by the status alone.
4668
4679
 
4669
4680
 
4670
4681
  🔴 IDEMPOTENCY (server >= 7.24.0): this POST now consumes the `Idempotency-Key` header — the header is
@@ -4713,6 +4724,20 @@ paths:
4713
4724
  content:
4714
4725
  application/json:
4715
4726
  schema: { $ref: '#/components/schemas/ErrorResponse' }
4727
+ '422':
4728
+ description: >-
4729
+ errorCode "request.autonomy_lane_refused" (server >= 7.122.0) — the deployment runs the operator `AUTONOMY=plan`: every task starts
4730
+ in plan mode and leaves it only when a person approves its plan through plan review, and a leader task's workers have no plan
4731
+ review channel, so they could neither write nor hand their plan to anyone who can approve it. REFUSED at the door, NO run created;
4732
+ a replay with the same `Idempotency-Key` gets the same 422 (the idempotency key is not consumed). The `capabilities.leader` bit
4733
+ reads false on such a deployment (the same judgement as this refusal) — read it, or `GET /v1/policy` `autonomy`, beforehand. Exits: submit the work as a run (`POST /v1/runs`, plan reviewed through
4734
+ `POST /v1/assistant/tasks/{taskId}/plan_review`), or ask the operator to set `AUTONOMY=read-only` or `ask` (leader tasks run
4735
+ there). Not the hosted-posture refusal (`request.host_posture_refused`): that one is the tenant axis, this one is the AUTONOMY
4736
+ axis. A change to `plan` after acceptance is caught where the run is born: the 202'd run ends with the same sentence in
4737
+ `GET /v1/leader/{leaderRunId}` `error` and no worker is started.
4738
+ content:
4739
+ application/json:
4740
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4716
4741
  '409':
4717
4742
  description: >-
4718
4743
  errorCode "conflict.leader_run_exists" (server >= 7.24.0) — your `Idempotency-Key` hit a leader run
@@ -5595,6 +5620,65 @@ paths:
5595
5620
  '403': { description: 'Multi-tenant worker: wiring diagnostics are operator-only (`auth.operator_only`). With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization — branch on `errorCode`, not the status).' }
5596
5621
  '404': { description: 'This worker serves no wiring diagnostics (older server, or a process not assembled by the composition root).' }
5597
5622
 
5623
+ /v1/admin/drain:
5624
+ parameters:
5625
+ - $ref: '#/components/parameters/PrincipalHeader'
5626
+ post:
5627
+ tags: [admin]
5628
+ operationId: adminDrain
5629
+ x-status: live # server >= 7.121.0 contract body; 7.35.0–7.120.x took a retired `{reason}` body.
5630
+ summary: Put THIS engine instance into draining by a deadline (operator-only).
5631
+ description: >
5632
+ Server >= 7.121.0. The body is the contract `DrainRequest` (owned by `@sema-agent/settings-schema` >= 9.0.0 `./fleet`), the answer
5633
+ the contract `DrainReceipt`. Entering drain: new work is refused (billable submits 503 `draining`; HITL answers — including the
5634
+ durable `/decide` — and resumes are ADMITTED), `GET /readyz` turns 503, preemptible in-flight legs park durably at their next clean
5635
+ turn boundary, live approval cards are re-routed to durable parks; the engine stops itself when in-flight reaches zero or at
5636
+ `deadlineAt` (fixed when entered — a later hot change to the drain window does not move it). After a drain entered through this
5637
+ endpoint, the FIRST stop signal the platform sends is absorbed (deadline unchanged) and only a later one, at least the minimum gap
5638
+ apart, hard-stops — so the platform stop timeout (Docker `stop_grace_period`, systemd `TimeoutStopSec`, k8s
5639
+ `terminationGracePeriodSeconds`) must be >= the requested deadline.
5640
+ IDENTITY — the request names WHICH instance, compared field by field (no state change on a mismatch, 409 `conflict.fleet_identity`
5641
+ naming the field, never echoing the value): `worker` = the engine's worker name (`SEMA_REGISTRY_WORKER`; an engine without a center
5642
+ lane has none, so every request mismatches); `expectedGeneration` = the fleet generation (`/health.generation`), ABSENT AGAINST ABSENT
5643
+ (omitting it is the standalone form, not a wildcard — it mismatches a fleet member); `scope` = the tenant scope, both sides read as a
5644
+ value (the engine's = env `FLEET_SCOPE`; unset on the engine and omitted in the request both read as the default scope `global`);
5645
+ `instanceId` (`/health.instanceId`) is compared only when present.
5646
+ IDEMPOTENT — the same `actionId` (or any other action) while already draining answers 200 `already_draining` and echoes the FIRST
5647
+ drain's `since` / `deadlineAt`, changing nothing. Clients do not auto-retry this POST; re-send with the same `actionId` to retry.
5648
+ RETIRED (zero alias): the 7.35.0–7.120.x `{reason}` "shutdown reason declaration" body (it only recorded a sentence and never
5649
+ drained) now answers 400 `request.field_invalid`; `/health.drainReason` and the `reason` key on 503 `draining` bodies are gone too.
5650
+ Gate order (same as every operator face): identity -> authorization -> body shape -> drain entry -> four-part identity.
5651
+ requestBody:
5652
+ required: true
5653
+ content:
5654
+ application/json:
5655
+ schema: { $ref: '#/components/schemas/DrainRequest' }
5656
+ responses:
5657
+ '200':
5658
+ description: Draining (this call entered it) or already draining (the first drain's facts, unchanged).
5659
+ content:
5660
+ application/json:
5661
+ schema: { $ref: '#/components/schemas/DrainReceipt' }
5662
+ '400': { $ref: '#/components/responses/BadRequest' } # request.field_invalid — the body is not a DrainRequest (including the retired `{reason}` form); nothing was changed
5663
+ '401': { $ref: '#/components/responses/Unauthorized' } # auth.unauthorized (credential gate) / auth.principal_required (REQUIRE_PRINCIPAL deployment, no usable identity)
5664
+ '403': { description: '`auth.operator_only` — OPERATOR_PRINCIPALS is non-empty and the caller is not in it.' }
5665
+ '409':
5666
+ description: >-
5667
+ `conflict.fleet_identity` (server >= 7.121.0) — the request's four-part identity (`scope` / `worker` / `expectedGeneration` /
5668
+ `instanceId`) does not match this engine; the sentence names the mismatched fields (never the values). Nothing was changed.
5669
+ Compare with the target engine's `GET /health` (`instanceId` / `generation`), its worker name and `FLEET_SCOPE`, then re-send.
5670
+ content:
5671
+ application/json:
5672
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
5673
+ '501':
5674
+ description: >-
5675
+ `capability.operator_principals_required` — OPERATOR_PRINCIPALS is empty, so no identity is an operator on this deployment (any
5676
+ deployment shape, ahead of authorization). `capability.drain_state_required` — the host has no shutdown chain wired (an embedded
5677
+ engine), so there is no drain to enter (answered after the body check). Branch on `errorCode`.
5678
+ content:
5679
+ application/json:
5680
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
5681
+
5598
5682
  /v1/sendfile-links:
5599
5683
  parameters:
5600
5684
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -5804,7 +5888,7 @@ paths:
5804
5888
  content:
5805
5889
  application/json:
5806
5890
  schema: { $ref: '#/components/schemas/CcImportPrepareResult' }
5807
- '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — not {layers:[{layer,path,root,content}]}, or a layer `root` the server does not accept (server >= 7.120.0: not a complete absolute path — see RuleScope; sentence "scope must be 'global' or 'project:<root>' with an absolute root", no ticket minted; <= 7.119.x: an empty `root`, answered with the body-shape sentence)
5891
+ '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — not {layers:[{layer,path,root,content}]}, or a layer `root` the server does not accept (server >= 7.120.0: not a complete absolute path — see RuleScope; sentence "layers[<i>].root must be an absolute path" on >= 7.121.0, the scope sentence on 7.120.x; no ticket minted; <= 7.119.x: an empty `root`, answered with the body-shape sentence)
5808
5892
  '401': { $ref: '#/components/responses/Unauthorized' }
5809
5893
  '413': { $ref: '#/components/responses/PayloadTooLarge' } # request.payload_too_large — more than 200 candidates across all submitted layers; SHORTEN and resend (retrying the same payload cannot help)
5810
5894
  '429': { $ref: '#/components/responses/RateLimited' }
@@ -6589,7 +6673,12 @@ components:
6589
6673
  ⚠️ On the billable submit set (every POST that starts a model turn — the server's
6590
6674
  `isBillableSubmitPath`, which is wider than the three task/run endpoints) this
6591
6675
  503 slot is SHARED with producers that are not auth at all, so `errorCode` — not the status — is the
6592
- branch key: `draining` (this replica is shutting down; retry against a replacement -> DrainingError),
6676
+ branch key: `draining` (this replica is shutting down; retry against a replacement -> DrainingError; server >= 7.121.0
6677
+ refuses only NEW work while draining — HITL answers, including the durable `/decide`, and resumes are admitted — and the body
6678
+ carries no `reason` key any more),
6679
+ `state.lease_expired` (server >= 7.121.0: this fleet member's serving lease has expired and the center has been unreachable
6680
+ for the deployment's `FLEET_LEASE_OUTAGE_MAX_MS`; new work only — HITL answers and resumes pass, in-flight runs are untouched;
6681
+ it lifts when the center grants again; retry later or route elsewhere -> ServiceStateError),
6593
6682
  `state.model_roster_pending` (roster not loaded yet; retry -> ServiceStateError), and (server >=7.0.0,
6594
6683
  synchronous `POST /v1/tasks` only) `memory.admission_required` — the org-memory admission directory is
6595
6684
  transiently undecidable, refused fail-closed BEFORE any model call, mapped to MemoryAdmissionError.
@@ -6612,7 +6701,7 @@ components:
6612
6701
  schema: { type: integer }
6613
6702
  description: >
6614
6703
  Seconds to wait before retrying. Presence is per producer, not per status:
6615
- `draining` always sends it (15), `state.model_roster_pending` always sends it (5),
6704
+ `draining` always sends it (15), `state.model_roster_pending` always sends it (5), `state.lease_expired` always sends it (60),
6616
6705
  `memory.admission_required` sends it ONLY when the engine supplied a hint — a steady-state refusal
6617
6706
  (no resolver wired) honestly carries neither this header nor the body's `retryAfterSec`.
6618
6707
  The auth legs (401 bad token / 503 missing token) never send it.
@@ -7138,7 +7227,12 @@ components:
7138
7227
  `config.permission_mode_unavailable`. `bypassPermissions` RELEASES the ten releasable ask classes (server >= 7.115.0: the tenth is `unstated`, a server-tool call nobody stated)
7139
7228
  and DISCLOSES each (`tool_end.gate.disposition.released` + one `engine_notice` `permission.ask_released_by_mode` per (mode, class) per run);
7140
7229
  immune classes (deployment-authored, ask / org rules, hooks, ancestors, questions …) still ask; a write whose protection
7141
- cannot be judged is refused (`deniedBy: mode`). Request vocabulary: SIX words (server >= 7.119.0) — `dontAsk`, the engine's
7230
+ cannot be judged is refused (`deniedBy: mode`) on the file tools — and, from server >= 7.122.0 / core 7.47.0, RELEASED on the SHELL face
7231
+ (an eleventh class, `write_protection_unresolved`, for a caller-supplied tool whose wire name is exactly `Bash` or `Monitor` and which
7232
+ declares a write-path target; see `ModeRelease.reason`). `plan` (server >= 7.122.0): the engine's plan mode and nothing the service adds —
7233
+ the tool table equals `default`'s, the engine mounts the plan-presenting tool, and a write or any non-read-only call is asked AT CALL TIME by the
7234
+ engine's plan gate (a card where somebody can answer, a refusal where nobody can; a delegated sub-agent inherits plan as its ceiling); up to
7235
+ 7.121.x the service also mapped `plan` to read-only hands, withheld the delegation tool and refused `Workflow`. Request vocabulary: SIX words (server >= 7.119.0) — `dontAsk`, the engine's
7142
7236
  "auto-refuse" mode, is accepted and passes through verbatim: the ENGINE refuses the calls that would ask (no card, no parked row,
7143
7237
  `tool_end.gate.disposition = { kind: denied, deniedBy: mode, mode: dontAsk, … }`), the run ends normally, and it is never looser
7144
7238
  than `default`. One exception: a PreToolUse hook's OWN ask, when no other class in that ask's set backs it, is still handed to a person
@@ -7148,7 +7242,7 @@ components:
7148
7242
  hold for <= 7.105.0 only.
7149
7243
  CC-parity permission-mode INTENT — the values THIS wire's closed submit-gate accepts (the first five on server <= 7.118.x,
7150
7244
  plus `dontAsk` from 7.119.0 = the engine's whole mode vocabulary, `capabilities.permissionModes`). The frontend (TUI shell / web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
7151
- `plan` ⇒ mount present_plan (core enablePlanMode) + read-only hands (CC EnterPlanMode research);
7245
+ `plan` ⇒ mount present_plan (core enablePlanMode) + read-only hands (CC EnterPlanMode research) — that is the <= 7.121.x reading; server >= 7.122.0: see the `plan` sentence in the first block;
7152
7246
  `default` adds the manual ask gate on non-exempt writes (scratchpad dirs / remembered session
7153
7247
  exemptions still auto-allow — not literally every Write/Edit); the server emits the SAME gate-policy
7154
7248
  for `auto` as for `default` — sending `auto` is NOT itself what arms core's auto-mode classifier; that
@@ -7250,6 +7344,22 @@ components:
7250
7344
  Pin a SEPARATE (usually cheaper) model for compaction. Validated against the catalog on submit — an
7251
7345
  unknown ref is a 400, not a silent fallback to the main model's price.
7252
7346
  appendSystemPrompt: { type: string, description: Text appended to the assembled system prompt for this task. }
7347
+ outputStyle:
7348
+ type: object
7349
+ required: [name]
7350
+ additionalProperties: false
7351
+ description: >-
7352
+ Server >= 7.121.0 (send only when `capabilities.outputStyle` is true; an older server answers 400 `request.body_shape` for the
7353
+ top-level key): the output style's PER-TURN line, handed verbatim to the engine's seat of the same name. Each attachment cycle the
7354
+ engine states "<name> output style is active. <turnReminder>" in its own marker frame (the engine's default sentence when
7355
+ `turnReminder` is absent), echoed on the stream as `steering_injected` with `source: "output_style"`. One fact, one seat: the
7356
+ style's BODY (the guide text) still goes in `settings.outputStyle`; this key carries only the name and the per-turn line.
7357
+ The value's owner is the engine (exactly these two keys, `name` non-empty and at most 256 characters, `turnReminder` a non-empty
7358
+ string when present); the server does not judge it — a bad value is accepted and refused at prepare, the run ends `failed` with
7359
+ code `config.output_style_invalid` (never silently ignored).
7360
+ properties:
7361
+ name: { type: string, minLength: 1, maxLength: 256 }
7362
+ turnReminder: { type: string, minLength: 1 }
7253
7363
  promptProfile:
7254
7364
  type: string
7255
7365
  enum: [simple, classic]
@@ -8993,7 +9103,16 @@ components:
8993
9103
  asyncRuns: { type: boolean }
8994
9104
  artifacts: { type: boolean }
8995
9105
  approvals: { type: boolean }
8996
- leader: { type: boolean }
9106
+ leader:
9107
+ type: boolean
9108
+ description: >-
9109
+ The `POST /v1/leader` lane. server >= 7.122.0 reads the conjunction of two facts: the leader face is mounted (`LEADER_ENABLED` etc.) AND the
9110
+ operator entry floor takes leader submissions — the same judgement as the 422 `request.autonomy_lane_refused` at the door (a leader task's
9111
+ workers have no plan review channel). So on a deployment whose operator runs `AUTONOMY=plan` the bit reads false and a direct submit still
9112
+ answers 422 `request.autonomy_lane_refused`; with `AUTONOMY=read-only` / `ask` / unset the bit follows the face. A false bit from an absent face
9113
+ answers 501 `capability.leader_endpoint_required` on submit (404 `not_found.route` on <= 7.115.x) — tell the two falses apart by the submit's
9114
+ `errorCode`, or read `GET /v1/policy` `autonomy` beforehand. <= 7.121.x (historical reading): the bit read the face only — true on a `plan`
9115
+ deployment, no prediction of the refusal.
8997
9116
  version: { type: string }
8998
9117
  scenarios:
8999
9118
  type: array
@@ -9221,6 +9340,8 @@ components:
9221
9340
  bgNotifyFailClosed: { type: boolean }
9222
9341
  steerPriority: { type: string }
9223
9342
  hookFailureNotice: { type: boolean, description: 'server >= 7.48.0 (#281): this binary emits a SECOND `hook_notice` kind, `hook_non_blocking_failure` (the user''s own hook broke: exit != 0 and != 2 / timeout / spawn failure / bad JSON). Absent = an older worker that only emits `hook_decision_unavailable` — branch by `kind`, never crash on an unknown one.' }
9343
+ leaseClient: { type: boolean, description: 'server >= 7.121.0, a version bit (always true, rides with the `fleet` object): this binary carries the serving-lease CLIENT — when the engine is a fleet member (`FLEET_GENERATION`) and the center declares in its heartbeat reply that it answers renewals (`servingLease`), the engine renews and is governed by the lease. Deliberately NOT named `servingLease` (that is the center''s bit): this says the client exists, not that a lease is in force — read `/health.leaseState` for that. Absent = an older worker.' }
9344
+ generationEcho: { type: boolean, description: 'server >= 7.121.0, a version bit (always true, rides with the `fleet` object): this binary echoes env `FLEET_GENERATION` as `generation` on the heartbeat and on `/health` (with `leaseState` / `leaseUntil`), and the drain endpoint `POST /v1/admin/drain` compares the four-part identity including `expectedGeneration`. Absent = an older worker.' }
9224
9345
  workspace:
9225
9346
  description: >
9226
9347
  #3 (server 1.299–7.51): workspace-browse capability family — an OBJECT when the E19 snapshot-store
@@ -9257,6 +9378,13 @@ components:
9257
9378
  effortSelection: { type: boolean, description: "`reasoningEffort` accepted per request." }
9258
9379
  compactionModel: { type: boolean, description: "A separate model may be pinned for compaction." }
9259
9380
  appendSystemPrompt: { type: boolean, description: "Per-request system-prompt append is honoured." }
9381
+ outputStyle:
9382
+ type: boolean
9383
+ description: >-
9384
+ server >= 7.121.0, hardcoded true (a predicate-class bit, same family as `approvalDecidedBy` / `sessionReadRedaction`): the
9385
+ top-level request key TaskRequest.outputStyle (the output style's per-turn line) is accepted and handed verbatim to the engine.
9386
+ Send the key only when this is present; absent = an older server, which answers 400 for it. Not the same thing as
9387
+ `taskSettings.outputStyle` (that bit says `settings.outputStyle`, the style BODY, is accepted) — two seats, two facts.
9260
9388
  toolOutput: { type: boolean, description: "`tool_end` carries the model-facing output." }
9261
9389
  messageIdentity: { type: boolean, description: "Content events carry `eventId` (+ `parentToolCallId` for sub-agents)." }
9262
9390
  forwardSubagentEvents: { type: boolean, description: "Sub-agent events are forwarded onto the parent stream." }
@@ -9835,13 +9963,16 @@ components:
9835
9963
  taskWriteFaceOpen:
9836
9964
  type: boolean
9837
9965
  description: >
9838
- server >= 7.95.0 (S-591; value meaning refined at 7.96.0 / S-613; mint `routes/capabilities.ts:653` =
9839
- `taskWriteFaceRefusal(deps.config) === undefined && autonomyOverrides(deps.config.autonomy).writeFace !== "roots"`)
9966
+ server >= 7.95.0 (S-591; value meaning refined at 7.96.0 / S-613; mint `routes/capabilities.ts` =
9967
+ `taskWriteFaceRefusal(deps.config) === undefined && autonomyKeepsOpenWriteFace(deps.config.autonomy)` from server >= 7.122.0;
9968
+ up to 7.121.x the right half was `autonomyOverrides(deps.config.autonomy).writeFace !== "roots"`)
9840
9969
  — DISCLOSURE BIT OF THE WRITE-CONTAINMENT FACE: does this deployment honour the task-level "open the write
9841
9970
  fence" declaration (`permissionMode:"bypassPermissions"` ⇒ `TaskSpec.writeFace:"open"`, writes outside the
9842
9971
  workspace roots)? `false` has two forms: (1) the hosted-posture gate refuses ⇒ sending that mode gets
9843
- `422 request.host_posture_refused`; (2) the deployment runs `AUTONOMY=read-only|plan` ⇒ the request is
9844
- accepted but the write face is clamped back to roots — the declaration silently does nothing. Three states
9972
+ `422 request.host_posture_refused`; (2) the deployment runs `AUTONOMY=read-only` ⇒ the request is
9973
+ accepted but the write face is clamped back to roots — the declaration silently does nothing; server >= 7.122.0: `AUTONOMY=plan`
9974
+ reads `false` too, but because the entry mode word is clamped to `plan` at admission (the engine's plan mode runs instead of
9975
+ `bypassPermissions`; up to 7.121.x it was the same read-only-hands + roots clamp as `AUTONOMY=read-only`, and a hosted deployment answered 422 `request.host_posture_refused` where it now accepts the request into plan). Three states
9845
9976
  are folded into one bit on purpose: the caller has exactly one decision (send that key or not); tier and
9846
9977
  evidence live in the refusal body and the operator's `GET /v1/diagnostics/wiring`. It speaks ONLY for the
9847
9978
  write-containment cell — the other two columns of `bypassPermissions` are unaffected.
@@ -12755,8 +12886,9 @@ components:
12755
12886
  REQUIRED iff `decision==="edit"` (FORBIDDEN otherwise → 400).
12756
12887
  🔴 THE BODY KEY SET IS CLOSED (server >= 7.86.0 / S-438): an unrecognised top-level key is refused with
12757
12888
  400 `request.body_shape` plus the `unknownKeys` / `unsupportedKeys` machine tables — refused rather
12758
- than dropped, because a misspelled `permissionModeAfter` would otherwise silently leave the task
12759
- read-only after its plan was approved.
12889
+ than dropped, because a misspelled `permissionModeAfter` would otherwise silently fold to `default`
12890
+ (the task keeps asking before each write after its plan was approved) while the caller believes the
12891
+ mode was settled.
12760
12892
  required: [decision]
12761
12893
  additionalProperties: false
12762
12894
  properties:
@@ -12767,18 +12899,35 @@ components:
12767
12899
  type: string
12768
12900
  enum: [default, acceptEdits]
12769
12901
  description: >
12770
- server >= 7.86.0 (S-433, P1; mints `routes/approvals-assistant.ts:1021/1024/1148`) — WHICH
12771
- PERMISSION MODE THIS TASK CONTINUES IN once the plan is approved. Before this key, approving a plan
12772
- was a BLANK CHEQUE: the plan was approved while the read-only clamp stayed on and every write kept
12773
- asking. `acceptEdits` = writes stop asking; `default` = keep asking (AND is the folded value when
12774
- the key is absent, so an older shell executes plans unchanged).
12775
- 🔴 THREE 400s, each with its own code: a value outside the two-word closed set ⇒
12902
+ server >= 7.86.0 (S-433, P1) — WHICH PERMISSION MODE THIS TASK CONTINUES IN once the plan is approved.
12903
+ `acceptEdits` = writes stop asking; `default` = keep asking (AND is the folded value when the key is
12904
+ absent, so an older shell executes plans unchanged). The decision writes the mode back into the task's
12905
+ stored submit body, so every later park -> resume continues in it and never falls back to plan.
12906
+ 🔴 server >= 7.122.0 — ONE JUDGE, ONE SOURCE: plan is only the engine's `permissionMode:"plan"`. Since 7.122.0
12907
+ the service adds no read-only clamp of its own, so there is none to lift: the tool table equals `default`'s,
12908
+ and a write or any non-read-only call is asked (somebody can answer) or refused (nobody can) AT CALL TIME by
12909
+ the engine's plan gate. A run's mode word has ONE source, the stored submit body; its two writers are the submit door
12910
+ and this write-back. A decision that carries the plan out of plan mode — `approve` (the original plan) or
12911
+ `edit` (the person's revised plan; the engine proceeds with it) — writes this key back (absent => `default`);
12912
+ only `reject` (the review goes back to redo the plan, plan mode stays) does not. Before 7.122.0 the service
12913
+ also mapped plan to read-only hands; this key was added in 7.86.0 because, before it, approving a plan was a
12914
+ BLANK CHEQUE (the plan was approved while the read-only clamp stayed on and every write kept asking); and on
12915
+ 7.86.0-7.121.x `edit` + this key was a 400 while `edit` itself wrote back `plan`, the continuation still held
12916
+ by the read-only hands. All of that is gone since 7.122.0.
12917
+ (A deployment's `AUTONOMY=read-only` static fence is a separate thing: since 7.122.0 it is all the service's governance
12918
+ layer re-folds on each resume, it has nothing to do with approving a plan, and approval does not lift it.)
12919
+ 🔴 THREE 400s, each with its own code (match by CODE, not by sentence — the two `request.field_conflict`
12920
+ sentences were reworded in 7.122.0, codes unchanged): a value outside the two-word closed set =>
12776
12921
  `request.field_invalid` (ZERO widening arm — this bit decides whether the model has hands after
12777
12922
  approval, so folding a misspelling into either mode is a silent downgrade on the security axis);
12778
- sending it with `edit`/`reject` ⇒ `request.field_conflict` (core: those two send the plan back to be
12779
- redone and the read-only clamp stays — dropping it there would let the caller believe the mode was
12780
- settled); sending it for a task that was NOT submitted with `permissionMode:"plan"` ⇒
12781
- `request.field_conflict` (approving the plan lifts no read-only restriction there).
12923
+ sending it with `reject` => `request.field_conflict`, server sentence `permissionModeAfter is only valid with a
12924
+ decision that carries the plan out ('approve' or 'edit'; got 'reject')` (a silent drop there would let the caller
12925
+ believe the mode was settled); sending it for a task whose STORED permission mode is not `plan` (the model
12926
+ entered plan mode itself, so approving returns it to the mode it was in before planning) =>
12927
+ `request.field_conflict`, server sentence `permissionModeAfter does not apply to this task: it was not
12928
+ started in plan mode (its stored permission mode is not "plan"), so approving the plan returns it to the mode
12929
+ it was in before planning`. A task STARTED in plan mode is one the request put there, or one an operator
12930
+ `AUTONOMY=plan` stamped at the submit door (its stored word is `plan`): the key is accepted for both.
12782
12931
  ⚠️ DIRECT-DOOR deployments: this key rides the HMAC envelope's TRAILING EXTENSION OBJECT (server
12783
12932
  7.87.0: `{answer?, permissionModeAfter?}`, present keys in lexicographic order; 7.86.0's bare
12784
12933
  trailing slot is DELETED with zero double-read) ⇒ a signer must sign the new shape, and mixed
@@ -13624,6 +13773,14 @@ components:
13624
13773
  `writeFace: open`; read `capabilities.taskWriteFaceOpen` first); (2) server >= 7.106.0 / S-895: `inheritEnv: "all"` on a
13625
13774
  hosted deployment (`REQUIRE_PRINCIPAL=true`; reach = the hosted posture only; read `capabilities.projectContext` first).
13626
13775
  Remove the key (or send `inheritEnv: "scrub"`) and resend. Resume legs fold it into 409 `resume_blocked_by_policy`.
13776
+ 🔴 `request.autonomy_lane_refused` (422, server >= 7.122.0): the deployment's operator `AUTONOMY` does not take tasks on this LANE.
13777
+ Today's only trigger is `AUTONOMY=plan` × `POST /v1/leader` — plan leaves plan mode only through a person approving the plan (plan
13778
+ review), a leader task's workers have no plan review channel, so the refusal is at the door and NO run is created (a replay with the
13779
+ same `Idempotency-Key` gets the same 422 and does not consume the key). Same family as `host_posture_refused` but a different axis:
13780
+ that one is the hosted / tenant posture axis (remove the key and resend); this one is the AUTONOMY axis (nothing to remove — submit
13781
+ as a run via `POST /v1/runs`, or ask the operator for `AUTONOMY=read-only` / `ask`, under which leader tasks run). Read the live
13782
+ config: a hot change takes effect on the next request; read `GET /v1/policy` `autonomy` beforehand (the `capabilities.leader` bit
13783
+ reads false on such a deployment).
13627
13784
  error: { type: string }
13628
13785
  errorMessage: { type: string }
13629
13786
  activeTaskId:
@@ -14362,7 +14519,9 @@ components:
14362
14519
  bypass-IMMUNE (not releasable).
14363
14520
  CLOSED on the gate record (screened with core `isAskClass`). `bypassPermissions` releases only the ten RELEASABLE classes
14364
14521
  (`transcript_write` / `rule_unreadable` / `probe` / `write_protection` / `tool_marks_mcp` / `tool_marks_a2a` /
14365
- `artifact_shared_read` / `mode` / `unstated` / `ordinary`); every other class (deployment-authored `operator_forced`, ask / org rules, hook,
14522
+ `artifact_shared_read` / `mode` / `unstated` / `ordinary`) — on every face — plus, from server >= 7.122.0 / core 7.47.0, an eleventh on the SHELL
14523
+ face only: `write_protection_unresolved` (core `releasableAskClassesFor(face)`: shell 11, other faces 10; on the file tools that class is
14524
+ refused, `deniedBy: mode`); every other class (deployment-authored `operator_forced`, ask / org rules, hook,
14366
14525
  ancestor, content question, …) asks under every mode.
14367
14526
  ModeRelease:
14368
14527
  type: object
@@ -14384,8 +14543,19 @@ components:
14384
14543
  mandate: { $ref: '#/components/schemas/PersistedRuleMandate' }
14385
14544
  reason:
14386
14545
  type: string
14387
- enum: [rule_unreadable]
14388
- description: '`rule_unreadable` = the release went over a rule this call could not be read against (the rule store was unreadable — a security-axis fail-open direction the server discloses on its own).'
14546
+ enum: [rule_unreadable, write_protection_unresolved]
14547
+ description: >
14548
+ WHAT the release went over — one word: the FIRST member (in core `MODE_RELEASE_REASONS` array order) that the ask's class
14549
+ set holds; ABSENT when the set holds none. CLOSED set — the server screens it with core `isModeReleaseReason`, so a word
14550
+ outside it withholds the whole `gate` from the frame (and a consumer that dispatches on the word must learn each new one).
14551
+ `rule_unreadable` = the release went over a rule this call could not be read against (the rule store was unreadable — a
14552
+ security-axis fail-open direction the server discloses on its own).
14553
+ `write_protection_unresolved` (server >= 7.122.0 / core 7.47.0) = the release went over a write whose target the
14554
+ write-protection judge could not RESOLVE (whether it lands on the protected table is unknown). It is released on the SHELL
14555
+ face only: a caller-supplied tool whose wire name is exactly `Bash` or `Monitor` and which declares a write-path target,
14556
+ under `bypassPermissions`; on the file tools the same unresolved write is still refused (`deniedBy: mode`). The engine's
14557
+ own `Bash` / `Monitor` cannot mint this class and the server mounts no such tool — it comes from a caller-embedded tool of
14558
+ those names. Before server 7.122.0 / core 7.47.0 the set was `rule_unreadable` alone.
14389
14559
 
14390
14560
  GateClassifierAttribution:
14391
14561
  # sdk 9.5.0:具名 —— 同一份形挂在 `GateDisposition` 的两只臂上,内联会立刻是两份会各自漂的镜像。
@@ -15509,6 +15679,17 @@ components:
15509
15679
  was armed but declined/failed (check the store, check authorisation), `false` = there was no lane at all
15510
15680
  (wire one up). The two recovery verbs differ, so a consumer MUST render them apart — NEVER fold them into
15511
15681
  one "nobody approved it".
15682
+ `server.model_unreachable` (server-minted, audience `user`; server >= 7.108.0, `taskId` added in server 7.121.0) — a model call on
15683
+ this leg ended in error and its last request failed AT CONNECT (VPN switched mid-session / the proxy read at boot went stale / the
15684
+ gateway is gone) — not the provider's answer. `{ code, targetHost, proxyInUse?, remedy, sessionId, taskId? }`: `code` one of the
15685
+ connect-failure set (`ECONNREFUSED` / `ENOTFOUND` / `EAI_AGAIN` / `EHOSTUNREACH` / `ENETUNREACH` / `UND_ERR_CONNECT_TIMEOUT`),
15686
+ `targetHost` the model endpoint's `host:port` (a proxy address never rides this frame), `proxyInUse` present only when this hop
15687
+ went through a proxy and only as the variable NAME (`HTTP_PROXY` / `HTTPS_PROXY`), `remedy` a fixed sentence. `taskId`
15688
+ (server >= 7.121.0) = this leg's run resource id (streaming leg: `X-Task-Id` / the `meta` frame's `taskId`; background leg: the
15689
+ submit receipt's `taskId`; resume leg: the resumed run's id) — the same value as that run's terminal and its run-domain codes'
15690
+ `detail.taskId`; absent when the leg has no run resource (a streaming leg on a deployment without a run store) or on an older
15691
+ server. NO `runId` (the model-call layer cannot see the engine's run identity): a resume leg and its original leg share the
15692
+ `taskId`. Once per leg per target; it says "this endpoint is unreachable", not "this run failed".
15512
15693
  `session.records_skipped` (K-24; server >= 7.104.0 / S-612 form B; contract §17.4 / appendix D.3) — 🔴 SERVER-MINTED
15513
15694
  (not in core's register; owner, audience `user` and mint all live in the server). The session's saved log was
15514
15695
  OPENED and some records could not be read and were skipped (file session backend only — both SQL backends refuse a
@@ -17767,13 +17948,16 @@ components:
17767
17948
  🔴 Its arrival means the RUN IS STILL ALIVE — the 15-minute stream cap fired. The correct handling is to
17768
17949
  RECONNECT with `Last-Event-ID` and keep reading (the durable leg stamps `id:` on every frame); rendering
17769
17950
  it as a terminal would declare a live task dead. `done` / `failed` are the terminal arms; this one is not.
17770
- Known `errorCode`: `STREAM_MAX_DURATION` (open set — branch on the code, degrade unknown codes to the
17771
- generic "reconnectable stream-control signal").
17951
+ Known `errorCode`: `STREAM_MAX_DURATION` (the 15-minute cap) · `draining` (server >= 7.122.0: the instance entered drain / is being
17952
+ replaced — each of the eight read-only subscription streams gets this frame at the moment drain begins and is then closed by the server;
17953
+ same code and sentence as the billable-submit 503 `draining`, same reconnect protocol: reconnect with `Last-Event-ID`, the replacement
17954
+ instance serves it; NOT a run failure; the same reason family as the cap — the server closes a stream for flow control)
17955
+ (open set — branch on the code, degrade unknown codes to the generic "reconnectable stream-control signal").
17772
17956
  additionalProperties: false
17773
17957
  required: [type, errorCode]
17774
17958
  properties:
17775
17959
  type: { const: error }
17776
- errorCode: { type: string, description: 'STREAM_MAX_DURATION (open set).' }
17960
+ errorCode: { type: string, description: 'STREAM_MAX_DURATION | draining (server >= 7.122.0) (open set).' }
17777
17961
  message: { type: string }
17778
17962
  Event_question:
17779
17963
  type: object
@@ -19593,6 +19777,33 @@ components:
19593
19777
  The three user-editable CC settings layers this version reads (core `ImportedSettingsLayer`). The two
19594
19778
  layers it does NOT read are named in `CcImportPreview.uncovered` rather than being silently absent.
19595
19779
 
19780
+ DrainRequest:
19781
+ type: object
19782
+ required: [actionId, worker, deadlineAt]
19783
+ description: >-
19784
+ The drain endpoint's request (contract `DrainRequest`, `@sema-agent/settings-schema` >= 9.0.0 `./fleet`): "put this instance into
19785
+ draining by this deadline". See `POST /v1/admin/drain` for how the identity fields are compared.
19786
+ properties:
19787
+ actionId: { type: string, minLength: 1, description: 'Idempotency key: the same id again answers `already_draining` and changes nothing. Hand-issued actions pick their own (e.g. `manual:<uuid>`).' }
19788
+ worker: { type: string, minLength: 1, description: The engine's worker name (`SEMA_REGISTRY_WORKER`). }
19789
+ deadlineAt: { type: integer, minimum: 0, description: Epoch milliseconds by which the engine stops on its own. }
19790
+ scope: { type: string, minLength: 1, description: 'The tenant scope; omitted = the default scope `global` (compared against env `FLEET_SCOPE`, unset = `global`).' }
19791
+ expectedGeneration: { type: string, pattern: '^(0|[1-9][0-9]*)$', description: 'The fleet generation as a canonical decimal string (`/health.generation`); absent against absent — omit it only for a non-member.' }
19792
+ instanceId: { type: string, minLength: 1, description: The engine's per-process identity (`/health.instanceId`); compared only when present. }
19793
+ term: { type: string, minLength: 1, description: The claim term the sending executor holds for this action (center-issued, opaque); absent on a direct or hand-issued call. }
19794
+ DrainReceipt:
19795
+ type: object
19796
+ required: [actionId, since, deadlineAt, state]
19797
+ additionalProperties: false
19798
+ description: >-
19799
+ The drain endpoint's answer (contract `DrainReceipt`): the engine's drain fact plus how this call found it. `state: draining` = this
19800
+ call entered drain; `already_draining` = it already was (including a drain the engine entered on its own), and the receipt reports the
19801
+ FIRST drain's facts.
19802
+ properties:
19803
+ actionId: { type: string, minLength: 1, description: The action that entered this drain. }
19804
+ since: { type: integer, minimum: 0, description: 'Epoch ms the drain began (the same value as `/health.drainingSince`).' }
19805
+ deadlineAt: { type: integer, minimum: 0, description: Epoch ms the engine stops on its own (fixed when the drain was entered). }
19806
+ state: { type: string, enum: [draining, already_draining] }
19596
19807
  RuleScope:
19597
19808
  description: >
19598
19809
  Where a persisted rule applies (core `RuleScope`). `project.root` is the directory prefix that decides
@@ -19602,9 +19813,12 @@ components:
19602
19813
  layer `root`, and the `project:<root>` discriminant string on write / revoke / the list filter):
19603
19814
  server >= 7.120.0 accepts only a COMPLETE ABSOLUTE PATH — POSIX `/…`, a Windows drive root `X:\…` / `X:/…`,
19604
19815
  or a UNC root `\\host\…` (the engine's durable-scope predicate; every server entry calls it rather than
19605
- keeping a copy). A relative root (`proj`, `./x`, `~/x`) or a drive without a root (`C:`, `C:x`) is a 400 whose
19606
- `error` is exactly "scope must be 'global' or 'project:<root>' with an absolute root" — code
19607
- `request.body_shape` on write / revoke / cc-import prepare, `request.query_invalid` on the list filter.
19816
+ keeping a copy). A relative root (`proj`, `./x`, `~/x`) or a drive without a root (`C:`, `C:x`) is a 400 — code
19817
+ `request.body_shape` on write / revoke / cc-import prepare, `request.query_invalid` on the list filter. Branch on `errorCode`;
19818
+ the sentence is for people: on write / revoke / the list filter the `error` is exactly "scope must be 'global' or
19819
+ 'project:<root>' with an absolute root"; on cc-import prepare (whose body has no `scope` field) server >= 7.121.0 answers
19820
+ "layers[<i>].root must be an absolute path" (`<i>` = the first refused layer, from 0; same predicate), while 7.120.x
19821
+ answered with the scope sentence.
19608
19822
  Server <= 7.119.x checks only that the root is non-empty and keeps relative roots as written; such rows take
19609
19823
  no part in any check on 7.120.0 — SQL backends delete them with a one-time migration at boot, while on the
19610
19824
  file (`local`) backend they stay in the store, are absent from the listing, cannot be revoked, and each run
@@ -19699,8 +19913,8 @@ components:
19699
19913
  what is read is still only the allow bucket.
19700
19914
 
19701
19915
  🔴 `root` follows the RuleScope rule (server >= 7.120.0: a complete absolute path; otherwise 400
19702
- `request.body_shape` with "scope must be 'global' or 'project:<root>' with an absolute root" and no ticket
19703
- minted). It applies to ALL layers alike — a `userSettings` layer's root never becomes a scope, but it is the
19916
+ `request.body_shape` and no ticket minted — the `error` is "layers[<i>].root must be an absolute path" on server >= 7.121.0,
19917
+ naming the first refused layer, and the scope sentence on 7.120.x). It applies to ALL layers alike — a `userSettings` layer's root never becomes a scope, but it is the
19704
19918
  same field under the same check. On <= 7.119.x only an empty root is refused (with the body-shape sentence).
19705
19919
  Its TRUTHFULNESS is deliberately NOT validated: the server has no such tree to check against, and pretending
19706
19920
  to validate would be a lie. It is the user's own label for "which cwds should this rule be live under".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "17.2.0",
3
+ "version": "18.1.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",
@@ -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": "^8.0.0"
55
+ "@sema-agent/settings-schema": "^9.0.0"
56
56
  }
57
57
  }