@sema-agent/sdk 9.3.0 → 9.5.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
@@ -2670,15 +2670,19 @@ paths:
2670
2670
  — the workflow run's record carries no readable `parks[]` list (the ENGINE's own park-ownership
2671
2671
  ledger, and the single source this lane joins the caller's checkpoint against). A record written by
2672
2672
  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
2673
+ cannot be read; the redemption keys this lane would need are therefore not on the record, so the
2675
2674
  decision is STRUCTURALLY UNDELIVERABLE and this lane refuses loudly rather than answer a 200 nothing
2676
2675
  could redeem. Body carries `runId` (the WORKFLOW run) and NO `taskId` (same two-field shape as the
2677
2676
  SEVENTH group). SDK → `DecideWorkflowParksUnreadableError` (a `DecideError` subclass, NOT a
2678
2677
  `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`
2678
+ group: not a retry of this decide and not waiting for the host to park again — RESUME THE RUN
2679
+ (`runId`). Whether that resume is ADMITTED is the engine's answer, not this lane's: admission weighs
2680
+ the record, the journal and the record's own parked agent rows together and proves each candidate, so
2681
+ a caller must neither pre-filter recovery candidates on this code nor promise the user it will work.
2682
+ 🔴 CORRECTED at server 7.78.0 / core 7.18.0: this text used to say the engine refuses to resume such a
2683
+ record and the only recovery was to start a NEW run — core 7.18.0 falsified that, and the old advice
2684
+ abandoned recoverable runs. Once admission completes the engine writes `parks` onto the record, and
2685
+ 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
2686
  (retired at this same 7.75.0 — a store size-degrade step stopped keeping a SECOND copy of a parked
2683
2687
  row's recovery identity once `parks[]` became the engine's single source, so the state that old code
2684
2688
  named is now structurally unreachable). That retired code was NEVER modeled by this SDK (it went
@@ -8151,6 +8155,80 @@ components:
8151
8155
  type: array
8152
8156
  description: 'The nested ctx.workflow group tree (roots only — children nest recursively). Empty when the script used no nesting.'
8153
8157
  items: { $ref: '#/components/schemas/WorkflowGroupNode' }
8158
+ parks:
8159
+ type: array
8160
+ description: >
8161
+ core 7.17.0 (#652) / server >= 7.74.0 — the engine's own PARK RESPONSIBILITY table for this run
8162
+ (detail face only; the list row has no such key).
8163
+ 🔴 `undefined` AND `[]` ARE TWO DIFFERENT THINGS: the WHOLE KEY ABSENT means an OLDER ENGINE wrote
8164
+ this record (or the store's projection stripped it), so THIS FIELD ALONE cannot prove whether parks
8165
+ exist; `parks: []` is a POSITIVE FACT — "this run has no park". Never fold one into the other; the
8166
+ reading is `"parks" in run`.
8167
+ 🔴 THIS KEY DECIDES NOTHING ABOUT WHETHER THE RUN CAN BE RESUMED — in EITHER direction. Resume
8168
+ admission is the ENGINE's: it weighs the record's `parks` (when present), the journal's `parked`
8169
+ entries and the record's own `status:"parked"` agent rows together, proves each candidate against the
8170
+ checkpoint store, and has refusal arms this read face cannot see. A consumer must not drop a run from
8171
+ its recovery candidates because this key is absent (that abandons recoverable runs), and must not
8172
+ promise recovery because it is present.
8173
+ ⚠️ This description deliberately does NOT restate the admission algorithm: a mirror of someone else's
8174
+ rule goes stale (this very text asserted "absent => the engine refuses to resume, start a new run"
8175
+ between server 7.75.0 and 7.78.0, which core 7.18.0 falsified). The ONE bit that IS decisive on this
8176
+ face is `resumeAdmissionIncomplete`.
8177
+ ⚠️ The `/decide` lane does still refuse such a record (the keys it must join against are derived in
8178
+ the admission pass and cannot be read off the record); its recovery verb is RESUME THIS RUN, not
8179
+ "start a new one". Two lanes, two predicates — never infer one from the other.
8180
+ 🔴 KEYS ONLY — not a pending count and not a status (core's words): entries are replaced by
8181
+ `callKey` and are NEVER removed when a decision lands, so a listed token may already be resolved /
8182
+ expired / reaped. Never read `parks.length` as "how many approvals are waiting" (that is
8183
+ `GET /v1/approvals`).
8184
+ 🔴 The redemption TOKEN is deliberately NOT projected: one GET would silently promote read access
8185
+ to decide access. `/decide` takes the token from the checkpoint row itself.
8186
+ items: { $ref: '#/components/schemas/WorkflowRunPark' }
8187
+ resumeAdmissionIncomplete:
8188
+ type: boolean
8189
+ enum: [true]
8190
+ description: >
8191
+ core 7.18.0 (#755) / server >= 7.78.0 — this RESUME record's admission did not complete.
8192
+ 🔴 NEVER-FALSE: core DELETES the key the moment admission completes and never writes `false`, so the
8193
+ only reading is presence. ABSENT = admission completed, OR this is not a resume at all (the normal
8194
+ case) — absence asserts nothing.
8195
+ 🔴 PRESENT => IT IS NOT A RESUME BASE: the engine refuses it whole (`admission_incomplete`) and
8196
+ derives no candidate from it. A face rendering resume candidates MUST carry this bit — dropping it
8197
+ turns a REFUSED record back into an admissible one (core's words; the projection obligation is
8198
+ word-for-word the same rank as `parks`). It does NOT fold into the absence of `parks`, which has
8199
+ its own two meanings (above).
8200
+
8201
+ WorkflowRunPark:
8202
+ # sdk 9.5.0:具名行形(同 `WiringManifestMcpEntry` 的裁定),并且**封闭** —— 服务端逐键挑出这四位,
8203
+ # 多出来的键意味着有人在别处手拼了第二份形(凭据位就是这样漏出去的)。
8204
+ type: object
8205
+ additionalProperties: false
8206
+ description: >
8207
+ One row of `WorkflowRun.parks` — the engine's park responsibility table, EXACTLY four keys because the
8208
+ server picks them key by key rather than spreading core's row.
8209
+ 🔴 THE REDEMPTION TOKEN IS STRUCTURALLY ABSENT from this shape: core's row carries one (its own note
8210
+ says never log / never URL), and the read face leaves it in the store. So "I can read the park row"
8211
+ never means "I can decide it".
8212
+ required: [callKey, sessionId, originRunId]
8213
+ properties:
8214
+ callKey: { type: string, description: "Which call this park belongs to — joins `agents[].callKey`." }
8215
+ sessionId: { type: string, description: 'The parked CHILD session the host routes by.' }
8216
+ originRunId:
8217
+ type: string
8218
+ description: >
8219
+ Which run this park was MADE on. On an inherited entry it is NOT this run's id; and while
8220
+ `originUnconfirmed` is present it says which run the material BELONGS TO, not where it was seen.
8221
+ originUnconfirmed:
8222
+ type: boolean
8223
+ enum: [true]
8224
+ description: >
8225
+ core 7.18.0 (#757) / server >= 7.78.0 — NO journal has yet read or written a `parked` entry for this
8226
+ token.
8227
+ 🔴 It PAIRS with `originRunId`: while present, that coordinate is UNCONFIRMED — core's contract,
8228
+ verbatim, "a consumer must not read it as confirmed". Projecting the coordinate and dropping this
8229
+ bit renders an unconfirmed coordinate as a confirmed one.
8230
+ 🔴 NEVER-FALSE: upstream DELETES the key at confirmation and never writes `false`, so absence =
8231
+ confirmed, and rows written before 7.78.0 read identically (a true additive).
8154
8232
 
8155
8233
  # ── B4 命名化(2026-07-31):以下 10 个 component 与 SDK resources/* 的同名导出类型 1:1;内容为
8156
8234
  # 原端点内联 schema 的字节级搬运(零语义变更),使用点换 $ref。census 封闭注随迁。
@@ -8849,13 +8927,16 @@ components:
8849
8927
  🔴 A SIXTH PROVENANCE (server >= 7.75.0 / S-252, same lane): a 409 `decide.workflow_parks_unreadable`
8850
8928
  instead of the 200 above means the run record's `parks[]` — the engine's OWN park-ownership ledger,
8851
8929
  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
8930
+ engine older than 7.17.0 never wrote the key, or one entry is damaged). The redemption keys this lane
8931
+ needs are not on such a record, so it refuses rather than accept a decision it could never deliver.
8932
+ This is NOT the same failure as `decide.workflow_host_not_parked`: recovery is neither a retry nor
8933
+ waiting for a park — it is RESUMING the run named by `runId`, and whether that resume is admitted is the
8934
+ engine's answer, not this lane's. 🔴 CORRECTED at server 7.78.0 / core 7.18.0: this text used to say the
8935
+ engine refuses to resume such a record and that the only recovery was a NEW run — core 7.18.0 falsified
8936
+ that (admission does not decide on the record's `parks` key alone), and the old advice abandoned
8937
+ recoverable runs. SDK → a dedicated `DecideWorkflowParksUnreadableError` (NOT
8938
+ `DecideWorkflowHostError` — folding the two together would make "retry this decide" look like valid
8939
+ advice, when the action is to resume the run instead). This
8859
8940
  code REPLACES the 7.71.0 code `decide.workflow_park_identity_lost`, retired at this same 7.75.0 and
8860
8941
  never modeled by any released version of this SDK.
8861
8942
  The durable approval-queue projection is UNCHANGED — no key marks the workflow origin on any read face.
@@ -9566,7 +9647,11 @@ components:
9566
9647
  description: >
9567
9648
  `GET /v1/sessions/:id/mcp` envelope. `asOf` = THIS materialization moment (ISO). `degraded:true` (servers
9568
9649
  empty) ⇒ materialize timed out/failed (NOT "no MCP"). No MCP configured ⇒ `servers:[]` without `degraded`.
9650
+ server >= 7.77.0 adds the OPTIONAL top-level key `lastLegMcp` (see its own description) — zero new
9651
+ capability bit, read it as absent-by-default.
9569
9652
  # 封闭(census 批2 第三段,2026-07-30):三个铸造臂(`routes/sessions.ts` 的空面板 / 超时 degraded / 正常)键集并集恰是这 3 键;degraded 只在超时臂铸。
9653
+ # sdk 9.4.0:第 4 键 `lastLegMcp`(server ≥7.77.0)—— 同一个铸造点上按调用方裁的可选顶层键,不在
9654
+ # 面板那张短 TTL 单飞缓存里(缓存键=场景×principal,与会话无关)。
9570
9655
  required: [asOf, servers]
9571
9656
  additionalProperties: false
9572
9657
  properties:
@@ -9575,6 +9660,46 @@ components:
9575
9660
  type: array
9576
9661
  items: { $ref: '#/components/schemas/McpServerStatus' }
9577
9662
  degraded: { type: boolean }
9663
+ lastLegMcp:
9664
+ type: object
9665
+ description: >
9666
+ server >= 7.77.0 — WHAT THIS SESSION ACTUALLY MOUNTED LAST TIME: the `mcp` section of the LAST
9667
+ `wiring_manifest` in the ledger of this session's most recent run. `servers` above answers "what
9668
+ does this worker's DEFAULT SCENARIO give you"; this answers "what did my last run get". The two
9669
+ may LEGITIMATELY differ (request-level MCP injection, a non-default scenario, a server that was
9670
+ down for that leg) — 🔴 this is NOT a merge and NOT a reconciliation: the server does not infer a
9671
+ difference, mints no `divergent` verdict key, and `servers[]` is unchanged to the byte. Judging the
9672
+ difference belongs to the consumer and to people.
9673
+ 🔴 ABSENT ⇒ THE KEY IS NOT PRESENT (never `null`), and every reachable cause is INDISTINGUISHABLE
9674
+ on the wire — the only read is `"lastLegMcp" in panel`: this session has not run a leg yet / that
9675
+ leg's ledger rows are outside the retention window / that leg never pushed a `wiring_manifest`
9676
+ (engines <= 7.8) / that frame carried no roster section / this deployment has no runs ledger / the
9677
+ ledger read failed or timed out (the panel still answers 200 — an additive optional key must not
9678
+ give an existing read face a new way to fall over) / the leg has an owner who is not the caller and
9679
+ the caller is not an explicit operator (the key is withheld; the panel is NOT 404'd).
9680
+ ⚠️ `"mcp": []` is NOT absence — a present empty array means "that leg declared no servers".
9681
+ 🔴 FRESHNESS: the panel body rides a short-TTL single-flight cache keyed by scenario x principal
9682
+ (session-independent); this key is DELIBERATELY OUTSIDE it, so within one response `asOf` may come
9683
+ from the cache while this key was read just now — both timestamps are honest about their own half.
9684
+ additionalProperties: false
9685
+ required: [runId, at, mcp]
9686
+ properties:
9687
+ runId:
9688
+ type: string
9689
+ description: >
9690
+ That leg's run id (the anchor for GET /v1/runs/{id}/events; same reading as the session list
9691
+ face's `lastRunId` — both always point at the same row).
9692
+ at:
9693
+ type: string
9694
+ description: "The LEDGER timestamp (ISO) of that `wiring_manifest` event — NOT the panel's `asOf`."
9695
+ mcp:
9696
+ type: array
9697
+ description: >
9698
+ Verbatim that event's `data.mcp` — the SAME shape as `Event_wiring_manifest.mcp` down to the
9699
+ byte (zero new shape: one payload, a second wire). Rows missing `name`/`status` were already
9700
+ dropped individually at projection time, and the engine's free-text `error` was already
9701
+ stripped there.
9702
+ items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
9578
9703
 
9579
9704
  SessionMemoryStatus:
9580
9705
  type: object
@@ -9881,8 +10006,11 @@ components:
9881
10006
  `workflow_host_unknown`, `workflow_host_not_parked`, and — server >= 7.75.0 / S-252 —
9882
10007
  `workflow_parks_unreadable`): the WORKFLOW run's id. On the first three it is the RECOVERY HANDLE
9883
10008
  (wait for the host session to park and re-decide, or resume that run directly); on
9884
- `workflow_parks_unreadable` it names a run that CANNOT be resumed at all — the handle is for
9885
- identifying which run to abandon in favor of a new one, not for retrying it.
10009
+ `workflow_parks_unreadable` it is ALSO a recovery handle but for a DIFFERENT verb — resume that run
10010
+ (admission is where the engine settles its park set), do not re-issue this decide against it; whether
10011
+ that resume is admitted is the engine's answer, not this code's.
10012
+ 🔴 CORRECTED at server 7.78.0 / core 7.18.0: this text used to call it a run that cannot be resumed
10013
+ at all, to be abandoned in favour of a new one — that was true of core < 7.18.0 only.
9886
10014
  🔴 NOT interchangeable with `taskId`: the workflow-child park lane mints no taskId at all (this
9887
10015
  decision never landed on any run, so minting one would be a lie), and the other two decide lanes
9888
10016
  mint no runId. Branch by `errorCode`, never fall back from one handle to the other.
@@ -9965,6 +10093,15 @@ components:
9965
10093
  # (server `routes/tasks.ts` mint points x this union), which now runs on every build.
9966
10094
  - $ref: '#/components/schemas/Event_text_end'
9967
10095
  - $ref: '#/components/schemas/Event_tool_roster_delta'
10096
+ # sdk 9.4.0: `reasoning_end` — the REASONING twin of `text_end`, BYTE-IDENTICAL in shape (both are
10097
+ # `allOf: [SegmentEndFields]`, one wire concept, one shape). SERVER-MINTED (server >= 7.77.0): the
10098
+ # engine emits only `reasoning_delta`, so this frame's presence tracks the SERVER version, not core's.
10099
+ - $ref: '#/components/schemas/Event_reasoning_end'
10100
+ # sdk 9.5.0: the two frames core 7.18.0 added and server 7.78.0 forwards — `tool_disclosure` (#786,
10101
+ # the leg's DEFERRAL CENSUS, all three legs) and `tool_progress` (#741, a still-running call's
10102
+ # liveness tick, LIVE ONLY — never on the durable replay).
10103
+ - $ref: '#/components/schemas/Event_tool_disclosure'
10104
+ - $ref: '#/components/schemas/Event_tool_progress'
9968
10105
  discriminator:
9969
10106
  propertyName: type
9970
10107
  mapping:
@@ -10010,6 +10147,9 @@ components:
10010
10147
  approval_revoke: '#/components/schemas/Event_approval_revoke'
10011
10148
  text_end: '#/components/schemas/Event_text_end'
10012
10149
  tool_roster_delta: '#/components/schemas/Event_tool_roster_delta'
10150
+ reasoning_end: '#/components/schemas/Event_reasoning_end'
10151
+ tool_disclosure: '#/components/schemas/Event_tool_disclosure'
10152
+ tool_progress: '#/components/schemas/Event_tool_progress'
10013
10153
 
10014
10154
  # design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
10015
10155
  # the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
@@ -10120,6 +10260,35 @@ components:
10120
10260
  parentToolCallId: { type: string }
10121
10261
  sourceTaskId: { type: string }
10122
10262
  bgAgentId: { type: string }
10263
+ Event_reasoning_end:
10264
+ type: object
10265
+ description: >
10266
+ LIVE stream only (server >= 7.77.0) — the END-OF-SEGMENT signal for one REASONING segment, the twin of
10267
+ `text_end`. 🔴 SERVER-MINTED: core emits only `reasoning_delta`, so this frame's presence tracks the
10268
+ SERVER version, not core's. `content` is the segment's AUTHORITATIVE full text (the whole segment's
10269
+ deltas, through the SAME redactor as the durable leg) — and that is the reason the frame exists: a
10270
+ per-chunk `reasoning_delta` cannot see a credential that straddles two chunks, only the whole segment
10271
+ can. CONSUME IT BY REPLACING the deltas accumulated for that identity stream, not as a mere boundary
10272
+ signal; `reasoning_delta` itself is byte-for-byte unchanged.
10273
+ 🔴 EMPTY SEGMENTS ARE NOT SENT (empty or all-whitespace): absence means "this segment has no
10274
+ replacement text", never "the segment has not ended" — a blank authoritative segment would make a
10275
+ consumer wipe what it already rendered.
10276
+ 🔴 Segments are accounted PER IDENTITY STREAM (parentToolCallId / sourceTaskId / bgAgentId): on one SSE
10277
+ connection concurrent sub-agents interleave, so another stream's frame is NOT your boundary.
10278
+ 🔴 The durable leg does not append it (the batched `reasoning` row carries the same final text, byte for
10279
+ byte), so GET /v1/runs/{id}/events never replays it.
10280
+ Shape is IDENTICAL to Event_text_end (one wire concept, one shape — the server mints both through one
10281
+ `segmentEndFields`); the only difference between the two frames is the empty-segment predicate.
10282
+ allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
10283
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
10284
+ required: [type, content]
10285
+ properties:
10286
+ type: { const: reasoning_end }
10287
+ content: { type: string }
10288
+ eventId: { type: string }
10289
+ parentToolCallId: { type: string }
10290
+ sourceTaskId: { type: string }
10291
+ bgAgentId: { type: string }
10123
10292
  Event_tool_start:
10124
10293
  type: object
10125
10294
  allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
@@ -10283,12 +10452,25 @@ components:
10283
10452
  required: [kind]
10284
10453
  properties:
10285
10454
  kind: { const: allowed }
10455
+ classifier:
10456
+ allOf: [{ $ref: '#/components/schemas/GateClassifierAttribution' }]
10457
+ description: >
10458
+ server >= 7.78.0 (core 7.18.0 #742) — the classification's model attribution. It rides BOTH arms
10459
+ (a classification ends either way, allow or deny), which is why this key is mirrored on the deny
10460
+ arm below. Full reading in `GateClassifierAttribution`.
10286
10461
  - type: object
10287
10462
  additionalProperties: false
10288
10463
  required: [kind, deniedBy]
10289
10464
  properties:
10290
10465
  kind: { const: denied }
10291
10466
  deniedBy: { $ref: '#/components/schemas/DeniedBy' }
10467
+ classifier:
10468
+ allOf: [{ $ref: '#/components/schemas/GateClassifierAttribution' }]
10469
+ description: >
10470
+ server >= 7.78.0 (core 7.18.0 #742) — the same member as on the allow arm, same shape, same
10471
+ reading (see `GateClassifierAttribution`). Orthogonal to the sibling `cause`: `cause` says WHAT
10472
+ SHAPE this deny was, this key says WHICH MODEL decided — an inherited chain carries a `cause`
10473
+ with no attribution seat, so the two can appear apart.
10292
10474
  cause:
10293
10475
  type: string
10294
10476
  description: >-
@@ -10302,6 +10484,52 @@ components:
10302
10484
  — branch the two known words and keep a default arm. Absent = this deny was not a classifier-fault
10303
10485
  shape (policy / person / rule refused); never read absence as "classifier healthy".
10304
10486
 
10487
+ GateClassifierAttribution:
10488
+ # sdk 9.5.0:具名 —— 同一份形挂在 `GateDisposition` 的两只臂上,内联会立刻是两份会各自漂的镜像。
10489
+ type: object
10490
+ additionalProperties: false
10491
+ description: >
10492
+ WHICH MODEL decided one auto-mode classification (core 7.18.0 #742 `AutoModeClassifierRound`,
10493
+ server >= 7.78.0): the rung it was FOR, the rung that ANSWERED, and — only when the ladder actually
10494
+ MOVED — where it left, where it landed and why.
10495
+ 🔴 ABSENT READS **UNKNOWN**, NEVER "the seat answered" (core's words). "The seat answered" is this
10496
+ member PRESENT with `fallback` ABSENT — the two are distinguishable on the wire. The three causes of
10497
+ the member being absent are NOT distinguishable from the record: no classification happened; one ran on
10498
+ a decider given no candidate ladder (it can name no model); or one ran on an ANCESTOR's inherited
10499
+ chain, which carries the FORM but has no attribution seat yet.
10500
+ 🔴 NOT PERSISTED into the durable row (same as core): it is an observation of HOW this gate pass was
10501
+ decided, not a terminal fact. It appears on `tool_end.gate` and on the two raw replay legs.
10502
+ 🔴 A record failing core's invariant I6 is withheld WHOLE by the server, so every attribution that
10503
+ reaches a consumer has two non-empty model ids, `fallback.to === modelUsed`, and a cause inside the
10504
+ closed set.
10505
+ required: [modelRequested, modelUsed]
10506
+ properties:
10507
+ modelRequested: { type: string, description: "The ladder's FIRST rung — the seat this classification was for." }
10508
+ modelUsed:
10509
+ type: string
10510
+ description: >
10511
+ The rung whose answer the gate ACTED ON; where no candidate ruled, the last rung ATTEMPTED (stated,
10512
+ never implied by absence).
10513
+ fallback:
10514
+ type: object
10515
+ additionalProperties: false
10516
+ required: [from, to, cause]
10517
+ description: >
10518
+ Present EXACTLY when the ladder MOVED to reach `modelUsed`; ABSENT = the first rung answered
10519
+ ("the seat answered"). This is the one place on this shape where an absence IS an assertion.
10520
+ properties:
10521
+ from: { type: string, description: 'The rung it left.' }
10522
+ to: { type: string, description: 'The rung it arrived at — ALWAYS equal to `modelUsed` (core invariant I6).' }
10523
+ cause:
10524
+ type: string
10525
+ enum: [error, timeout, parse_error]
10526
+ description: >
10527
+ Why the ladder moved: `error` (that rung threw / rejected), `timeout` (the round-trip cap),
10528
+ `parse_error` (it answered outside the verdict contract).
10529
+ 🔴 TRULY CLOSED here, unlike most engine words on this wire: core's I6 screen calls an
10530
+ out-of-set cause a record DEFECT and the server withholds a defective record whole, so an
10531
+ unknown word reaches a consumer as an ABSENT `gate`, never as a strange value.
10532
+
10305
10533
  GateOutcome:
10306
10534
  type: object
10307
10535
  additionalProperties: false
@@ -10881,6 +11109,27 @@ components:
10881
11109
  already owns the single redaction mint point for it). The actionable cause is `errorCode`.
10882
11110
  A row missing `name` or `status` is dropped individually — the other servers'' rows still ship.
10883
11111
  items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
11112
+ hooks:
11113
+ type: array
11114
+ description: >
11115
+ core 7.18.0 (#789), server >= 7.78.0 — TENANT-visible: the hook seats THIS leg wired, one row per
11116
+ family, in core's declaration order. EFFECTIVE half only.
11117
+ 🔴 PRESENT IFF at least one seat is wired — core NEVER emits an empty array, so "the key is here"
11118
+ means non-empty. 🔴 ABSENCE READS "this leg wired no hook RECORD of its own", NOT "no hook runs":
11119
+ a delegated child with no local record can still be screened (and refused) by an ANCESTOR's
11120
+ `preToolUse` through the gate's constraint fold, which this face deliberately does not call a hook
11121
+ of this leg. A row missing `family` or `owner` is dropped INDIVIDUALLY (same rule as `mcp[]`); all
11122
+ rows dropped => the key is absent. 🔴 EXCLUDED from `configFingerprint` (the record is a per-task
11123
+ seat), so two legs with the same fingerprint and different `hooks[]` are NOT in contradiction.
11124
+ items: { $ref: '#/components/schemas/WiringManifestHookEntry' }
11125
+ lsp:
11126
+ allOf: [{ $ref: '#/components/schemas/WiringManifestLspSeam' }]
11127
+ description: >
11128
+ core 7.18.0 (#789), server >= 7.78.0 — TENANT-visible: the LSP code-intelligence seam. EFFECTIVE
11129
+ half only. PRESENT IFF a manager seat resolved (`spec.lspManager ?? deps.lspManager`); a deployment
11130
+ that wires none reports nothing at all. The server never invents `mounted: false` when it cannot
11131
+ read the bit — the whole section goes absent instead (a minted false would read as "really not
11132
+ mounted").
10884
11133
  governance:
10885
11134
  type: object
10886
11135
  additionalProperties: false
@@ -10930,6 +11179,104 @@ components:
10930
11179
  parentToolCallId: { type: string }
10931
11180
  sourceTaskId: { type: string }
10932
11181
  bgAgentId: { type: string }
11182
+ Event_tool_disclosure:
11183
+ type: object
11184
+ description: >
11185
+ This leg's DEFERRAL CENSUS (server >= 7.78.0, core 7.18.0 #786): which mounted tools ship today as
11186
+ NAME-ONLY placeholders, which ones the model has activated, and which policy decided it.
11187
+ 🔴 A DIFFERENT QUESTION FROM THE ROSTER: `wiring_manifest.tools` says WHICH TOOLS CAN BE CALLED; this
11188
+ frame says WHETHER ONE MUST BE FETCHED FIRST. A deferred name is ON the roster.
11189
+ 🔴 ALL THREE LEGS (live SSE + durable ledger + resume replay), same as `tool_roster_delta`. The leg-start
11190
+ census is the FIRST frame after `wiring_manifest`; every later frame means the census CHANGED (an
11191
+ activation landed).
11192
+ 🔴 WHOLE SNAPSHOT: `deferred` and `activated` are both complete — replace your copy, never merge.
11193
+ 🔴 ABSENCE READINGS, three of them: (a) the WHOLE FRAME absent = "this leg deferred NOTHING" (a leg with
11194
+ an empty deferred set emits no frame at all) — NOT "this build has no such face" and NOT "unknown";
11195
+ (b) `thresholdPercent` absent = under `always` / `never` NO THRESHOLD WAS EVER CONSULTED — never read it
11196
+ as "the default percent"; (c) a RESUME leg's FIRST census may carry a non-empty `activated` (the engine
11197
+ reseeded it) — never read that as "this leg activated them".
11198
+ 🔴 Half a census never reaches the wire: the server drops the WHOLE frame when any of the three required
11199
+ members is unreadable (half a table would read as "these are the only deferred ones").
11200
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
11201
+ required: [type, policy, deferred, activated]
11202
+ properties:
11203
+ type: { const: tool_disclosure }
11204
+ policy:
11205
+ type: string
11206
+ description: >
11207
+ Which arm of `RunnerDeps.toolSearch` decided this (`always` / `never` / `auto` today). READ AS OPEN:
11208
+ the server passes it through verbatim and does not enumerate, so a new word must pass through your
11209
+ default arm.
11210
+ thresholdPercent:
11211
+ type: number
11212
+ description: >
11213
+ Under `auto`: the percent of the window the candidate set had to reach. ABSENT under `always` /
11214
+ `never` because no threshold was consulted — NEVER substitute a default.
11215
+ 🔴 A FINITE NUMBER, NOT AN INTEGER: the engine's seat accepts any finite percent in [0, 100]
11216
+ (`{mode:"auto", thresholdPercent: 12.5}` is a legal deployment) and the server forwards it through
11217
+ a finite-number guard, verbatim. Typing it `integer` here would make a spec-validating consumer
11218
+ REJECT a legal frame, and the symptom ("this build has no census") is indistinguishable from the
11219
+ frame being absent. The range is NOT constrained here either: the seat enforces [0, 100] at parse
11220
+ time, the server re-derives nothing, and a bound this face does not enforce would be a second
11221
+ screen that can only disagree with the first.
11222
+ deferred:
11223
+ type: array
11224
+ items: { type: string }
11225
+ description: 'Every name-only placeholder on this leg (frozen at prepare, sorted). WHOLE snapshot.'
11226
+ activated:
11227
+ type: array
11228
+ items: { type: string }
11229
+ description: >
11230
+ The deferred names ACTIVE right now (the engine's state, not a record of commits; sorted, always a
11231
+ subset of `deferred`). WHOLE snapshot.
11232
+ # identity:两键 —— server 的 `identityFields` 只在这条臂上铸 eventId/parentToolCallId(本帧不在
11233
+ # live 腿那张四键白名单上),同 Event_compaction_outcome 的处置。
11234
+ eventId: { type: string }
11235
+ parentToolCallId: { type: string }
11236
+ Event_tool_progress:
11237
+ type: object
11238
+ description: >
11239
+ A LIVENESS TICK for a tool call that is STILL RUNNING (server >= 7.78.0, core 7.18.0 #741), minted
11240
+ between that call's `tool_start` and `tool_end` under the same ordering law as every other content
11241
+ frame; a child's ticks reach a parent under the same opt-in as those two
11242
+ (`TaskSpec.forwardSubagentEvents`).
11243
+ 🔴 LIVE ONLY — NOT PERSISTED, NOT REPLAYED (core's `tool_progress.ephemeral`, same family as
11244
+ `task_progress` / `status`): `GET /v1/runs/{id}/events` has no such arm and a reconnect back-fills
11245
+ nothing. The DURABLE fact about how long a call took is `tool_end` — core states explicitly that
11246
+ `elapsedTimeSeconds` does NOT equal the `tool_end` duration (this clock starts when the call ENTERS
11247
+ EXECUTION, earlier than a shell command's spawn).
11248
+ 🔴 THE FRAME'S ABSENCE IS NEVER EVIDENCE (core's `tool_progress.cadence`): at most one frame per second
11249
+ per call, and a call that finishes INSIDE one interval emits NONE. Never expect one per `tool_start`,
11250
+ and never use its presence to decide whether a call ran.
11251
+ 🔴 `output` / `totalLines` / `totalBytes` RIDE OR STAY AWAY TOGETHER: present = "output has been
11252
+ observed", absent = "not one byte has been observed" (`sleep 3`; or an execution environment that does
11253
+ not stream — a buffered remote `exec` never invokes the output callbacks) — NEVER "unchanged since the
11254
+ last frame". The server projects the counters only when `output` itself is readable, so a counter never
11255
+ arrives without the text it counts.
11256
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
11257
+ required: [type, toolCallId, toolName]
11258
+ properties:
11259
+ type: { const: tool_progress }
11260
+ 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." }
11261
+ toolName: { type: string, description: 'The running tool name (the `tool_start` value verbatim).' }
11262
+ elapsedTimeSeconds:
11263
+ type: integer
11264
+ description: >
11265
+ Whole seconds this call has been running: floored, monotonic across its frames, ONE ORIGIN per call
11266
+ stamped by the mint. It does NOT equal the `tool_end` duration. ABSENT (rather than 0) when the
11267
+ server's finite-number guard could not read it — "the clock was unreadable" is not "zero seconds".
11268
+ output:
11269
+ type: string
11270
+ description: >
11271
+ The TAIL of what the command has written so far (stdout and stderr interleaved in arrival order),
11272
+ capped by the mint to a few trailing lines — a live pane, never a log.
11273
+ 🔴 UNTRUSTED RAW, exactly like `tool_end.output`: the server has passed it through the SAME
11274
+ redactor, a consumer MAY bound it further before display, and it must NEVER be re-fed to a model
11275
+ (it is a moving, truncated window; the model already holds the full result).
11276
+ totalLines: { type: integer, description: 'Newlines observed so far (>= the tail''s own line count — the tail is only a window). Rides iff `output` does.' }
11277
+ totalBytes: { type: integer, description: 'Bytes observed BEFORE the tail cap, so it keeps growing after `output` stops being able to. Rides iff `output` does.' }
11278
+ eventId: { type: string }
11279
+ parentToolCallId: { type: string }
10933
11280
  ToolRosterDelta:
10934
11281
  type: object
10935
11282
  description: >
@@ -12868,12 +13215,40 @@ components:
12868
13215
  usedTokens: { type: integer }
12869
13216
  windowTokens: { type: integer, description: 'autocompact 窗,不是模型上下文大小。' }
12870
13217
  compactAtTokens: { type: integer }
13218
+ sections:
13219
+ type: array
13220
+ description: >
13221
+ core 7.18.0 (#790), server >= 7.78.0 — WHAT THIS LEG'S SYSTEM PROMPT IS MADE OF: one row per
13222
+ rendered pack section.
13223
+ 🔴 NOT THE SAME COORDINATE SYSTEM AS `usedTokens` — NEVER SUBTRACT (core's contract, verbatim):
13224
+ `sections[].tokens` is a PREPARE-time estimate of the SYSTEM PROMPT from rendered character counts,
13225
+ while `usedTokens` is the engine's triggering input over the WHOLE conversation. So: never subtract
13226
+ the section sum from `usedTokens`, never compare one section against `compactAtTokens`, and never
13227
+ read a cross-frame change as "it grew" — the values FREEZE at prepare and every boundary on this leg
13228
+ re-reports the same set verbatim. The sum MAY exceed `usedTokens` (two quantities, not one ledger).
13229
+ 🔴 THE WHOLE KEY ABSENT = this leg assembled NO section IR (some providers replace the prompt
13230
+ wholesale and hand back an opaque block) — NOT "every section weighs zero". An element missing
13231
+ `id`/`tokens` is dropped individually; all dropped => the key is absent.
13232
+ items: { $ref: '#/components/schemas/ContextUsageSection' }
12871
13233
  # LIVE 腿(routes/tasks.ts)在这条臂上挂 identity;durable 腿不挂。同 turn_end 的处置:
12872
13234
  # 本地可选声明,不 allOf(不把 identity 承诺进契约面)。
12873
13235
  eventId: { type: string }
12874
13236
  parentToolCallId: { type: string }
12875
13237
  sourceTaskId: { type: string }
12876
13238
  bgAgentId: { type: string }
13239
+ ContextUsageSection:
13240
+ # sdk 9.5.0:具名而非内联 —— 与 `WiringManifestMcpEntry` 同一条裁定(一份行形,多处引用)。
13241
+ type: object
13242
+ description: >
13243
+ One rendered section of this leg's SYSTEM PROMPT (core 7.18.0 #790). `id` shares ONE vocabulary with
13244
+ `prompt.assembled`'s `sections[].id`, so the two faces join. See `Event_context_usage.sections` for the
13245
+ do-not-subtract rule — it is the load-bearing half of this shape's contract.
13246
+ additionalProperties: false
13247
+ required: [id, tokens]
13248
+ properties:
13249
+ id: { type: string, description: "The section id — same vocabulary as `prompt.assembled`'s `sections[].id`." }
13250
+ tokens: { type: integer, description: 'PREPARE-time estimate of this section, in ITS OWN coordinate system (see the parent key).' }
13251
+ kind: { type: string, description: 'The semantic slot; ABSENT when the manifest entry declared none (not "no semantics").' }
12877
13252
  Event_compaction_outcome:
12878
13253
  type: object
12879
13254
  description: >
@@ -13776,6 +14151,66 @@ components:
13776
14151
  properties:
13777
14152
  armed: { type: boolean }
13778
14153
  reason: { type: string }
14154
+ hooks:
14155
+ type: array
14156
+ description: >
14157
+ core 7.18.0 (#789), server >= 7.78.0 — the hook seats THIS leg wired, one row per family.
14158
+ EFFECTIVE half only (the diagnostics endpoint's static half never carries it). Present iff at least
14159
+ one seat is wired; absence means "no hook RECORD of this leg", never "no hook runs".
14160
+ Full per-key contract: see Event_wiring_manifest.hooks.
14161
+ items: { $ref: '#/components/schemas/WiringManifestHookEntry' }
14162
+ lsp:
14163
+ allOf: [{ $ref: '#/components/schemas/WiringManifestLspSeam' }]
14164
+ description: >
14165
+ core 7.18.0 (#789), server >= 7.78.0 — the LSP seam. EFFECTIVE half only; present iff a manager
14166
+ seat resolved. `mounted` and `lane` are INDEPENDENT facts — never fold them into one.
14167
+ Full per-key contract: see Event_wiring_manifest.lsp.
14168
+ WiringManifestHookEntry:
14169
+ # sdk 9.5.0:与 `WiringManifestMcpEntry` 同一条裁定 —— 同一份行形被 live 帧与静态诊断面两处引用,
14170
+ # 内联就是两份会各自漂的镜像,所以一开始就具名。
14171
+ type: object
14172
+ description: >
14173
+ One row of a leg's HOOK seat manifest (core 7.18.0 #789): which family, where the seat came from, and
14174
+ whether the `preToolUse` row was DECLARED observational.
14175
+ additionalProperties: false
14176
+ required: [family, owner]
14177
+ properties:
14178
+ family:
14179
+ type: string
14180
+ description: >
14181
+ The hook family. core's closed vocabulary today (`preToolUse` / `postToolUse` / `userPromptSubmit` /
14182
+ `stop` / `postToolUseFailure` / `postToolBatch` / `preCompact` / `postCompact` / `stopFailure` /
14183
+ `permissionDenied`) but READ AS OPEN: the server passes it through verbatim and does not enumerate,
14184
+ so a newly minted family must pass through your default arm.
14185
+ owner:
14186
+ type: string
14187
+ description: "Where the seat came from — core's `SeamProvenance` (`spec` / `deps`). Closed today, read as open."
14188
+ observational:
14189
+ type: boolean
14190
+ enum: [true]
14191
+ description: >
14192
+ Only on the `preToolUse` row, and only when the deployment DECLARED the face observational: it still
14193
+ runs on every call but is not folded into a delegated child's inherited constraints.
14194
+ 🔴 NEVER-FALSE — core's words: "Absence means 'not declared', never 'declared false'".
14195
+ WiringManifestLspSeam:
14196
+ # sdk 9.5.0:同上 —— live 帧与静态面共用一份段形。
14197
+ type: object
14198
+ description: >
14199
+ A leg's LSP code-intelligence seam (core 7.18.0 #789). 🔴 THE TWO BITS ARE INDEPENDENT AND MUST NOT BE
14200
+ READ AS ONE: `mounted` is the ROSTER fact (a wired manager whose `LSP` tool was excluded reports
14201
+ `false`), while `lane` arms on the manager / the hands / the opt-out and NOT on the mount — so
14202
+ `{mounted: false, lane: "diagnostics"}` is a REAL, REACHABLE state, not bad data.
14203
+ additionalProperties: false
14204
+ required: [mounted]
14205
+ properties:
14206
+ mounted:
14207
+ type: boolean
14208
+ 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).'
14209
+ lane:
14210
+ type: string
14211
+ description: >
14212
+ Which lanes are live — core's closed three (`tool` / `diagnostics` / `tool_and_diagnostics`), read as
14213
+ open. ABSENT = neither lane is live (not "unknown").
13779
14214
  WiringManifestMcpEntry:
13780
14215
  # 8.9.0:从 `Event_wiring_manifest.mcp.items` 的内联形**提取**为具名 schema —— 同一份行形现在被
13781
14216
  # 两处引用(live 帧与 `WiringManifest` 的静态/两半形),内联会立刻变成两份会各自漂的镜像。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "9.3.0",
3
+ "version": "9.5.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",