@sema-agent/sdk 9.4.0 → 9.6.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
@@ -83,6 +83,25 @@ info:
83
83
  • New contract fields are OPTIONAL with no default (CORE-PRINCIPLES §6).
84
84
  • Async results are delivered by POLL (`GET /v1/runs/:id`) or SSE (`/v1/runs/:id/events`).
85
85
  There is NO generic webhook/callback and NO signing (SERVICE-CORE-CONTEXT §2.4).
86
+ • 🔴 STRING-LENGTH UNIT (applies to EVERY `maxLength`/`minLength` in this file). The producer's own
87
+ gates are JavaScript `.length`, i.e. **UTF-16 code units**; JSON Schema counts **code points**.
88
+ The two agree across the entire BMP and differ only on supplementary characters (emoji, rare CJK,
89
+ musical symbols), each of which costs 2 units but 1 code point. => near a bound, a value made of
90
+ supplementary characters can pass this schema and still be refused by the producer. THAT DIRECTION
91
+ IS DELIBERATE: a schema looser than the producer yields a loud, readable 4xx naming the field, while
92
+ a schema tighter than the producer makes a client refuse a request the producer would have accepted —
93
+ an invisible false refusal whose symptom is indistinguishable from a server rejection (this contract
94
+ shipped exactly that defect on `SkillSpec.content` for several releases). Halving every bound to be
95
+ safe for all-supplementary input would refuse roughly half of the legal range on ordinary text, so the
96
+ bounds here state the producer's number verbatim and this convention states the unit. A client that
97
+ must pre-check exactly counts UTF-16 units itself.
98
+ (`maxLength` is the only keyword involved: it counts code points and that is not configurable. A
99
+ `pattern` could count code units — but only on validators whose regex engine is UTF-16-based and
100
+ non-unicode-mode, which is implementation-specific rather than contract-portable, and it would cost a
101
+ full regex scan of a megabyte-scale payload. The unit is therefore stated, not encoded.)
102
+ • The same unit applies to every BYTE-sounding phrase in this file unless it says UTF-8 explicitly: the
103
+ producer's guards are character/unit counts, so a non-ASCII value is NOT twice as expensive against
104
+ them. Do not pre-check these bounds with a byte length.
86
105
  x-sources:
87
106
  - ../docs/SERVICE-CORE-CONTEXT.md # §2 wire answers, §4 verb surface (authoritative producer input)
88
107
  - ../docs/CORE-PRINCIPLES.md # §2 secret discipline, §3 principal-first, §5 contract-as-anchor, §6 optional-no-default
@@ -2670,15 +2689,19 @@ paths:
2670
2689
  — the workflow run's record carries no readable `parks[]` list (the ENGINE's own park-ownership
2671
2690
  ledger, and the single source this lane joins the caller's checkpoint against). A record written by
2672
2691
  an engine older than 7.17.0 does not carry the key at all, or one entry is damaged and the whole list
2673
- cannot be read; a RESUME of that run is refused by the engine for the identical reason
2674
- (`workflow.park_truth_unreadable` / `parks_unreadable`, or `workflow.park_binding_broken`), so the
2692
+ cannot be read; the redemption keys this lane would need are therefore not on the record, so the
2675
2693
  decision is STRUCTURALLY UNDELIVERABLE and this lane refuses loudly rather than answer a 200 nothing
2676
2694
  could redeem. Body carries `runId` (the WORKFLOW run) and NO `taskId` (same two-field shape as the
2677
2695
  SEVENTH group). SDK → `DecideWorkflowParksUnreadableError` (a `DecideError` subclass, NOT a
2678
2696
  `DecideWorkflowHostError` — see its class doc for why). 🔴 RECOVERY IS THE OPPOSITE of the SEVENTH
