@sema-agent/sdk 4.2.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +22 -0
  2. package/dist/control-types.d.ts +22 -4
  3. package/dist/control-types.d.ts.map +1 -1
  4. package/dist/control-types.js +3 -1
  5. package/dist/control-types.js.map +1 -1
  6. package/dist/health.d.ts +76 -7
  7. package/dist/health.d.ts.map +1 -1
  8. package/dist/health.js +40 -2
  9. package/dist/health.js.map +1 -1
  10. package/dist/index.d.ts +10 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/registry/auth.d.ts.map +1 -1
  15. package/dist/registry/auth.js +3 -1
  16. package/dist/registry/auth.js.map +1 -1
  17. package/dist/registry/health.d.ts +6 -1
  18. package/dist/registry/health.d.ts.map +1 -1
  19. package/dist/registry/health.js +6 -4
  20. package/dist/registry/health.js.map +1 -1
  21. package/dist/registry/index.d.ts +7 -1
  22. package/dist/registry/index.d.ts.map +1 -1
  23. package/dist/registry/index.js +7 -1
  24. package/dist/registry/index.js.map +1 -1
  25. package/dist/registry/me-config.d.ts +2 -2
  26. package/dist/registry/me-config.d.ts.map +1 -1
  27. package/dist/registry/me-config.js +1 -1
  28. package/dist/registry/me-config.js.map +1 -1
  29. package/dist/registry/scope-config-feedback.d.ts +3 -3
  30. package/dist/registry/scope-config-feedback.d.ts.map +1 -1
  31. package/dist/registry/scope-config-feedback.js +3 -3
  32. package/dist/registry/scope-config-feedback.js.map +1 -1
  33. package/dist/resources/attachments.d.ts +9 -2
  34. package/dist/resources/attachments.d.ts.map +1 -1
  35. package/dist/resources/attachments.js.map +1 -1
  36. package/dist/resources/control/secrets.d.ts +10 -1
  37. package/dist/resources/control/secrets.d.ts.map +1 -1
  38. package/dist/resources/control/secrets.js +10 -1
  39. package/dist/resources/control/secrets.js.map +1 -1
  40. package/dist/resources/fleet.d.ts +3 -2
  41. package/dist/resources/fleet.d.ts.map +1 -1
  42. package/dist/resources/fleet.js.map +1 -1
  43. package/dist/resources/images.d.ts +11 -2
  44. package/dist/resources/images.d.ts.map +1 -1
  45. package/dist/resources/images.js +20 -5
  46. package/dist/resources/images.js.map +1 -1
  47. package/dist/resources/leader.d.ts +7 -3
  48. package/dist/resources/leader.d.ts.map +1 -1
  49. package/dist/resources/leader.js +7 -3
  50. package/dist/resources/leader.js.map +1 -1
  51. package/dist/resources/ops.d.ts +29 -2
  52. package/dist/resources/ops.d.ts.map +1 -1
  53. package/dist/resources/ops.js +6 -1
  54. package/dist/resources/ops.js.map +1 -1
  55. package/dist/resources/session-sync.d.ts +77 -17
  56. package/dist/resources/session-sync.d.ts.map +1 -1
  57. package/dist/resources/session-sync.js +81 -20
  58. package/dist/resources/session-sync.js.map +1 -1
  59. package/dist/resources/sessions.d.ts +72 -12
  60. package/dist/resources/sessions.d.ts.map +1 -1
  61. package/dist/resources/sessions.js +29 -6
  62. package/dist/resources/sessions.js.map +1 -1
  63. package/dist/resources/trace.d.ts +17 -3
  64. package/dist/resources/trace.d.ts.map +1 -1
  65. package/dist/resources/trace.js +25 -5
  66. package/dist/resources/trace.js.map +1 -1
  67. package/dist/resources/workflows.d.ts +42 -10
  68. package/dist/resources/workflows.d.ts.map +1 -1
  69. package/dist/resources/workflows.js +46 -9
  70. package/dist/resources/workflows.js.map +1 -1
  71. package/dist/resources/workspace.d.ts +36 -11
  72. package/dist/resources/workspace.d.ts.map +1 -1
  73. package/dist/resources/workspace.js.map +1 -1
  74. package/dist/sync.d.ts +37 -8
  75. package/dist/sync.d.ts.map +1 -1
  76. package/dist/sync.js +24 -8
  77. package/dist/sync.js.map +1 -1
  78. package/dist/transport.d.ts +17 -3
  79. package/dist/transport.d.ts.map +1 -1
  80. package/dist/transport.js +18 -1
  81. package/dist/transport.js.map +1 -1
  82. package/dist/types.d.ts +88 -27
  83. package/dist/types.d.ts.map +1 -1
  84. package/openapi.yaml +174 -73
  85. package/package.json +2 -2
  86. package/registry-openapi.yaml +34 -3
