@sema-agent/sdk 18.1.0 → 18.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml CHANGED
@@ -2416,7 +2416,23 @@ paths:
2416
2416
  required: [rules]
2417
2417
  properties:
2418
2418
  rules: { $ref: '#/components/schemas/StoredSessionRules' }
2419
- '400': { $ref: '#/components/responses/BadRequest' }
2419
+ '400':
2420
+ description: >-
2421
+ 400 — the rules body is refused BEFORE anything is written (not one byte reaches the store). Branch on `errorCode`, never on the sentence.
2422
+ `rules.legacy_tool_name` (server >= 7.105.0; the `rules.*` family, the SDK lands it on `BadRequestError`): a tool-name bucket (`toolAllow` / `toolDeny`) entry
2423
+ that would be written but could never run — an engine-retired name, a `__` name without a protocol prefix, or a protocol name whose peer segment the
2424
+ engine cannot mint. Body keys (top level; the SDK reads them from `err.extra`): `field` (which bucket), `name` (which entry), `verdict`
2425
+ (`retired` / `unprefixed_namespace`, server >= 7.116.0; `unmintable_segment`, server >= 7.118.0), `currentName` (server >= 7.116.0; present when
2426
+ there is a 1:1 successor), `guidance` (server >= 7.119.0; the engine judge's wording of the fix, byte for byte — carried by `retired` and
2427
+ `unmintable_segment`, absent for `unprefixed_namespace`). 🔴 server >= 7.123.0 also lands a protocol name with an EMPTY TOOL SEGMENT
2428
+ (`mcp__<server>__`, `a2a__<peer>__`) here, as `verdict: "unmintable_segment"`; server 7.116.0–7.122.x answered `400 request.field_invalid`
2429
+ for it — a caller that branched on that code for this shape now branches on `rules.legacy_tool_name` (the body's `verdict` tells the shapes apart).
2430
+ `request.field_invalid`: a bucket that is not a list of non-empty strings (a bare string, `null`, a non-string member) and — behavior change
2431
+ in server >= 7.123.0 — an EMPTY or BLANK (whitespace-only) member, which earlier servers took in (200); the sentence is the engine's own,
2432
+ match on the code. Also `request.id_invalid` (malformed session id) and a malformed body.
2433
+ content:
2434
+ application/json:
2435
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2420
2436
  '401': { $ref: '#/components/responses/Unauthorized' }
2421
2437
  '403':
2422
2438
  description: 403 `loosen_forbidden` — a non-operator write tried to relax the rules (tighten-only).
@@ -3116,7 +3132,20 @@ paths:
3116
3132
  content:
3117
3133
  application/json:
3118
3134
  schema: { $ref: '#/components/schemas/ImportStaged' }
3119
- '400': { $ref: '#/components/responses/BadRequest' }
3135
+ '400':
3136
+ description: >-
3137
+ 400 — malformed input (the `policy` face is judged by the same write-door reader as `PUT /v1/sessions/{sessionId}/policy`, BEFORE any staging
3138
+ is created, so a refused push leaves nothing behind). Branch on `errorCode`, never on the sentence. `rules.legacy_tool_name` (server >= 7.105.0):
3139
+ a record's tool-name bucket (`toolAllow` / `toolDeny`) holds a name that could never run — an engine-retired name, a `__` name without a protocol prefix,
3140
+ or a protocol name whose peer segment the engine cannot mint. Body keys (top level; the SDK reads them from `err.extra`): `field`, `name`, `verdict`
3141
+ (`retired` / `unprefixed_namespace`, server >= 7.116.0; `unmintable_segment`, server >= 7.118.0), `currentName` (server >= 7.116.0, when there is a 1:1 successor),
3142
+ `guidance` (server >= 7.119.0, the engine judge's wording; absent for `unprefixed_namespace`). 🔴 server >= 7.123.0 also lands a protocol name with an EMPTY TOOL
3143
+ SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) here (`verdict: "unmintable_segment"`); server 7.116.0–7.122.x answered `400 request.field_invalid` for it.
3144
+ `request.field_invalid`: a bucket that is not a list of non-empty strings and — behavior change in server >= 7.123.0 — an EMPTY or BLANK member, which earlier
3145
+ servers took in (the sentence carries the `policy: [i].rules: ` position prefix and then the engine's own sentence; match on the code).
3146
+ content:
3147
+ application/json:
3148
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3120
3149
  '401': { $ref: '#/components/responses/Unauthorized' }
3121
3150
  '404': { $ref: '#/components/responses/NotFound' }
3122
3151
  '409':
@@ -4123,8 +4152,9 @@ paths:
4123
4152
  description: >
4124
4153
  READ-ONLY catalog of `@mention`-pickable models. The user selects a model PER-TASK by typing `@<name>` in
4125
4154
  the objective body (the service allowlist-parses it; the client never re-decides). Envelope
4126
- `{ models, default, defaultModel }` (`default` = the deployment default model id, display-only — the
4127
- internal `default` alias is hidden; `defaultModel` = the same default as `{id, name?}` in one piece).
4155
+ `{ models, default, defaultModel, droppedEntries? }` — see `ModelsResponse` (`default` = the deployment default model id,
4156
+ display-only — the internal `default` alias is hidden; `defaultModel` = the same default as `{id, name?}` in one piece;
4157
+ `droppedEntries` = server >= 7.123.0, the catalog entries the read leg dropped, in the same generation as `models`).
4128
4158
  🔴 NON-SECRET: only name/id/provider/reasoning/vision + the E4/E7 capability fields — NEVER baseUrl/apiKey/
4129
4159
  headers (the service strips them). Fail-safe: on error, render an empty picker (never block).
4130
4160
  responses:
@@ -4132,26 +4162,7 @@ paths:
4132
4162
  description: Model catalog (possibly empty).
4133
4163
  content:
4134
4164
  application/json:
4135
- schema:
4136
- type: object
4137
- # 🔴 封闭 + `defaultModel` 补登记(census 批2 第三段,2026-07-30):铸造点
4138
- # `routes/capabilities.ts` 三键字面量。`defaultModel`([865]③ id/name 撕裂消解,server
4139
- # 已发多版)SDK `models.list()` 的返回类型早就带,**spec 是唯一没登记的一侧** —— 生成式
4140
- # 消费方对它全瞎。`name` 条件(目录无命中即省略),`id` 恒在。
4141
- additionalProperties: false
4142
- required: [models, default, defaultModel]
4143
- properties:
4144
- models:
4145
- type: array
4146
- items: { $ref: '#/components/schemas/ModelInfo' }
4147
- default: { type: string, description: "deployment default model id (display-only; legacy id-form, byte-stable)" }
4148
- defaultModel:
4149
- type: object
4150
- additionalProperties: false
4151
- required: [id]
4152
- properties:
4153
- id: { type: string }
4154
- name: { type: string, description: 'the catalog @handle of the default model; omitted when the catalog has no match (env single-model lane).' }
4165
+ schema: { $ref: '#/components/schemas/ModelsResponse' }
4155
4166
  '401': { $ref: '#/components/responses/Unauthorized' }
4156
4167
 
4157
4168
  /v1/elicitations/{id}/respond:
@@ -7469,6 +7480,10 @@ components:
7469
7480
  `MANUAL_MODE_SHELL_GATE=always`, write protection, governance, a hook's ask and plan-mode asks still ask (a matching `request` member then shows as `allowRuleShadowed` on the
7470
7481
  card). The submit gate judges only the shape (array of strings, at most 1000 entries => 400) and the NAMES (the same judge and the same sentence as `deny` / `ask`: a retired name,
7471
7482
  an unprefixed short name, or a protocol name whose server segment cannot be minted / is missing / is a wildcard (`mcp__*`) is a whole-request 422) — never the content.
7483
+ 🔴 server >= 7.118.0: the NAME layer of the allow side moved to the engine — a retired name, an unprefixed short name or an unmintable protocol name is ACCEPTED here (no 422) and the run opens with an
7484
+ `engine_notice` `permission.allowed_tool_not_granted` (`entries[].reason: tool_name`); the `deny` / `ask` buckets still refuse that family with a whole-request 422. 🔴 server >= 7.123.0 (core 7.48.0) also accepts a
7485
+ protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) in this bucket (server 7.118.0–7.122.x refused it on every table). One name, three outcomes:
7486
+ allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name` (`verdict: unmintable_segment`).
7472
7487
  🔴 THE 422 SENTENCE (server >= 7.117.0): `error` starts with `request settings cannot be applied as written: ` (<= 7.116.x: a different sentence that said tighten-only — all three
7473
7488
  buckets share this exit, and the old head misled a caller writing a tightening rule and lied about the allow bucket). Do NOT dispatch on the sentence: it carries no code of its own, so
7474
7489
  `errorCode` is the status-derived coarse code `request.unprocessable`.
@@ -9601,6 +9616,9 @@ components:
9601
9616
  `engine_notice` `permission.allowed_tool_not_granted`; any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / shell floor / write protection /
9602
9617
  governance, a hook's ask and plan-mode asks still ask (a matching member then shows as `allowRuleShadowed` on the card). The submit gate judges only the shape and the NAMES (retired
9603
9618
  name / unprefixed short name / unmintable protocol server segment => 422, at most 1000 entries => 400) — never the content.
9619
+ 🔴 server >= 7.118.0: the allow side's NAME layer moved to the engine (retired / short / unmintable names are accepted there and announced with `engine_notice` `permission.allowed_tool_not_granted`; `deny` / `ask` still 422);
9620
+ server >= 7.123.0 also accepts a protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`) on the allow side (server 7.118.0–7.122.x refused it on every table):
9621
+ allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name`.
9604
9622
  🔴 ABSENT `allowLayer` with `allowGrant` present = a 7.115.0–7.116.x server: allow is a STATEMENT (it does not clear the engine's own write gate / shell checks; a content form
9605
9623
  is a whole-request 422). ABSENT KEY altogether (no `settings`) = an older server (<= 7.114.x), which read `allow` as a WHITELIST (everything off the list refused) — do not put
9606
9624
  anything in the allow bucket there. NOT the same bit as `taskSettings.permissions` (unchanged, still a boolean: "this server accepts the `settings.permissions` face"). The
@@ -9779,10 +9797,11 @@ components:
9779
9797
  does not know auto" and "it knows auto but this box has no entitlement source" would collapse to
9780
9798
  the same false, and a shell must do different things for those. `reason` is present ONLY when
9781
9799
  `armed` is false; read its vocabulary as an OPEN set (server already grew it from four words to
9782
- six). `model` is omitted when the classifier route cannot be resolved (the server logs the
9800
+ six, and to seven with `operator_floor` in server >= 7.123.0). `model` is omitted when the classifier route cannot be resolved (the server logs the
9783
9801
  engine's refusal rather than 500-ing the read face). `?permissionMode=<mode word>` folds the
9784
9802
  caller's INTENT into the verdict (server >= 7.119.0 takes the six words of `capabilities.permissionModes` — `dontAsk` answers
9785
- `armed: false` / `reason: mode_not_auto`; server <= 7.118.x takes five); anything outside the server's words is a 400
9803
+ `armed: false` / `reason: mode_not_auto`, except on a deployment whose operator entry floor rewrites the word — `AUTONOMY=plan`, where
9804
+ server >= 7.123.0 answers `operator_floor`, see `reason`; server <= 7.118.x takes five); anything outside the server's words is a 400
9786
9805
  `request.field_invalid`.
9787
9806
  # 🔴 required 只有**三位**:武装两键(armed / reason)是 server >= 7.57.0 (S-80)
9788
9807
  # 才有的,而本包的支持地板是 3.0.0 => every 7.x <= 7.56.0 server is inside the promised face and
@@ -9803,7 +9822,17 @@ components:
9803
9822
  fold that absence into false (it would render a 7.50 box that IS arming as "auto == default").
9804
9823
  reason:
9805
9824
  type: string
9806
- enum: [mode_not_auto, deployment_incapable, org_denied, local_denied, settings_denied, resolver_fault]
9825
+ enum: [mode_not_auto, operator_floor, deployment_incapable, org_denied, local_denied, settings_denied, resolver_fault]
9826
+ description: >
9827
+ Why `armed` is false — the server's closed SEVEN words, in judgement order (read as an open set; keep a default arm).
9828
+ `mode_not_auto` = this request's intent (the `?permissionMode=` query word) is not `auto` and that word reaches the engine as it is.
9829
+ `operator_floor` (server >= 7.123.0) = this deployment's operator entry floor (`AUTONOMY=plan`) rewrote the intent into another word, and that
9830
+ word is not `auto`: the word that reaches the engine is the operator's, so the way out is the operator changing the level, NOT the word the
9831
+ request sends. On an `AUTONOMY=plan` deployment an absent intent (answered as the `auto` intent), `auto` and any word looser than `plan` read
9832
+ `operator_floor`; an intent that IS `plan` stays `mode_not_auto` (the word arrives as sent, not rewritten by the floor).
9833
+ Server <= 7.122.x answers `mode_not_auto` for both cases. The rest: `deployment_incapable` = no classifier seat mounted;
9834
+ `org_denied` = this principal's caps say auto is off and no local deny is set (the center's explicit deny, or its fail-closed refusal); `local_denied` = the local `PERMISSIONS_DISABLE_AUTO_MODE`;
9835
+ `settings_denied` = the settings layer folds auto (run-level, not produced by this read face); `resolver_fault` = the resolver threw.
9807
9836
  model: { type: string }
9808
9837
  readFace:
9809
9838
  type: ["string", "null"]
@@ -10228,6 +10257,9 @@ components:
10228
10257
  segment cannot be minted / is missing / is a wildcard (`mcp__*`) is a 400 too, and from 7.117.0 so is a retired name / unprefixed short
10229
10258
  name (the name layer is judged by the same judge as the allow bucket) and a list of more than 1000 entries (top-level `skills[]` and
10230
10259
  `agents[].skills[]` judge identically). Probe `capabilities.skillToolLists`.
10260
+ 🔴 The name-layer 400s above hold for server 7.116.x–7.117.x only. server >= 7.118.0: the allow side's name layer moved to the engine — a retired name / unprefixed short name / unmintable protocol name is ACCEPTED here and the run opens with an
10261
+ `engine_notice` `permission.allowed_tool_not_granted` (`entries[].reason: tool_name`); server >= 7.123.0 also accepts a protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) here (server 7.118.0–7.122.x refused it).
10262
+ One name, three outcomes: allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name` (`disallowedTools` below is a deny side).
10231
10263
  disallowedTools:
10232
10264
  type: array
10233
10265
  items: { type: string }
@@ -10242,6 +10274,8 @@ components:
10242
10274
  user audience, on the session stream). 🔴 The resume legs judge the stored body the same way: a parked task whose stored skill `disallowedTools` carries such an entry answers
10243
10275
  409 `resume_blocked_by_policy` (`blockedBy: "request.field_invalid"`, same sentence) BEFORE the claim — row and card untouched; a terminal run's `POST /v1/runs/{taskId}/steer`
10244
10276
  answers it before minting the wake card. Why: engine 7.41 trimmed whitespace (`"Bash "` was a working deny), engine 7.42 reads verbatim (reads as nothing). The allow side does not refuse.
10277
+ 🔴 A protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) is refused here (server >= 7.123.0: `error` is the engine's entry-reading sentence, `... reads as nothing (tool_name) ...`), while `allowedTools` accepts it from 7.123.0 —
10278
+ one name, three outcomes: allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name`.
10245
10279
 
10246
10280
  McpServerSpec:
10247
10281
  type: object
@@ -10975,6 +11009,92 @@ components:
10975
11009
  `"other"`). This is the "where did those 180 404s actually land" cell. Empty object = zero failures;
10976
11010
  key absent = an older server.
10977
11011
 
11012
+ ModelsResponse:
11013
+ type: object
11014
+ description: >
11015
+ The `GET /v1/models` 200 body (the envelope; service `src/http/routes/capabilities.ts` mints the keys as literals).
11016
+ `models` = the `@mention`-pickable catalog (`ModelInfo`); `default` = the deployment default model id (display-only, legacy id
11017
+ form, byte-stable); `defaultModel` = the same default as `{id, name?}`; `droppedEntries` = server >= 7.123.0, ALWAYS present
11018
+ there: the catalog entries THIS generation's read leg dropped one by one (an entry that does not pass the settings-schema
11019
+ `ModelEntry` shape; the rest of the catalog is served as usual). ABSENT = an earlier server (<= 7.122.x) — so the key is
11020
+ optional here (the SDK's supported server floor is 3.0.0); `[]` = the engine says nothing was dropped (also the env lane,
11021
+ where no catalog is read), which is NOT the same as absent (the engine cannot say).
11022
+ # 🔴 封闭 + `defaultModel` 补登记(census 批2 第三段,2026-07-30):铸造点
11023
+ # `routes/capabilities.ts` 三键字面量。`defaultModel`([865]③ id/name 撕裂消解,server
11024
+ # 已发多版)SDK `models.list()` 的返回类型早就带,**spec 是唯一没登记的一侧** —— 生成式
11025
+ # 消费方对它全瞎。`name` 条件(目录无命中即省略),`id` 恒在。
11026
+ # 18.2.0:信封升为具名 schema(此前内联在 `GET /v1/models` 的 200 上),新键 `droppedEntries`(server >= 7.123.0)挂在上面,**不进 required**。
11027
+ additionalProperties: false
11028
+ required: [models, default, defaultModel]
11029
+ properties:
11030
+ models:
11031
+ type: array
11032
+ items: { $ref: '#/components/schemas/ModelInfo' }
11033
+ default: { type: string, description: "deployment default model id (display-only; legacy id-form, byte-stable)" }
11034
+ defaultModel:
11035
+ type: object
11036
+ additionalProperties: false
11037
+ required: [id]
11038
+ properties:
11039
+ id: { type: string }
11040
+ name: { type: string, description: 'the catalog @handle of the default model; omitted when the catalog has no match (env single-model lane).' }
11041
+ droppedEntries:
11042
+ type: array
11043
+ items: { $ref: '#/components/schemas/DroppedEntry' }
11044
+ description: >
11045
+ server >= 7.123.0 (settings-schema >= 10.1.0), always present there; ABSENT = an earlier server. The catalog entries the read leg
11046
+ dropped from THIS generation (an entry that does not pass the `ModelEntry` shape) so a shell / admin panel can tell "which entry
11047
+ I wrote did the engine not take, and why" without re-judging the schema. Rows carry an index, a closed reason word and (when
11048
+ disclosable) a name / id — NEVER free text (the schema diagnostics, the entry value and any Record key stay in the server log).
11049
+ 🔴 SAME GENERATION as `models`: both are the two faces of one catalog generation and commit together. When a center candidate's
11050
+ model plane is refused as a group (for example the default model is not in the catalog), or the candidate has no enabled entry at all
11051
+ (survivors and dropped rows both count by the `enabled` each row wrote; a dropped row whose `enabled` cannot be read counts as enabled),
11052
+ `models` and `droppedEntries` both stay on the previous generation — the key does NOT report the entries of the candidate that did not
11053
+ land (why it did not land: `/health.modelRefusals`). So a disabled dropped entry shows up only in a catalog that has an enabled entry and whose model
11054
+ plane landed. Different question from `/health.modelRefusals` (which names the models this box does NOT serve, with the engine's refusal code):
11055
+ this key lists what the read leg dropped, including nameless and disabled rows.
11056
+ 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
11057
+ enabled; while serving from it with the center still unreachable this key lists only those rows (possibly `[]`) and the first pull after the
11058
+ center is reachable fills it in. An unknown `reasonCode` word reads as "dropped, reason unknown".
11059
+
11060
+ DroppedEntry:
11061
+ type: object
11062
+ description: >
11063
+ One row of `ModelsResponse.droppedEntries` (server >= 7.123.0): a catalog entry the read leg dropped. `index` = the entry's original
11064
+ position in the `models` array of the catalog document read this time (the local lane = its place in `config.d/models.json`; the
11065
+ center lane = the catalog the center sent this worker) — the same coordinate as the server's `models.<index>` log line.
11066
+ `identity` is optional: absent when none of the three keys is disclosable.
11067
+ additionalProperties: false
11068
+ required: [index, reasonCode]
11069
+ properties:
11070
+ index: { type: integer, minimum: 0 }
11071
+ reasonCode: { $ref: '#/components/schemas/DroppedEntryReasonCode' }
11072
+ identity: { $ref: '#/components/schemas/DroppedEntryIdentity' }
11073
+
11074
+ DroppedEntryReasonCode:
11075
+ type: string
11076
+ enum: [entry-schema-invalid, entry-key-invalid, entry-unreadable]
11077
+ description: >
11078
+ Why the read leg dropped a catalog entry (server >= 7.123.0). The words are settings-schema's closed `DROPPED_ENTRY_REASON_CODES`, passed through
11079
+ verbatim (the server neither hand-copies nor rewrites them; a new word in the package is a compile error there, forcing an explicit decision).
11080
+ `entry-schema-invalid` = the entry did not pass its own schema; `entry-key-invalid` = a Record-shaped collection's key did not pass the key schema
11081
+ (the catalog is an array, so this word does not appear on `droppedEntries` today — keep it in the switch anyway); `entry-unreadable` = the judging
11082
+ threw (a pathological accessor; the engine never carries what it threw). Read an unknown word as "dropped, reason unknown".
11083
+
11084
+ DroppedEntryIdentity:
11085
+ type: object
11086
+ description: >
11087
+ The disclosable identity of a dropped entry (settings-schema's entry-identity shape). `name` / `id` appear only when they are DISCLOSABLE identifiers
11088
+ (allow-listed characters, no secret shape, no URL userinfo — the server asks again at the exit); a non-disclosable one is simply absent, NOT masked (a masked
11089
+ string is no longer an identity). `enabled` is the boolean the entry wrote itself (a disabled dropped entry is listed with `enabled: false`). With none of the
11090
+ three keys the whole `identity` is absent — an empty object never reaches the wire, hence minProperties: 1.
11091
+ additionalProperties: false
11092
+ minProperties: 1
11093
+ properties:
11094
+ name: { type: string }
11095
+ id: { type: string }
11096
+ enabled: { type: boolean }
11097
+
10978
11098
  ModelInfo:
10979
11099
  type: object
10980
11100
  description: >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "18.1.0",
3
+ "version": "18.2.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": "^9.0.0"
55
+ "@sema-agent/settings-schema": "^10.1.1"
56
56
  }
57
57
  }