2679
- group: there is no retry, no waiting for the host to park again, and no resuming this run at all —
2680
- it cannot be resumed by this engine. The card stays PENDING (nothing is forged); the only recovery
2681
- action is to START A NEW RUN. ⚰️ This code REPLACES the 7.71.0 code `decide.workflow_park_identity_lost`
2697
+ group: not a retry of this decide and not waiting for the host to park again — RESUME THE RUN
2698
+ (`runId`). Whether that resume is ADMITTED is the engine's answer, not this lane's: admission weighs
2699
+ the record, the journal and the record's own parked agent rows together and proves each candidate, so
2700
+ a caller must neither pre-filter recovery candidates on this code nor promise the user it will work.
2701
+ 🔴 CORRECTED at server 7.78.0 / core 7.18.0: this text used to say the engine refuses to resume such a
2702
+ record and the only recovery was to start a NEW run — core 7.18.0 falsified that, and the old advice
2703
+ abandoned recoverable runs. Once admission completes the engine writes `parks` onto the record, and
2704
+ the next decide takes the ordinary arm. The card stays PENDING throughout (nothing is forged). ⚰️ This code REPLACES the 7.71.0 code `decide.workflow_park_identity_lost`
2682
2705
  (retired at this same 7.75.0 — a store size-degrade step stopped keeping a SECOND copy of a parked
2683
2706
  row's recovery identity once `parks[]` became the engine's single source, so the state that old code
2684
2707
  named is now structurally unreachable). That retired code was NEVER modeled by this SDK (it went
@@ -5414,11 +5437,16 @@ components:
5414
5437
  Server cap 16384 chars → 400.
5415
5438
  skills:
5416
5439
  type: array
5417
- maxItems: 10
5418
5440
  description: >
5419
5441
  Per-request user skills (LIVE, service 170c384) — progressive disclosure, same
5420
5442
  mechanism as scenario skills. Merge: scenario WINS on name collision (user's dropped, warn-logged).
5421
- Caps server-enforced; violations 400 with the offending rule named.
5443
+ 🔴 server >= 7.78.1 (hard BREAKING, no alias): the ITEM-COUNT cap (was 10), the `description` 1024-char
5444
+ cap and the `name` 64-char cap are GONE — and so are their three refusal messages, so a consumer branch
5445
+ anchored on them can be deleted (a refusal turned into an acceptance: no harm in the other direction).
5446
+ The only size gate left on this face is `SkillSpec.content` <= 1 MiB (the engine's own load gate);
5447
+ bulk is otherwise bounded by the NON-skill-specific 8 MiB request-body cap. Per-turn cost belongs to the
5448
+ LISTING RENDERER, not to this gate: turn it down with `settings.skillListingBudgetFraction` /
5449
+ `settings.skillListingMaxDescChars`.
5422
5450
  items: { $ref: '#/components/schemas/SkillSpec' }
5423
5451
  reasoningEffort:
5424
5452
  type: string
@@ -5686,7 +5714,24 @@ components:
5686
5714
  service ACTIVELY interprets this bundle (task-settings.ts: permissions + permissions.defaultMode +
5687
5715
  model + outputStyle + env shipped in the 1.26.0 v1 scope; hooks was deferred at that point and wired
5688
5716
  in a later batch — both are live and interpreted today, not a raw uninterpreted passthrough);
5689
- `permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`. OPEN object.
5717
+ `permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`.
5718
+ 🔴 ON THIS SUBMIT FACE THE KEY SET IS CLOSED (server >= 7.57.0 for the sub-tree): accepted top-level keys
5719
+ are `env` / `hooks` / `model` / `outputStyle` / `permissions` / `skillListingBudgetFraction` /
5720
+ `skillListingMaxDescChars` / `ultracode` / `webSearch`, and accepted `permissions.*` keys are
5721
+ `allow` / `ask` / `defaultMode` / `deny` / `disableAutoMode`; anything else is 400 `request.body_shape`
5722
+ with the names in the refusal's `unknownKeys` / `unsupportedKeys` arrays (REFUSED, not dropped — a dropped
5723
+ key reads as "in effect" to the caller). The object stays OPEN here because the same `SemaSettings`
5724
+ schema also serves the local `settings.json` wiring, where deferred CC keys are legal.
5725
+ 🔴 `skillListingBudgetFraction` (number in (0,1]) / `skillListingMaxDescChars` (positive integer),
5726
+ server >= 7.78.1: the SKILL-LISTING RENDER BUDGET, passed to the engine with ZERO processing
5727
+ (no clamping, no conversion, no folding) — ABSENT means the key is not written at all (the server does
5728
+ NOT restate the engine defaults, CC-identical `0.01` / `1536`), and an out-of-domain value is
5729
+ 400 `request.field_invalid` on the fresh leg (the resume replay leg drops it instead of 4xx).
5730
+ ⚠️ `skillListingBudgetFraction` is a ONE-WAY knob — turning it DOWN works, turning it UP does not:
5731
+ the engine's listing budget is `min(window x fraction, 8000 bytes)` and 8000 is a STRUCTURAL ceiling of
5732
+ the delivery lane, so raising it only helps a model whose declared window is below 200 000 tokens.
5733
+ The engine's own twin code `config.skill_listing_budget_invalid` (400) is NOT reachable through this
5734
+ submit leg today — it belongs to callers that build a `TaskSpec` directly.
5690
5735
  additionalProperties: true
5691
5736
  cwd: { type: string, description: Working directory for the execution env. }
5692
5737
  selfOrchestration: { type: boolean, description: Permit S8 self-orchestration (run_workflow) inside this task. }
@@ -7409,12 +7454,19 @@ components:
7409
7454
 
7410
7455
  SkillSpec:
7411
7456
  type: object
7412
- description: A per-request skill (passed as an object; core-native TaskSpec.skills shape).
7457
+ description: >-
7458
+ A per-request skill (passed as an object; core-native TaskSpec.skills shape).
7459
+ 🔴 server >= 7.78.1: `name` / `description` carry NO length cap any more (the engine's listing renderer
7460
+ trims descriptions and falls back to name-only within its own byte budget — it never REFUSES a skill for
7461
+ being long). `content` keeps the one real gate: 1 MiB, the twin of the engine's load gate (over it the
7462
+ engine refuses the skill outright at load time, so the HTTP face answers first where the caller can read it).
7463
+ The bound is 1 048 576 **UTF-16 code units** (`MAX_SKILL_CONTENT_CHARS`); see the STRING-LENGTH UNIT
7464
+ convention in `info.description` for why this `maxLength` states that number verbatim.
7413
7465
  required: [name, description, content]
7414
7466
  properties:
7415
- name: { type: string, maxLength: 64 }
7416
- description: { type: string, maxLength: 1024 }
7417
- content: { type: string, maxLength: 32768 }
7467
+ name: { type: string, minLength: 1 }
7468
+ description: { type: string }
7469
+ content: { type: string, minLength: 1, maxLength: 1048576 }
7418
7470
 
7419
7471
  McpServerSpec:
7420
7472
  type: object
@@ -8151,6 +8203,80 @@ components:
8151
8203
  type: array
8152
8204
  description: 'The nested ctx.workflow group tree (roots only — children nest recursively). Empty when the script used no nesting.'
8153
8205
  items: { $ref: '#/components/schemas/WorkflowGroupNode' }
8206
+ parks:
8207
+ type: array
8208
+ description: >
8209
+ core 7.17.0 (#652) / server >= 7.74.0 — the engine's own PARK RESPONSIBILITY table for this run
8210
+ (detail face only; the list row has no such key).
8211
+ 🔴 `undefined` AND `[]` ARE TWO DIFFERENT THINGS: the WHOLE KEY ABSENT means an OLDER ENGINE wrote
8212
+ this record (or the store's projection stripped it), so THIS FIELD ALONE cannot prove whether parks
8213
+ exist; `parks: []` is a POSITIVE FACT — "this run has no park". Never fold one into the other; the
8214
+ reading is `"parks" in run`.
8215
+ 🔴 THIS KEY DECIDES NOTHING ABOUT WHETHER THE RUN CAN BE RESUMED — in EITHER direction. Resume
8216
+ admission is the ENGINE's: it weighs the record's `parks` (when present), the journal's `parked`
8217
+ entries and the record's own `status:"parked"` agent rows together, proves each candidate against the
8218
+ checkpoint store, and has refusal arms this read face cannot see. A consumer must not drop a run from
8219
+ its recovery candidates because this key is absent (that abandons recoverable runs), and must not
8220
+ promise recovery because it is present.
8221
+ ⚠️ This description deliberately does NOT restate the admission algorithm: a mirror of someone else's
8222
+ rule goes stale (this very text asserted "absent => the engine refuses to resume, start a new run"
8223
+ between server 7.75.0 and 7.78.0, which core 7.18.0 falsified). The ONE bit that IS decisive on this
8224
+ face is `resumeAdmissionIncomplete`.
8225
+ ⚠️ The `/decide` lane does still refuse such a record (the keys it must join against are derived in
8226
+ the admission pass and cannot be read off the record); its recovery verb is RESUME THIS RUN, not
8227
+ "start a new one". Two lanes, two predicates — never infer one from the other.
8228
+ 🔴 KEYS ONLY — not a pending count and not a status (core's words): entries are replaced by
8229
+ `callKey` and are NEVER removed when a decision lands, so a listed token may already be resolved /
8230
+ expired / reaped. Never read `parks.length` as "how many approvals are waiting" (that is
8231
+ `GET /v1/approvals`).
8232
+ 🔴 The redemption TOKEN is deliberately NOT projected: one GET would silently promote read access
8233
+ to decide access. `/decide` takes the token from the checkpoint row itself.
8234
+ items: { $ref: '#/components/schemas/WorkflowRunPark' }
8235
+ resumeAdmissionIncomplete:
8236
+ type: boolean
8237
+ enum: [true]
8238
+ description: >
8239
+ core 7.18.0 (#755) / server >= 7.78.0 — this RESUME record's admission did not complete.
8240
+ 🔴 NEVER-FALSE: core DELETES the key the moment admission completes and never writes `false`, so the
8241
+ only reading is presence. ABSENT = admission completed, OR this is not a resume at all (the normal
8242
+ case) — absence asserts nothing.
8243
+ 🔴 PRESENT => IT IS NOT A RESUME BASE: the engine refuses it whole (`admission_incomplete`) and
8244
+ derives no candidate from it. A face rendering resume candidates MUST carry this bit — dropping it
8245
+ turns a REFUSED record back into an admissible one (core's words; the projection obligation is
8246
+ word-for-word the same rank as `parks`). It does NOT fold into the absence of `parks`, which has
8247
+ its own two meanings (above).
8248
+
8249
+ WorkflowRunPark:
8250
+ # sdk 9.5.0:具名行形(同 `WiringManifestMcpEntry` 的裁定),并且**封闭** —— 服务端逐键挑出这四位,
8251
+ # 多出来的键意味着有人在别处手拼了第二份形(凭据位就是这样漏出去的)。
8252
+ type: object
8253
+ additionalProperties: false
8254
+ description: >
8255
+ One row of `WorkflowRun.parks` — the engine's park responsibility table, EXACTLY four keys because the
8256
+ server picks them key by key rather than spreading core's row.
8257
+ 🔴 THE REDEMPTION TOKEN IS STRUCTURALLY ABSENT from this shape: core's row carries one (its own note
8258
+ says never log / never URL), and the read face leaves it in the store. So "I can read the park row"
8259
+ never means "I can decide it".
8260
+ required: [callKey, sessionId, originRunId]
8261
+ properties:
8262
+ callKey: { type: string, description: "Which call this park belongs to — joins `agents[].callKey`." }
8263
+ sessionId: { type: string, description: 'The parked CHILD session the host routes by.' }
8264
+ originRunId:
8265
+ type: string
8266
+ description: >
8267
+ Which run this park was MADE on. On an inherited entry it is NOT this run's id; and while
8268
+ `originUnconfirmed` is present it says which run the material BELONGS TO, not where it was seen.
8269
+ originUnconfirmed:
8270
+ type: boolean
8271
+ enum: [true]
8272
+ description: >
8273
+ core 7.18.0 (#757) / server >= 7.78.0 — NO journal has yet read or written a `parked` entry for this
8274
+ token.
8275
+ 🔴 It PAIRS with `originRunId`: while present, that coordinate is UNCONFIRMED — core's contract,
8276
+ verbatim, "a consumer must not read it as confirmed". Projecting the coordinate and dropping this
8277
+ bit renders an unconfirmed coordinate as a confirmed one.
8278
+ 🔴 NEVER-FALSE: upstream DELETES the key at confirmation and never writes `false`, so absence =
8279
+ confirmed, and rows written before 7.78.0 read identically (a true additive).
8154
8280
 
8155
8281
  # ── B4 命名化(2026-07-31):以下 10 个 component 与 SDK resources/* 的同名导出类型 1:1;内容为
8156
8282
  # 原端点内联 schema 的字节级搬运(零语义变更),使用点换 $ref。census 封闭注随迁。
@@ -8849,13 +8975,16 @@ components:
8849
8975
  🔴 A SIXTH PROVENANCE (server >= 7.75.0 / S-252, same lane): a 409 `decide.workflow_parks_unreadable`
8850
8976
  instead of the 200 above means the run record's `parks[]` — the engine's OWN park-ownership ledger,
8851
8977
  and the only source this lane can join the caller's checkpoint against — cannot be read at all (an
8852
- engine older than 7.17.0 never wrote the key, or one entry is damaged). A resume of that run is refused
8853
- by the engine for the identical reason, so this lane refuses too rather than accept a decision it could
8854
- never actually deliver. This is NOT the same failure as `decide.workflow_host_not_parked`: recovery is
8855
- the OPPOSITE — there is no retry and no waiting for a park, because this run cannot be resumed at all.
8856
- The card stays pending; the only recovery action is to start a NEW run. SDK → a dedicated
8857
- `DecideWorkflowParksUnreadableError` (NOT `DecideWorkflowHostError` — folding the two together would
8858
- make "retry via `.runId`" look like valid advice for a run this engine can never touch again). This
8978
+ engine older than 7.17.0 never wrote the key, or one entry is damaged). The redemption keys this lane
8979
+ needs are not on such a record, so it refuses rather than accept a decision it could never deliver.
8980
+ This is NOT the same failure as `decide.workflow_host_not_parked`: recovery is neither a retry nor
8981
+ waiting for a park — it is RESUMING the run named by `runId`, and whether that resume is admitted is the
8982
+ engine's answer, not this lane's. 🔴 CORRECTED at server 7.78.0 / core 7.18.0: this text used to say the
8983
+ engine refuses to resume such a record and that the only recovery was a NEW run — core 7.18.0 falsified
8984
+ that (admission does not decide on the record's `parks` key alone), and the old advice abandoned
8985
+ recoverable runs. SDK → a dedicated `DecideWorkflowParksUnreadableError` (NOT
8986
+ `DecideWorkflowHostError` — folding the two together would make "retry this decide" look like valid
8987
+ advice, when the action is to resume the run instead). This
8859
8988
  code REPLACES the 7.71.0 code `decide.workflow_park_identity_lost`, retired at this same 7.75.0 and
8860
8989
  never modeled by any released version of this SDK.
8861
8990
  The durable approval-queue projection is UNCHANGED — no key marks the workflow origin on any read face.
@@ -9925,8 +10054,11 @@ components:
9925
10054
  `workflow_host_unknown`, `workflow_host_not_parked`, and — server >= 7.75.0 / S-252 —
9926
10055
  `workflow_parks_unreadable`): the WORKFLOW run's id. On the first three it is the RECOVERY HANDLE
9927
10056
  (wait for the host session to park and re-decide, or resume that run directly); on
9928
- `workflow_parks_unreadable` it names a run that CANNOT be resumed at all — the handle is for
9929
- identifying which run to abandon in favor of a new one, not for retrying it.
10057
+ `workflow_parks_unreadable` it is ALSO a recovery handle but for a DIFFERENT verb — resume that run
10058
+ (admission is where the engine settles its park set), do not re-issue this decide against it; whether
10059
+ that resume is admitted is the engine's answer, not this code's.
10060
+ 🔴 CORRECTED at server 7.78.0 / core 7.18.0: this text used to call it a run that cannot be resumed
10061
+ at all, to be abandoned in favour of a new one — that was true of core < 7.18.0 only.
9930
10062
  🔴 NOT interchangeable with `taskId`: the workflow-child park lane mints no taskId at all (this
9931
10063
  decision never landed on any run, so minting one would be a lie), and the other two decide lanes
9932
10064
  mint no runId. Branch by `errorCode`, never fall back from one handle to the other.
@@ -10013,6 +10145,11 @@ components:
10013
10145
  # `allOf: [SegmentEndFields]`, one wire concept, one shape). SERVER-MINTED (server >= 7.77.0): the
10014
10146
  # engine emits only `reasoning_delta`, so this frame's presence tracks the SERVER version, not core's.
10015
10147
  - $ref: '#/components/schemas/Event_reasoning_end'
10148
+ # sdk 9.5.0: the two frames core 7.18.0 added and server 7.78.0 forwards — `tool_disclosure` (#786,
10149
+ # the leg's DEFERRAL CENSUS, all three legs) and `tool_progress` (#741, a still-running call's
10150
+ # liveness tick, LIVE ONLY — never on the durable replay).
10151
+ - $ref: '#/components/schemas/Event_tool_disclosure'
10152
+ - $ref: '#/components/schemas/Event_tool_progress'
10016
10153
  discriminator:
10017
10154
  propertyName: type
10018
10155
  mapping:
@@ -10059,6 +10196,8 @@ components:
10059
10196
  text_end: '#/components/schemas/Event_text_end'
10060
10197
  tool_roster_delta: '#/components/schemas/Event_tool_roster_delta'
10061
10198
  reasoning_end: '#/components/schemas/Event_reasoning_end'
10199
+ tool_disclosure: '#/components/schemas/Event_tool_disclosure'
10200
+ tool_progress: '#/components/schemas/Event_tool_progress'
10062
10201
 
10063
10202
  # design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
10064
10203
  # the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
@@ -10361,12 +10500,25 @@ components:
10361
10500
  required: [kind]
10362
10501
  properties:
10363
10502
  kind: { const: allowed }
10503
+ classifier:
10504
+ allOf: [{ $ref: '#/components/schemas/GateClassifierAttribution' }]
10505
+ description: >
10506
+ server >= 7.78.0 (core 7.18.0 #742) — the classification's model attribution. It rides BOTH arms
10507
+ (a classification ends either way, allow or deny), which is why this key is mirrored on the deny
10508
+ arm below. Full reading in `GateClassifierAttribution`.
10364
10509
  - type: object
10365
10510
  additionalProperties: false
10366
10511
  required: [kind, deniedBy]
10367
10512
  properties:
10368
10513
  kind: { const: denied }
10369
10514
  deniedBy: { $ref: '#/components/schemas/DeniedBy' }
10515
+ classifier:
10516
+ allOf: [{ $ref: '#/components/schemas/GateClassifierAttribution' }]
10517
+ description: >
10518
+ server >= 7.78.0 (core 7.18.0 #742) — the same member as on the allow arm, same shape, same
10519
+ reading (see `GateClassifierAttribution`). Orthogonal to the sibling `cause`: `cause` says WHAT
10520
+ SHAPE this deny was, this key says WHICH MODEL decided — an inherited chain carries a `cause`
10521
+ with no attribution seat, so the two can appear apart.
10370
10522
  cause:
10371
10523
  type: string
10372
10524
  description: >-
@@ -10380,6 +10532,52 @@ components:
10380
10532
  — branch the two known words and keep a default arm. Absent = this deny was not a classifier-fault
10381
10533
  shape (policy / person / rule refused); never read absence as "classifier healthy".
10382
10534
 
10535
+ GateClassifierAttribution:
10536
+ # sdk 9.5.0:具名 —— 同一份形挂在 `GateDisposition` 的两只臂上,内联会立刻是两份会各自漂的镜像。
10537
+ type: object
10538
+ additionalProperties: false
10539
+ description: >
10540
+ WHICH MODEL decided one auto-mode classification (core 7.18.0 #742 `AutoModeClassifierRound`,
10541
+ server >= 7.78.0): the rung it was FOR, the rung that ANSWERED, and — only when the ladder actually
10542
+ MOVED — where it left, where it landed and why.
10543
+ 🔴 ABSENT READS **UNKNOWN**, NEVER "the seat answered" (core's words). "The seat answered" is this
10544
+ member PRESENT with `fallback` ABSENT — the two are distinguishable on the wire. The three causes of
10545
+ the member being absent are NOT distinguishable from the record: no classification happened; one ran on
10546
+ a decider given no candidate ladder (it can name no model); or one ran on an ANCESTOR's inherited
10547
+ chain, which carries the FORM but has no attribution seat yet.
10548
+ 🔴 NOT PERSISTED into the durable row (same as core): it is an observation of HOW this gate pass was
10549
+ decided, not a terminal fact. It appears on `tool_end.gate` and on the two raw replay legs.
10550
+ 🔴 A record failing core's invariant I6 is withheld WHOLE by the server, so every attribution that
10551
+ reaches a consumer has two non-empty model ids, `fallback.to === modelUsed`, and a cause inside the
10552
+ closed set.
10553
+ required: [modelRequested, modelUsed]
10554
+ properties:
10555
+ modelRequested: { type: string, description: "The ladder's FIRST rung — the seat this classification was for." }
10556
+ modelUsed:
10557
+ type: string
10558
+ description: >
10559
+ The rung whose answer the gate ACTED ON; where no candidate ruled, the last rung ATTEMPTED (stated,
10560
+ never implied by absence).
10561
+ fallback:
10562
+ type: object
10563
+ additionalProperties: false
10564
+ required: [from, to, cause]
10565
+ description: >
10566
+ Present EXACTLY when the ladder MOVED to reach `modelUsed`; ABSENT = the first rung answered
10567
+ ("the seat answered"). This is the one place on this shape where an absence IS an assertion.
10568
+ properties:
10569
+ from: { type: string, description: 'The rung it left.' }
10570
+ to: { type: string, description: 'The rung it arrived at — ALWAYS equal to `modelUsed` (core invariant I6).' }
10571
+ cause:
10572
+ type: string
10573
+ enum: [error, timeout, parse_error]
10574
+ description: >
10575
+ Why the ladder moved: `error` (that rung threw / rejected), `timeout` (the round-trip cap),
10576
+ `parse_error` (it answered outside the verdict contract).
10577
+ 🔴 TRULY CLOSED here, unlike most engine words on this wire: core's I6 screen calls an
10578
+ out-of-set cause a record DEFECT and the server withholds a defective record whole, so an
10579
+ unknown word reaches a consumer as an ABSENT `gate`, never as a strange value.
10580
+
10383
10581
  GateOutcome:
10384
10582
  type: object
10385
10583
  additionalProperties: false
@@ -10959,6 +11157,27 @@ components:
10959
11157
  already owns the single redaction mint point for it). The actionable cause is `errorCode`.
10960
11158
  A row missing `name` or `status` is dropped individually — the other servers'' rows still ship.
10961
11159
  items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
11160
+ hooks:
11161
+ type: array
11162
+ description: >
11163
+ core 7.18.0 (#789), server >= 7.78.0 — TENANT-visible: the hook seats THIS leg wired, one row per
11164
+ family, in core's declaration order. EFFECTIVE half only.
11165
+ 🔴 PRESENT IFF at least one seat is wired — core NEVER emits an empty array, so "the key is here"
11166
+ means non-empty. 🔴 ABSENCE READS "this leg wired no hook RECORD of its own", NOT "no hook runs":
11167
+ a delegated child with no local record can still be screened (and refused) by an ANCESTOR's
11168
+ `preToolUse` through the gate's constraint fold, which this face deliberately does not call a hook
11169
+ of this leg. A row missing `family` or `owner` is dropped INDIVIDUALLY (same rule as `mcp[]`); all
11170
+ rows dropped => the key is absent. 🔴 EXCLUDED from `configFingerprint` (the record is a per-task
11171
+ seat), so two legs with the same fingerprint and different `hooks[]` are NOT in contradiction.
11172
+ items: { $ref: '#/components/schemas/WiringManifestHookEntry' }
11173
+ lsp:
11174
+ allOf: [{ $ref: '#/components/schemas/WiringManifestLspSeam' }]
11175
+ description: >
11176
+ core 7.18.0 (#789), server >= 7.78.0 — TENANT-visible: the LSP code-intelligence seam. EFFECTIVE
11177
+ half only. PRESENT IFF a manager seat resolved (`spec.lspManager ?? deps.lspManager`); a deployment
11178
+ that wires none reports nothing at all. The server never invents `mounted: false` when it cannot
11179
+ read the bit — the whole section goes absent instead (a minted false would read as "really not
11180
+ mounted").
10962
11181
  governance:
10963
11182
  type: object
10964
11183
  additionalProperties: false
@@ -11008,6 +11227,104 @@ components:
11008
11227
  parentToolCallId: { type: string }
11009
11228
  sourceTaskId: { type: string }
11010
11229
  bgAgentId: { type: string }
11230
+ Event_tool_disclosure:
11231
+ type: object
11232
+ description: >
11233
+ This leg's DEFERRAL CENSUS (server >= 7.78.0, core 7.18.0 #786): which mounted tools ship today as
11234
+ NAME-ONLY placeholders, which ones the model has activated, and which policy decided it.
11235
+ 🔴 A DIFFERENT QUESTION FROM THE ROSTER: `wiring_manifest.tools` says WHICH TOOLS CAN BE CALLED; this
11236
+ frame says WHETHER ONE MUST BE FETCHED FIRST. A deferred name is ON the roster.
11237
+ 🔴 ALL THREE LEGS (live SSE + durable ledger + resume replay), same as `tool_roster_delta`. The leg-start
11238
+ census is the FIRST frame after `wiring_manifest`; every later frame means the census CHANGED (an
11239
+ activation landed).
11240
+ 🔴 WHOLE SNAPSHOT: `deferred` and `activated` are both complete — replace your copy, never merge.
11241
+ 🔴 ABSENCE READINGS, three of them: (a) the WHOLE FRAME absent = "this leg deferred NOTHING" (a leg with
11242
+ an empty deferred set emits no frame at all) — NOT "this build has no such face" and NOT "unknown";
11243
+ (b) `thresholdPercent` absent = under `always` / `never` NO THRESHOLD WAS EVER CONSULTED — never read it
11244
+ as "the default percent"; (c) a RESUME leg's FIRST census may carry a non-empty `activated` (the engine
11245
+ reseeded it) — never read that as "this leg activated them".
11246
+ 🔴 Half a census never reaches the wire: the server drops the WHOLE frame when any of the three required
11247
+ members is unreadable (half a table would read as "these are the only deferred ones").
11248
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
11249
+ required: [type, policy, deferred, activated]
11250
+ properties:
11251
+ type: { const: tool_disclosure }
11252
+ policy:
11253
+ type: string
11254
+ description: >
11255
+ Which arm of `RunnerDeps.toolSearch` decided this (`always` / `never` / `auto` today). READ AS OPEN:
11256
+ the server passes it through verbatim and does not enumerate, so a new word must pass through your
11257
+ default arm.
11258
+ thresholdPercent:
11259
+ type: number
11260
+ description: >
11261
+ Under `auto`: the percent of the window the candidate set had to reach. ABSENT under `always` /
11262
+ `never` because no threshold was consulted — NEVER substitute a default.
11263
+ 🔴 A FINITE NUMBER, NOT AN INTEGER: the engine's seat accepts any finite percent in [0, 100]
11264
+ (`{mode:"auto", thresholdPercent: 12.5}` is a legal deployment) and the server forwards it through
11265
+ a finite-number guard, verbatim. Typing it `integer` here would make a spec-validating consumer
11266
+ REJECT a legal frame, and the symptom ("this build has no census") is indistinguishable from the
11267
+ frame being absent. The range is NOT constrained here either: the seat enforces [0, 100] at parse
11268
+ time, the server re-derives nothing, and a bound this face does not enforce would be a second
11269
+ screen that can only disagree with the first.
11270
+ deferred:
11271
+ type: array
11272
+ items: { type: string }
11273
+ description: 'Every name-only placeholder on this leg (frozen at prepare, sorted). WHOLE snapshot.'
11274
+ activated:
11275
+ type: array
11276
+ items: { type: string }
11277
+ description: >
11278
+ The deferred names ACTIVE right now (the engine's state, not a record of commits; sorted, always a
11279
+ subset of `deferred`). WHOLE snapshot.
11280
+ # identity:两键 —— server 的 `identityFields` 只在这条臂上铸 eventId/parentToolCallId(本帧不在
11281
+ # live 腿那张四键白名单上),同 Event_compaction_outcome 的处置。
11282
+ eventId: { type: string }
11283
+ parentToolCallId: { type: string }
11284
+ Event_tool_progress:
11285
+ type: object
11286
+ description: >
11287
+ A LIVENESS TICK for a tool call that is STILL RUNNING (server >= 7.78.0, core 7.18.0 #741), minted
11288
+ between that call's `tool_start` and `tool_end` under the same ordering law as every other content
11289
+ frame; a child's ticks reach a parent under the same opt-in as those two
11290
+ (`TaskSpec.forwardSubagentEvents`).
11291
+ 🔴 LIVE ONLY — NOT PERSISTED, NOT REPLAYED (core's `tool_progress.ephemeral`, same family as
11292
+ `task_progress` / `status`): `GET /v1/runs/{id}/events` has no such arm and a reconnect back-fills
11293
+ nothing. The DURABLE fact about how long a call took is `tool_end` — core states explicitly that
11294
+ `elapsedTimeSeconds` does NOT equal the `tool_end` duration (this clock starts when the call ENTERS
11295
+ EXECUTION, earlier than a shell command's spawn).
11296
+ 🔴 THE FRAME'S ABSENCE IS NEVER EVIDENCE (core's `tool_progress.cadence`): at most one frame per second
11297
+ per call, and a call that finishes INSIDE one interval emits NONE. Never expect one per `tool_start`,
11298
+ and never use its presence to decide whether a call ran.
11299
+ 🔴 `output` / `totalLines` / `totalBytes` RIDE OR STAY AWAY TOGETHER: present = "output has been
11300
+ observed", absent = "not one byte has been observed" (`sleep 3`; or an execution environment that does
11301
+ not stream — a buffered remote `exec` never invokes the output callbacks) — NEVER "unchanged since the
11302
+ last frame". The server projects the counters only when `output` itself is readable, so a counter never
11303
+ arrives without the text it counts.
11304
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
11305
+ required: [type, toolCallId, toolName]
11306
+ properties:
11307
+ type: { const: tool_progress }
11308
+ toolCallId: { type: string, description: "The still-running call's id — joins its own `tool_start`/`tool_end`. The server drops the whole frame without it." }
11309
+ toolName: { type: string, description: 'The running tool name (the `tool_start` value verbatim).' }
11310
+ elapsedTimeSeconds:
11311
+ type: integer
11312
+ description: >
11313
+ Whole seconds this call has been running: floored, monotonic across its frames, ONE ORIGIN per call
11314
+ stamped by the mint. It does NOT equal the `tool_end` duration. ABSENT (rather than 0) when the
11315
+ server's finite-number guard could not read it — "the clock was unreadable" is not "zero seconds".
11316
+ output:
11317
+ type: string
11318
+ description: >
11319
+ The TAIL of what the command has written so far (stdout and stderr interleaved in arrival order),
11320
+ capped by the mint to a few trailing lines — a live pane, never a log.
11321
+ 🔴 UNTRUSTED RAW, exactly like `tool_end.output`: the server has passed it through the SAME
11322
+ redactor, a consumer MAY bound it further before display, and it must NEVER be re-fed to a model
11323
+ (it is a moving, truncated window; the model already holds the full result).
11324
+ totalLines: { type: integer, description: 'Newlines observed so far (>= the tail''s own line count — the tail is only a window). Rides iff `output` does.' }
11325
+ totalBytes: { type: integer, description: 'Bytes observed BEFORE the tail cap, so it keeps growing after `output` stops being able to. Rides iff `output` does.' }
11326
+ eventId: { type: string }
11327
+ parentToolCallId: { type: string }
11011
11328
  ToolRosterDelta:
11012
11329
  type: object
11013
11330
  description: >
@@ -11871,6 +12188,41 @@ components:
11871
12188
  frame and the inbox row; the `card_json` card deliberately does NOT carry it (it travels with `origin`,
11872
12189
  which is not on the card). Absence = `origin` is not that word — NOT an assertion that the rule store
11873
12190
  is healthy.
12191
+ readRootCandidate:
12192
+ type: object
12193
+ required: [dir, clearsThisAsk]
12194
+ additionalProperties: false
12195
+ properties:
12196
+ dir:
12197
+ type: string
12198
+ minLength: 1
12199
+ maxLength: 1024
12200
+ description: >-
12201
+ The ABSOLUTE, lexically-folded directory to add to this session's read roots, spelled by the read
12202
+ boundary itself — THE STRING SHOWN IS THE STRING TO ADD. A shell may render a shortened / `~` form,
12203
+ but must echo THIS string back verbatim. The server carries it whole or drops the whole key
12204
+ (a malformed shape, or a `dir` over 1024 UTF-16 code units ⇒ the key is ABSENT — never truncated,
12205
+ because a truncated directory is a DIFFERENT directory and adding it would not clear this ask;
12206
+ see the STRING-LENGTH UNIT convention in `info.description` for this `maxLength`'s counting unit).
12207
+ clearsThisAsk:
12208
+ type: boolean
12209
+ enum: [true]
12210
+ description: >-
12211
+ Shape, not value — the domain is the literal `true` only, so this seat cannot be quietly widened
12212
+ into a "might help" hint.
12213
+ description: >-
12214
+ server >= 7.78.1 (core 7.19.0), ADDITIVE, LIVE `tool_approval` FRAME ONLY — the clearing directory for
12215
+ an OUT-OF-ROOT READ ask: add `dir` to this session's read roots (the shell's existing `/add-dir`) and
12216
+ the same call stops asking.
12217
+ 🔴 READ PRESENCE ONLY, NEVER ABSENCE. Absence is NOT "nothing can be done": it also covers sensitive-path
12218
+ deny rows (pattern-judged — no root changes them), commands the read boundary cannot parse (heredoc /
12219
+ newline / command substitution / an argument used as the program), unexpanded globs, recursive walks,
12220
+ and every ask NOT raised by the read boundary — every ask on server <= 7.78.0 has this shape too.
12221
+ ⚠️ THIS FACE ONLY: `card_json`, the §2 inbox row, the `/v1/approvals` rich row and the park face carry
12222
+ NO twin of this seat in this version — a PARKED out-of-root read ask still renders that single card.
12223
+ ⚠️ Known shapes (upstream-registered, so a shell must NOT render this option as a guarantee):
12224
+ `cd X && cat Y` yields X's PARENT directory; and on a deployment that normalizes declared read roots
12225
+ (e.g. `/tmp/x` stored as `/private/tmp/x`) the same call may ask once more after the root is added.
11874
12226
  ApprovalRequestFrame:
11875
12227
  type: object
11876
12228
  description: >
@@ -12946,12 +13298,40 @@ components:
12946
13298
  usedTokens: { type: integer }
12947
13299
  windowTokens: { type: integer, description: 'autocompact 窗,不是模型上下文大小。' }
12948
13300
  compactAtTokens: { type: integer }
13301
+ sections:
13302
+ type: array
13303
+ description: >
13304
+ core 7.18.0 (#790), server >= 7.78.0 — WHAT THIS LEG'S SYSTEM PROMPT IS MADE OF: one row per
13305
+ rendered pack section.
13306
+ 🔴 NOT THE SAME COORDINATE SYSTEM AS `usedTokens` — NEVER SUBTRACT (core's contract, verbatim):
13307
+ `sections[].tokens` is a PREPARE-time estimate of the SYSTEM PROMPT from rendered character counts,
13308
+ while `usedTokens` is the engine's triggering input over the WHOLE conversation. So: never subtract
13309
+ the section sum from `usedTokens`, never compare one section against `compactAtTokens`, and never
13310
+ read a cross-frame change as "it grew" — the values FREEZE at prepare and every boundary on this leg
13311
+ re-reports the same set verbatim. The sum MAY exceed `usedTokens` (two quantities, not one ledger).
13312
+ 🔴 THE WHOLE KEY ABSENT = this leg assembled NO section IR (some providers replace the prompt
13313
+ wholesale and hand back an opaque block) — NOT "every section weighs zero". An element missing
13314
+ `id`/`tokens` is dropped individually; all dropped => the key is absent.
13315
+ items: { $ref: '#/components/schemas/ContextUsageSection' }
12949
13316
  # LIVE 腿(routes/tasks.ts)在这条臂上挂 identity;durable 腿不挂。同 turn_end 的处置:
12950
13317
  # 本地可选声明,不 allOf(不把 identity 承诺进契约面)。
12951
13318
  eventId: { type: string }
12952
13319
  parentToolCallId: { type: string }
12953
13320
  sourceTaskId: { type: string }
12954
13321
  bgAgentId: { type: string }
13322
+ ContextUsageSection:
13323
+ # sdk 9.5.0:具名而非内联 —— 与 `WiringManifestMcpEntry` 同一条裁定(一份行形,多处引用)。
13324
+ type: object
13325
+ description: >
13326
+ One rendered section of this leg's SYSTEM PROMPT (core 7.18.0 #790). `id` shares ONE vocabulary with
13327
+ `prompt.assembled`'s `sections[].id`, so the two faces join. See `Event_context_usage.sections` for the
13328
+ do-not-subtract rule — it is the load-bearing half of this shape's contract.
13329
+ additionalProperties: false
13330
+ required: [id, tokens]
13331
+ properties:
13332
+ id: { type: string, description: "The section id — same vocabulary as `prompt.assembled`'s `sections[].id`." }
13333
+ tokens: { type: integer, description: 'PREPARE-time estimate of this section, in ITS OWN coordinate system (see the parent key).' }
13334
+ kind: { type: string, description: 'The semantic slot; ABSENT when the manifest entry declared none (not "no semantics").' }
12955
13335
  Event_compaction_outcome:
12956
13336
  type: object
12957
13337
  description: >
@@ -13854,6 +14234,66 @@ components:
13854
14234
  properties:
13855
14235
  armed: { type: boolean }
13856
14236
  reason: { type: string }
14237
+ hooks:
14238
+ type: array
14239
+ description: >
14240
+ core 7.18.0 (#789), server >= 7.78.0 — the hook seats THIS leg wired, one row per family.
14241
+ EFFECTIVE half only (the diagnostics endpoint's static half never carries it). Present iff at least
14242
+ one seat is wired; absence means "no hook RECORD of this leg", never "no hook runs".
14243
+ Full per-key contract: see Event_wiring_manifest.hooks.
14244
+ items: { $ref: '#/components/schemas/WiringManifestHookEntry' }
14245
+ lsp:
14246
+ allOf: [{ $ref: '#/components/schemas/WiringManifestLspSeam' }]
14247
+ description: >
14248
+ core 7.18.0 (#789), server >= 7.78.0 — the LSP seam. EFFECTIVE half only; present iff a manager
14249
+ seat resolved. `mounted` and `lane` are INDEPENDENT facts — never fold them into one.
14250
+ Full per-key contract: see Event_wiring_manifest.lsp.
14251
+ WiringManifestHookEntry:
14252
+ # sdk 9.5.0:与 `WiringManifestMcpEntry` 同一条裁定 —— 同一份行形被 live 帧与静态诊断面两处引用,
14253
+ # 内联就是两份会各自漂的镜像,所以一开始就具名。
14254
+ type: object
14255
+ description: >
14256
+ One row of a leg's HOOK seat manifest (core 7.18.0 #789): which family, where the seat came from, and
14257
+ whether the `preToolUse` row was DECLARED observational.
14258
+ additionalProperties: false
14259
+ required: [family, owner]
14260
+ properties:
14261
+ family:
14262
+ type: string
14263
+ description: >
14264
+ The hook family. core's closed vocabulary today (`preToolUse` / `postToolUse` / `userPromptSubmit` /
14265
+ `stop` / `postToolUseFailure` / `postToolBatch` / `preCompact` / `postCompact` / `stopFailure` /
14266
+ `permissionDenied`) but READ AS OPEN: the server passes it through verbatim and does not enumerate,
14267
+ so a newly minted family must pass through your default arm.
14268
+ owner:
14269
+ type: string
14270
+ description: "Where the seat came from — core's `SeamProvenance` (`spec` / `deps`). Closed today, read as open."
14271
+ observational:
14272
+ type: boolean
14273
+ enum: [true]
14274
+ description: >
14275
+ Only on the `preToolUse` row, and only when the deployment DECLARED the face observational: it still
14276
+ runs on every call but is not folded into a delegated child's inherited constraints.
14277
+ 🔴 NEVER-FALSE — core's words: "Absence means 'not declared', never 'declared false'".
14278
+ WiringManifestLspSeam:
14279
+ # sdk 9.5.0:同上 —— live 帧与静态面共用一份段形。
14280
+ type: object
14281
+ description: >
14282
+ A leg's LSP code-intelligence seam (core 7.18.0 #789). 🔴 THE TWO BITS ARE INDEPENDENT AND MUST NOT BE
14283
+ READ AS ONE: `mounted` is the ROSTER fact (a wired manager whose `LSP` tool was excluded reports
14284
+ `false`), while `lane` arms on the manager / the hands / the opt-out and NOT on the mount — so
14285
+ `{mounted: false, lane: "diagnostics"}` is a REAL, REACHABLE state, not bad data.
14286
+ additionalProperties: false
14287
+ required: [mounted]
14288
+ properties:
14289
+ mounted:
14290
+ type: boolean
14291
+ description: 'Is the LSP tool on this leg''s roster. Required — when the server cannot read it the WHOLE section is absent (it never mints a false).'
14292
+ lane:
14293
+ type: string
14294
+ description: >
14295
+ Which lanes are live — core's closed three (`tool` / `diagnostics` / `tool_and_diagnostics`), read as
14296
+ open. ABSENT = neither lane is live (not "unknown").
13857
14297
  WiringManifestMcpEntry:
13858
14298
  # 8.9.0:从 `Event_wiring_manifest.mcp.items` 的内联形**提取**为具名 schema —— 同一份行形现在被
13859
14299
  # 两处引用(live 帧与 `WiringManifest` 的静态/两半形),内联会立刻变成两份会各自漂的镜像。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "9.4.0",
3
+ "version": "9.6.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",