package/openapi.yaml CHANGED
@@ -1286,9 +1286,10 @@ paths:
1286
1286
  description: Delivered onto the live stream.
1287
1287
  content:
1288
1288
  application/json:
1289
+ # 封闭(census 批2 第三段,2026-07-30):live 臂铸造点 `routes/notify-wake.ts:98` 是两键字面量。
1290
+ # 4.3.0:形提成具名 `SessionNotifyResult` 的 live 臂(SDK 同批给了判别联合返回型)。
1289
1291
  schema:
1290
1292
  type: object
1291
- # 封闭(census 批2 第三段,2026-07-30):live 臂铸造点 `routes/notify-wake.ts:86` 是两键字面量。
1292
1293
  additionalProperties: false
1293
1294
  required: [sessionId, delivery]
1294
1295
  properties:
@@ -1298,9 +1299,9 @@ paths:
1298
1299
  description: No live stream — parked in the session inbox, drained on the next stream open.
1299
1300
  content:
1300
1301
  application/json:
1302
+ # 封闭 + note 补 required:parked 臂铸造点 `routes/notify-wake.ts:116` 是三键字面量,note 无条件。
1301
1303
  schema:
1302
1304
  type: object
1303
- # 封闭 + note 补 required:parked 臂铸造点 `routes/notify-wake.ts:104` 是三键字面量,note 无条件。
1304
1305
  additionalProperties: false
1305
1306
  required: [sessionId, delivery, note]
1306
1307
  properties:
@@ -1395,26 +1396,11 @@ paths:
1395
1396
  description: The snapshot listing.
1396
1397
  content:
1397
1398
  application/json:
1398
- schema:
1399
- type: object
1400
- # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts:621-626`(信封)与
1401
- # `:616`(行)。条件键=信封 `latest`(零快照即省略)、行 `bytes`(blobSizes 面缺席=诚实省略)。
1402
- additionalProperties: false
1403
- required: [sessionId, snapshots, total]
1404
- properties:
1405
- sessionId: { type: string }
1406
- snapshots:
1407
- type: array
1408
- items:
1409
- type: object
1410
- additionalProperties: false
1411
- required: [key, files]
1412
- properties:
1413
- key: { type: string }
1414
- files: { type: integer }
1415
- bytes: { type: integer }
1416
- latest: { type: string }
1417
- total: { type: integer }
1399
+ # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts:624-629`(信封)与
1400
+ # `:620`(行)。条件键=信封 `latest`(零快照即省略)、行 `bytes`(blobSizes 面缺席=诚实省略)。
1401
+ # 4.3.0:形提成具名 `WorkspaceSnapshotPage`(SDK 同批把返回位的内联字面量也提成了 exported type,
1402
+ # 让 spec-resource-schema-gate 罩得住 —— CAPS-OPS-8)。
1403
+ schema: { $ref: '#/components/schemas/WorkspaceSnapshotPage' }
1418
1404
  '400': { $ref: '#/components/responses/BadRequest' }
1419
1405
  '401': { $ref: '#/components/responses/Unauthorized' }
1420
1406
  '404': { $ref: '#/components/responses/NotFound' }
@@ -1455,27 +1441,10 @@ paths:
1455
1441
  description: One page of the tree.
1456
1442
  content:
1457
1443
  application/json:
1458
- schema:
1459
- type: object
1460
- # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts:648-654`(信封)与
1461
- # `:651`(行)。条件键=信封 `nextOffset`(尾页省略)、行 `size`(blobSizes 面缺席省略)。
1462
- additionalProperties: false
1463
- required: [sessionId, key, entries, total]
1464
- properties:
1465
- sessionId: { type: string }
1466
- key: { type: string }
1467
- entries:
1468
- type: array
1469
- items:
1470
- type: object
1471
- additionalProperties: false
1472
- required: [path, hash]
1473
- properties:
1474
- path: { type: string }
1475
- hash: { type: string }
1476
- size: { type: integer }
1477
- total: { type: integer }
1478
- nextOffset: { type: integer }
1444
+ # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts:651-657`(信封)与
1445
+ # `:654`(行)。条件键=信封 `nextOffset`(尾页省略)、行 `size`(blobSizes 面缺席省略)。
1446
+ # 4.3.0:形提成具名 `WorkspaceTreePage`(理由同 list 腿 —— CAPS-OPS-8)。
1447
+ schema: { $ref: '#/components/schemas/WorkspaceTreePage' }
1479
1448
  '400': { $ref: '#/components/responses/BadRequest' }
1480
1449
  '401': { $ref: '#/components/responses/Unauthorized' }
1481
1450
  '404': { $ref: '#/components/responses/NotFound' }
@@ -1696,7 +1665,11 @@ paths:
1696
1665
  2c session-sync PULL — the THIN snapshot: entry IDS (oldest-first, NOT payloads — those stream via
1697
1666
  `/sync/entries`) + per-snapshot relPath→blobHash + policy + anchors + leaf. The local peer feeds `entryIds`
1698
1667
  to the §7 classifier to decide fast-forward/fork BEFORE pulling the entry stream + only the blob hashes it
1699
- lacks. Owner-scoped (non-owner → 404, no oracle); 400 on a non-uuidv7 id; 501 without a durable session store.
1668
+ lacks. Owner-scoped (non-owner → 404, no oracle); 400 on an id that is not uuid-SHAPED — 🔴 the sync family's
1669
+ gate is `isUuidShape` (ANY uuid version), NOT `isUuidV7`: server `routes/session-sync.ts:135-138` deliberately
1670
+ keeps a v4-keyed legacy shell session pushable (it was creatable via the lenient submit face), while
1671
+ crafted-id classes stay shut out. `fork`/`delete`/`policy` DO use `isUuidV7` — only this family is lenient;
1672
+ 501 without a durable session store.
1700
1673
  responses:
1701
1674
  '200':
1702
1675
  description: The manifest, wrapped as `{ manifest }`.
@@ -1739,7 +1712,7 @@ paths:
1739
1712
  at 200 BEFORE the first line, so a mid-stream failure ends the body WITHOUT the trailer — the trailer's
1740
1713
  ABSENCE (or a count mismatch) is the SOLE truncation signal (NO 5xx mid-stream). The SDK reader throws
1741
1714
  `SyncTruncatedError` on a missing trailer / count mismatch. Owner-scoped (404); 400 on a bad afterSeq /
1742
- non-uuidv7 id; 501 without a durable entry-streaming store.
1715
+ non-uuid-SHAPED id (ANY uuid version passes — see the manifest leg); 501 without a durable entry-streaming store.
1743
1716
  responses:
1744
1717
  '200':
1745
1718
  description: The NDJSON entry stream (begin / entry× N / end-trailer).
@@ -1844,7 +1817,8 @@ paths:
1844
1817
  description: >
1845
1818
  2c session-sync PLAN — classify the §7 relation between the LOCAL peer's `entryIds` (oldest-first, the SOURCE)
1846
1819
  and the cloud's current log (the DST), WITHOUT writing. Returns the `SyncRelation` (`fresh` for a fresh cloud
1847
- session). own-or-FRESH gate; 400 on a non-string-array body / non-uuidv7 id. Unlike import, plan NEVER 409s —
1820
+ session). own-or-FRESH gate; 400 on a non-string-array body / a non-uuid-SHAPED id (ANY uuid version passes —
1821
+ see the manifest leg). Unlike import, plan NEVER 409s —
1848
1822
  a fork/stale is REPORTED in the relation, not raised.
1849
1823
  requestBody:
1850
1824
  required: true
@@ -1901,6 +1875,20 @@ paths:
1901
1875
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
1902
1876
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
1903
1877
  resolution: { type: string, enum: [overwrite-dst], description: Override a fork/stale loss-of-history conflict. }
1878
+ logDigest:
1879
+ type: string
1880
+ description: >
1881
+ 🔴 OPTIONAL content digest of the SOURCE's whole entry log (core `sessionLogDigest`, scheme
1882
+ `sema-log-v3`; this leg's core floor is ^1.416.0). Server producer/consumer:
1883
+ `routes/session-sync.ts:295,310,357-366`. This is THE key that makes `ImportStaged.payloadVerified:true`
1884
+ reachable — until 4.3.0 the response schema promised a `payloadVerified`/`entry-ids+digest` pair
1885
+ that the request side had no way to trigger. Rationale: `identical` is classified from entry-id
1886
+ SETS only, and an entry id is a uuidv7 (NOT content-addressed), so "same ids, different payloads"
1887
+ is structurally possible and would be reported as already-synced. Supply this and the server
1888
+ recomputes the digest over its own log: equal ⇒ `basis:"entry-ids+digest"` + `payloadVerified:true`;
1889
+ different or not comparable ⇒ it does NOT short-circuit and stages a full sync (safe direction).
1890
+ A malformed value is NOT rejected — it is treated as absent (a digest mismatch must never become
1891
+ an error, a 409, or a refusal to sync).
1904
1892
  responses:
1905
1893
  '200':
1906
1894
  description: Staged (or `identical` no-op).
@@ -3013,7 +3001,12 @@ paths:
3013
3001
  per-row delta for every transition. Frame order: `event: meta` `{version, scoped}` → `event: snapshot`
3014
3002
  `{tasks, workflows, ts}` → then `event: task`/`event: workflow` UPSERTS (each `data: {row, ts}` is the
3015
3003
  POST-MERGE FULL row — replace by `row.id`, NOT a sparse patch) + `event: task_remove`/`event:
3016
- workflow_remove` `{id, ts}` departures. Heartbeat `: hb` comment frames keep the connection alive.
3004
+ workflow_remove` `{id, ts}` departures.
3005
+ 🔴 Keep-alive is a REAL NAMED FRAME — `event: heartbeat` + `data: {}` every 15 s
3006
+ (`src/http/routes/fleet.ts:223`), NOT an SSE `: hb` comment (the旁注 says why: a per-frame-parsing BFF
3007
+ drops comments, so downstream saw a zero-frame window). Do NOT implement the reader as "skip lines
3008
+ starting with `:`" — swallow the heartbeat BY NAME. (Corrected 2026-08-02; the `: hb` shape belongs to
3009
+ `GET /v1/sessions/{sessionId}/events` alone, and beats at a tunable ~25 s.)
3017
3010
  🔴 OWNER-SCOPED by the credential-derived principal (NOT a spoofable header) UNLESS fleet-wide (an operator
3018
3011
  / deployment trace token sees every tenant's rows). The row `scope` is WIRE-STRIPPED after the owner-gate —
3019
3012
  it never appears on a row; the `meta.scoped` flag tells the consumer which view this is (true = owner-scoped).
@@ -4244,6 +4237,30 @@ components:
4244
4237
  description: Per-task sub-agent definitions (roster additions scoped to this task only).
4245
4238
  interactiveTools: { type: boolean, description: Mount interactive (prompting) tools. }
4246
4239
  retainBackgroundProcesses: { type: boolean, description: Keep background processes alive past the turn. }
4240
+ forwardSubagentEvents:
4241
+ type: boolean
4242
+ description: >
4243
+ 🔴 PER-TASK opt-in for forwarding subagent CONTENT onto the parent's event stream (server
4244
+ `src/http/wire-types.ts:157`, consumed at `boot/resolve-spec.ts:557` — read STRICTLY as `=== true`).
4245
+ ⚠️ The capability bit of the same name says "this body key is ACCEPTED", NOT "forwarding is on":
4246
+ it is hard-coded true, while forwarding itself is OFF unless this key is sent. Without it the parent
4247
+ stream carries progress-only frames and a subagent-transcript view stays empty forever.
4248
+ (Declared 2026-08-02 — the capability bit had been registered here since 3.x, the request key never was.)
4249
+ retainSubagentSessions:
4250
+ oneOf:
4251
+ - type: boolean
4252
+ - type: object
4253
+ additionalProperties: false
4254
+ properties:
4255
+ ttlMs: { type: integer, description: 'Retention window; server CLAMPS to ≤ 24 h (task-workflow.ts:77-83).' }
4256
+ max: { type: integer, description: 'Retained session count; server CLAMPS to ≤ 64.' }
4257
+ description: >
4258
+ 🔴 PER-TASK opt-in to RETAIN finished subagent sessions so they can be resumed (server
4259
+ `src/http/wire-types.ts:169`, normalized at `boot/resolve-spec.ts:564`). This is the OTHER HALF of
4260
+ `POST /v1/runs/{runId}/subagents/{handle}/resume`: the capability bit `subagentResume` can be true and
4261
+ the route mounted, and the call still 409s `resume.retain_off` — retention is per-run and OFF by default.
4262
+ An over-large ask is CLAMPED, not rejected. (Declared 2026-08-02; the error code and the capability bit
4263
+ were both already documented, only the knob that turns retention on was missing.)
4247
4264
  excludeTools:
4248
4265
  type: array
4249
4266
  items: { type: string }
@@ -4990,25 +5007,8 @@ components:
4990
5007
  scheduler: { type: boolean, description: "Assistant-scheduler face is mounted." }
4991
5008
  sendUserFile: { type: boolean, description: "Outbound user-file delivery is wired." }
4992
5009
  sendUserFileLedger: { type: boolean, description: "The send-file ledger (audit of deliveries) is available." }
4993
- excludeTools:
4994
- type: array
4995
- items: { type: string }
4996
- description: >
4997
- ⚠️ PHANTOM KEY — NEVER emitted by any server version (契约调和车1 H①, 2026-08-02: verified against
4998
- every casting site in `src/http/routes/capabilities.ts`). It is a **TaskRequest** field (submit-body
4999
- validation `src/http/server.ts:1358`, TaskSpec mapping `src/boot/resolve-spec.ts:588`) that was
5000
- mis-registered on the capability face. Reading it always yields absent. Kept (removing it is a
5001
- breaking change) — SCHEDULED FOR REMOVAL IN THE NEXT MAJOR.
5002
- Semantics, for the archive: roster TRUE-UNMOUNT — the named tools' schema bytes never reach the
5003
- model and the assembly manifest narrows honestly; tighten-only, unioned into every child spawn.
5004
- deferTools:
5005
- type: array
5006
- items: { type: string }
5007
- description: >
5008
- ⚠️ PHANTOM KEY — NEVER emitted by any server version; same mis-registration as `excludeTools`, and
5009
- the real thing is the **TaskRequest** field of this name. SCHEDULED FOR REMOVAL IN THE NEXT MAJOR.
5010
- Semantics, for the archive: DEFERRED DISCLOSURE — the named MOUNTED tools ride the wire as a
5011
- placeholder (schema bytes out of the cache prefix) and materialize via ToolSearch on demand.
5010
+ # [2400] CAPS-OPS-1(5.0.0):excludeTools/deferTools 两幻影属性已删——server 从不在能力面发;
5011
+ # 真身=TaskRequest 同名字段(本 spec TaskRequest 节登记的那两条是对的)。
5012
5012
  s3PublicEndpoint:
5013
5013
  type: ["string", "null"]
5014
5014
  description: >
@@ -5654,6 +5654,42 @@ components:
5654
5654
  result: { type: string }
5655
5655
  seq: { type: integer, minimum: 1 }
5656
5656
  source: { type: string, maxLength: 190 }
5657
+ SessionNotifyResult:
5658
+ # 4.3.0(SM-9):把 notify 的两态提成**具名** schema —— 此前只有 path 内联的两个匿名 200/202 形,
5659
+ # SDK 侧的返回型则是裸 `Record<string, unknown>`,`delivery` 这条承重语义(已送达 vs 排队)在类型面
5660
+ # 整个丢失。铸造点 `routes/notify-wake.ts:98`(live)/ `:116`(parked),两臂都是字面量。
5661
+ description: >
5662
+ POST /v1/sessions/{sessionId}/notify 的判别联合。分支 `delivery`:`live` = 会话有活流、通知已送达
5663
+ (HTTP 200);`parked` = 无活流,排队到下次开流才 drain(HTTP 202)。消费方不得把两者都渲染成"已通知"。
5664
+ oneOf:
5665
+ - type: object
5666
+ additionalProperties: false
5667
+ required: [sessionId, delivery]
5668
+ properties:
5669
+ sessionId: { type: string }
5670
+ delivery: { type: string, enum: [live] }
5671
+ - type: object
5672
+ additionalProperties: false
5673
+ required: [sessionId, delivery, note]
5674
+ properties:
5675
+ sessionId: { type: string }
5676
+ delivery: { type: string, enum: [parked] }
5677
+ note: { type: string }
5678
+ SessionWakeResult:
5679
+ # 4.3.0(SM-9):wake 200 形提成具名 schema(形与下面 /wake 路径的 200 逐字同源)。
5680
+ type: object
5681
+ description: >
5682
+ POST /v1/sessions/{sessionId}/wake 的 200 形。⚠️ 200 也可能带 `errorCode`(core 侧 `wake.*` 拒了) ——
5683
+ 按机器码分支,别只看 HTTP 状态码。
5684
+ additionalProperties: false
5685
+ required: [sessionId, status]
5686
+ properties:
5687
+ sessionId: { type: string }
5688
+ status: { type: string, description: 'The run''s resulting status (core TaskStatus verbatim).' }
5689
+ taskId: { type: string, description: 'The active run id; OMITTED when no active row.' }
5690
+ errorCode: { type: string, description: 'Failure code when the resumed leg failed / was refused.' }
5691
+ errorMessage: { type: string, description: 'Human-readable failure detail (server-side secret-redacted).' }
5692
+ retriable: { type: boolean, description: 'true = the park is STILL pending (re-parked); refetch and retry.' }
5657
5693
  WorkflowJournalRow:
5658
5694
  # 🔴 census 批2 四段:CLOSED against server's real projected/truncated row shapes — a row is EITHER the
5659
5695
  # projected result OR an honest truncated stub(超 64KiB 的行从不进程内存,投影为 truncated stub)。
@@ -5703,9 +5739,18 @@ components:
5703
5739
  WorkflowStreamEvent:
5704
5740
  type: object
5705
5741
  description: >
5706
- One frame of GET /v1/workflows/:id/stream (streamWorkflowRun). FIRST frame is `event:meta`
5707
- `{version, runId}`; subsequent frames carry a core WorkflowEvent (its `type` is the SSE event name). NOT
5708
- resumable (replica-local, in-process; no id/Last-Event-ID). `data` is permissive (core-internal, evolves).
5742
+ One frame of GET /v1/workflows/:id/stream (streamWorkflowRun). FIRST frame is `event:meta` with
5743
+ `data: {type:"meta", version, runId}` (`src/http/routes/workflows.ts:487` — the `type` is double-emitted
5744
+ on purpose: a proxy that forwards only `data:` lines would otherwise leave a `data.type`-dispatching
5745
+ consumer unable to recognise the opener). Subsequent frames carry a core WorkflowEvent verbatim (its
5746
+ `type` is the SSE event name). NOT resumable (replica-local, in-process; no id/Last-Event-ID).
5747
+ `data` is permissive (core-internal, evolves).
5748
+ 🔴 There IS exactly one TERMINAL frame and it means FAILURE: `event: error` with
5749
+ `data: {type:"error", errorCode:"workflow.stream_error", message:"workflow stream error"}`
5750
+ (`routes/workflows.ts:517`). Branch on `errorCode` — NEVER anchor on `message`; the errorCode was added
5751
+ precisely because consumers were otherwise forced to anchor prose or to conflate "the stream broke" with
5752
+ "the stream ended normally", i.e. to report a FAILED workflow as finished. (Declared 2026-08-02.)
5753
+ Keep-alive is a REAL NAMED FRAME `event: heartbeat` / `data: {}` @15 s (`:496`), not an SSE comment.
5709
5754
  NOTE: on the wire this is SSE framing (event name + JSON data), not a JSON body — schema describes one
5710
5755
  decoded frame.
5711
5756
  required: [event, data]
@@ -5809,12 +5854,19 @@ components:
5809
5854
  required: [id, name, status]
5810
5855
  additionalProperties: true
5811
5856
  properties:
5857
+ # 🔴 FW-6(2026-08-02):这里此前有一个 `sourceLane` —— **幻影键,已删**。`sourceLane` 只在
5858
+ # `FleetTaskRow`(`src/fleet/fleet-bus.ts:146`)上,`FleetWorkflowRow`(`:150-170`)全文没有它,
5859
+ # `publishWorkflow`(`:338-342`)也从不写它;SDK 侧同样没有(SDK 是对的那一边)。
5860
+ # 按它 codegen 的第三方会得到一个永远 undefined 的 `workflowRow.sourceLane`,并照 FleetTaskRow 那条
5861
+ # 「run-leg composite rows only」的描述写出一个**永不触发**的 workflow 行去重分支。
5812
5862
  id: { type: string }
5813
- sourceLane: { type: string, description: 'run-leg composite rows only (server >=3.6.0, [2070]-1 yield half); BCE-lane rows omit it.' }
5814
5863
  name: { type: string }
5815
5864
  description: { type: string }
5816
5865
  status: { type: string, description: 'Neutral workflow run status (running|completed|failed; open).' }
5817
5866
  doneCount: { type: integer }
5867
+ # 🔴 FW-6(2026-08-02):补上一个 server 真发、SDK 真声明、只有 spec 漏了的键。
5868
+ # `src/fleet/fleet-bus.ts:167` 声明,`src/orchestration/workflow-notify-journal.ts:511` 真发。
5869
+ startedCount: { type: integer, description: 'Agents actually STARTED (vs `totalCount` = planned, queued included). CC 2.1.220 的 `⚠ Large workflow` 规模告警用它当分母 —— 漏了它那条告警根本渲染不出来。' }
5818
5870
  totalCount: { type: integer }
5819
5871
  failedCount: { type: integer }
5820
5872
  elapsedMs: { type: integer }
@@ -5825,7 +5877,9 @@ components:
5825
5877
  wire this is SSE framing (the `type` here = the SSE `event` name; the object = the decoded `data:` JSON).
5826
5878
  Emit order: `meta` (FIRST) → `snapshot` (connect-time full set) → per-row `task`/`workflow` upserts +
5827
5879
  `task_remove`/`workflow_remove` departures. The SDK PRODUCES these frames; the consumer maintains the
5828
- active-set state (update-in-place by `row.id`, remove by `id`). Heartbeat `: hb` comments are not frames.
5880
+ active-set state (update-in-place by `row.id`, remove by `id`).
5881
+ 🔴 The keep-alive IS a frame: `event: heartbeat` / `data: {}` @15 s (corrected 2026-08-02 — this line
5882
+ previously said "`: hb` comments are not frames", which described a superseded shape). Swallow it by NAME.
5829
5883
  OPEN set — branch on `type`, ignore an unknown future frame.
5830
5884
  oneOf:
5831
5885
  - $ref: '#/components/schemas/FleetFrame_meta'
@@ -5849,12 +5903,22 @@ components:
5849
5903
  hook_notice: '#/components/schemas/FleetFrame_hook_notice'
5850
5904
  FleetFrame_meta:
5851
5905
  type: object
5852
- description: 'FIRST frame — `{version, scoped}`. `scoped:true` ⇒ owner-scoped view; `false` ⇒ fleet-wide (operator/trace token).'
5853
- required: [type, version, scoped]
5906
+ # 🔴 FW-10(2026-08-02):此前只声明三键,而铸造点 `src/http/routes/fleet.ts:79` **无条件**发五键 ——
5907
+ # `sessionScoped` / `bgNotifyFailClosed` 不在任何条件分支里。这两个键正是「server 契约方复审 F1
5908
+ # (行为错·最高)」的当事键:它们曾被一次白名单重构静默剥掉,导致 cli 的让位臂全程死代码。
5909
+ # SDK 侧已上锁(`test/fleet.test.ts` 有专门的透传回归钉),spec 侧一直空白 —— 按 spec 生成的客户端
5910
+ # 拿不到 `bgNotifyFailClosed`,只能保留帧级 own/foreign 判别(或更糟:以为拿不到就是老 server),
5911
+ # 代际让位机制对这类消费方永远不生效。
5912
+ description: >
5913
+ FIRST frame — `{type, version, scoped, sessionScoped, bgNotifyFailClosed}` (all five UNCONDITIONAL on
5914
+ server >= 1.247). `scoped:true` ⇒ owner-scoped view; `false` ⇒ fleet-wide (operator/trace token).
5915
+ required: [type, version, scoped, sessionScoped, bgNotifyFailClosed]
5854
5916
  properties:
5855
5917
  type: { const: meta }
5856
5918
  version: { type: integer }
5857
5919
  scoped: { type: boolean }
5920
+ sessionScoped: { type: boolean, description: 'true ⇒ this connection carried `?session=` (rows and notifications are filtered by the host session).' }
5921
+ bgNotifyFailClosed: { type: boolean, description: 'GENERATION marker — true ⇒ this server''s bg_notification injection path fails CLOSED for scoped subscribers, so a new-generation shell may drop its frame-level own/foreign checks entirely. ABSENT (older server) ⇒ keep them.' }
5858
5922
  FleetFrame_snapshot:
5859
5923
  type: object
5860
5924
  description: 'Connect-time FULL state (the visible, scope-stripped rows). Seed the active-set Map from this.'
@@ -8603,6 +8667,43 @@ components:
8603
8667
  delivery: { type: string, const: applied, description: 'Literal "applied" on today''s server.' }
8604
8668
  marker: { type: string, description: 'Engine-minted delivery marker.' }
8605
8669
 
8670
+ WorkspaceSnapshotPage:
8671
+ # 4.3.0(CAPS-OPS-8):把 GET …/workspace 的 200 信封提成具名 schema。SDK 侧此前是 `list()` 返回位上的
8672
+ # **内联闭合字面量**,两道 spec 门(field-drift 只扫 types.ts / resource-schema 只扫 resources 的 exported
8673
+ # type)对方法返回位的内联形都是盲的 —— 于是它漏了这里一直标为 required 的 `sessionId`,CI 全绿。
8674
+ type: object
8675
+ description: >
8676
+ GET /v1/sessions/{sessionId}/workspace 的 200 信封(server src/http/routes/sessions.ts:624-629)。
8677
+ ⚠️ `total` = **本页行数**(`snapshots.length`),不是快照总数,且这条腿没有任何分页游标 —— 与同族
8678
+ `WorkspaceTreePage.total`(真·全量)**同名反义**。`latest` 与展示行同源(防 ghost key)。
8679
+ additionalProperties: false
8680
+ required: [sessionId, snapshots, total]
8681
+ properties:
8682
+ sessionId: { type: string }
8683
+ snapshots:
8684
+ type: array
8685
+ items: { $ref: '#/components/schemas/WorkspaceSnapshotRow' }
8686
+ latest: { type: string, description: 'Newest snapshot key; absent when the session has none.' }
8687
+ total: { type: integer, description: 'ROWS ON THIS PAGE — not the snapshot count.' }
8688
+
8689
+ WorkspaceTreePage:
8690
+ # 4.3.0(CAPS-OPS-8):同上,tree 腿的 200 信封。
8691
+ type: object
8692
+ description: >
8693
+ GET /v1/sessions/{sessionId}/workspace/{key}/tree 的 200 信封(server src/http/routes/sessions.ts:651-657)。
8694
+ `total` 在这条腿上是**全量**(`all.length`)—— 与 `WorkspaceSnapshotPage.total` 语义相反。
8695
+ `key` 回的是解析后的真实键(请求可传 `latest` 别名)。
8696
+ additionalProperties: false
8697
+ required: [sessionId, key, entries, total]
8698
+ properties:
8699
+ sessionId: { type: string }
8700
+ key: { type: string, description: 'The RESOLVED snapshot key (a `latest` alias in the request comes back resolved).' }
8701
+ entries:
8702
+ type: array
8703
+ items: { $ref: '#/components/schemas/WorkspaceTreeEntry' }
8704
+ total: { type: integer, description: 'FULL entry count (not the page size).' }
8705
+ nextOffset: { type: integer, description: 'Present ⇒ more pages; send it back as ?offset=.' }
8706
+
8606
8707
  WorkspaceSnapshotRow:
8607
8708
  type: object
8608
8709
  description: >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "4.2.0",
3
+ "version": "5.0.0",
4
4
  "description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -48,6 +48,6 @@
48
48
  "prepublishOnly": "rm -f tsconfig.tsbuildinfo && npm run build && npm run typecheck --workspaces=false --prefix ../.. && npm test --workspaces=false --prefix ../.."
49
49
  },
50
50
  "devDependencies": {
51
- "@sema-agent/registry-core": "^0.10.24"
51
+ "@sema-agent/registry-core": "^0.13.0"
52
52
  }
53
53
  }
@@ -153,8 +153,21 @@ paths:
153
153
  - { in: path, name: domain, required: true, schema: { type: string } }
154
154
  get:
155
155
  operationId: registryScopeConfigDraftGet
156
- summary: Read a scope's config DRAFT (getScopeConfigDraft).
157
- responses: { '200': { description: '{value, version, …}.' } }
156
+ summary: Read a scope's config — DRAFT by default, or the PUBLISHED snapshot with `?source=published`.
157
+ # 🔴 RC-6(2026-08-02):`?source=published` 此前完全未成文,而 SDK 的 `getScopeConfigPublished`
158
+ # (`scope-config-feedback.ts:46`)一直在打它 —— 两个响应形不同(draft 是 `{value, version}`,
159
+ # published 是 `{value, published, publishedAt?}`),按 spec codegen 的人只知道 draft 那一个。
160
+ parameters:
161
+ - in: query
162
+ name: source
163
+ required: false
164
+ schema: { type: string, enum: [published] }
165
+ description: 'Omit ⇒ the DRAFT (workspace) read. `published` ⇒ the PUBLISHED snapshot — what members actually receive.'
166
+ responses:
167
+ '200':
168
+ description: >
169
+ Without `?source=`: the draft, `{value, version, …}` — `version` is the CAS baseline for the PUT.
170
+ With `?source=published`: `{value, published, publishedAt?, …}`.
158
171
  put:
159
172
  operationId: registryScopeConfigDraftPut
160
173
  summary: CAS-write the draft (putScopeConfigDraft; version NUMBER axis — not updatedAt).
@@ -164,10 +177,28 @@ paths:
164
177
  /api/v1/scopes/{s}/publish:
165
178
  parameters:
166
179
  - { in: path, name: s, required: true, schema: { type: string } }
180
+ # 🔴 RC-6(2026-08-02):此前这条路径**只有 post、只有 '200'**。两个后果,都命中同一个设计的核心:
181
+ # ① 缺 `get` ⇒ 按本 spec(随 npm 包发布,package.json exports `./registry-openapi.yaml`)生成客户端的人
182
+ # 拿不到状态查询 verb —— 而 SDK 的 `getPublishState` 一直在打它。
183
+ # ② 缺 `'202'` ⇒ 拿不到 pending 分支,他会把 202 当成一个未处理的异常状态。而 200/202 的判别**就是**
184
+ # 这条设计的核心(`scope-config-feedback.ts:85-86` 明写「消费方绝不许把 202 渲染成已发布」)。
185
+ # 现有的 registry path 门是双向零基线的、很强 —— 但它比对的是**路径集合**,method/responses 全不参与,
186
+ # 所以这两类问题结构上红不了。同批把那道门升到了 path × method(见 test/registry-spec-path-gate.test.ts)。
187
+ get:
188
+ operationId: registryScopePublishState
189
+ summary: Read the publish state (getPublishState) — is a request already pending, and how many approvals does it need?
190
+ responses:
191
+ '200':
192
+ description: >
193
+ `{published?, pending?, approvalsRequired}`. `pending` present ⇒ a request is already outstanding and a
194
+ new one cannot be opened. ⚠️ `approvalsRequired` gates a PROTECTION; if it is ever absent, treat that as
195
+ unknown and fail CLOSED — never render "no approval needed".
167
196
  post:
168
197
  operationId: registryScopePublishRequest
169
198
  summary: Request publish of the draft (requestPublish; goes through the approval gate).
170
- responses: { '200': { description: 'Publish requested/pending.' } }
199
+ responses:
200
+ '200': { description: 'PUBLISHED — the draft took effect immediately (no approval gate, or already satisfied).' }
201
+ '202': { description: 'PENDING — the approval gate is open and the draft is NOT in effect. Approvers act via …/publish/approve. A consumer MUST NOT render this as published.' }
171
202
 
172
203
  # ── feedback ──────────────────────────────────────────────────────────────────────────────────
173
204
  /api/v1/feedback: