@sema-agent/sdk 11.0.1 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- package/dist/client.d.ts +43 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +47 -0
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +25 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +66 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/resources/rules.d.ts +68 -28
- package/dist/resources/rules.d.ts.map +1 -1
- package/dist/resources/rules.js +34 -14
- package/dist/resources/rules.js.map +1 -1
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/openapi.yaml +386 -37
- package/package.json +1 -1
package/openapi.yaml
CHANGED
|
@@ -62,6 +62,14 @@ openapi: 3.1.0
|
|
|
62
62
|
# `test/live/route-existence.test.ts` (a planned path must have NO handler in the pinned
|
|
63
63
|
# server artifact). Both exist because `POST /v1/tasks/{taskId}/asks/{askId}/decision` kept
|
|
64
64
|
# this label for two server releases after it went live.
|
|
65
|
+
# ahead — implemented server-side, but in a version NEWER THAN THE PINNED ARTIFACT this repo's contract
|
|
66
|
+
# suite runs against (the SDK ships the verb in the same train — "the SDK is never behind the
|
|
67
|
+
# server"). DISTINCT FROM `planned`, which claims the server has NOT implemented it: an `ahead`
|
|
68
|
+
# face is real, has a named server version, and HAS a verb here calling it. Two gates police the
|
|
69
|
+
# pair so neither word can hide the other — `test/live/route-existence.test.ts` (an `ahead` path
|
|
70
|
+
# must have NO handler in the pinned artifact; the day it appears, flip this label to `live`/
|
|
71
|
+
# `gated`) and `spec-path-drift-gate` (every `ahead` path must be one the SDK actually calls —
|
|
72
|
+
# an `ahead` nobody calls is a mislabeled `planned`). It EXPIRES the same way `planned` does.
|
|
65
73
|
# gated — implemented but off by a server deploy flag (e.g. leader, STREAM_APPROVAL_ENABLED); answers
|
|
66
74
|
# 501 where the flag/deps are unmet. Says nothing about the flag's DEFAULT — that can differ
|
|
67
75
|
# per server version, so read the matching `capabilities` bit, never this label.
|
|
@@ -230,6 +238,170 @@ paths:
|
|
|
230
238
|
application/json:
|
|
231
239
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
232
240
|
|
|
241
|
+
# ── S-481(server >= 7.93.0;core 7.26.0 `probeMcpServers`)—— 无 run 可读的逐台 MCP 状态面,两条口一套词表 ──
|
|
242
|
+
/v1/capabilities/mcp:
|
|
243
|
+
parameters:
|
|
244
|
+
- $ref: '#/components/parameters/PrincipalHeader'
|
|
245
|
+
get:
|
|
246
|
+
tags: [metrics]
|
|
247
|
+
operationId: mcpCapabilities
|
|
248
|
+
x-status: ahead # server main 955fdbf0 routes/capabilities.ts `capability-mcp`(server 7.93.0,未上 npm ⇒ 钉版 7.92.2 产物里没有);SDK client.mcpCapabilities() 消费。钉版抬到 ≥7.93.0 当天改 `live`。
|
|
249
|
+
summary: Per-server MCP status for THIS deployment's own declared servers — dialed now, with no run.
|
|
250
|
+
description: >
|
|
251
|
+
What this deployment's OWN configured MCP servers answer RIGHT NOW, one row per declaration. It exists
|
|
252
|
+
because a session that never started a run had no face at all: `capabilities.mcp` is a single boolean, so a
|
|
253
|
+
shell's one-shot command had to dial and invent its own words — and then disagreed with the interactive
|
|
254
|
+
lane about the same server (a command line reporting Failed while that server's tools worked in a session).
|
|
255
|
+
|
|
256
|
+
🔴 THE ROWS ARE NOT MINTED HERE. `servers[]` is the engine's `probeMcpServers` entries through the SAME
|
|
257
|
+
projection a prepared leg pushes onto `wiring_manifest.mcp[]` — same mint point, same failure vocabulary.
|
|
258
|
+
So a row here and a row in that run are the same KIND of thing, not two things that look alike; every
|
|
259
|
+
clause of `WiringManifestMcpEntry` holds verbatim, including the deliberate ABSENCE of the engine's
|
|
260
|
+
`error` free text (the actionable cause is `errorCode`).
|
|
261
|
+
|
|
262
|
+
🔴 VISIBLE SET = THIS DEPLOYMENT'S DEFAULT SCENARIO (the same filter `resolveSpec` uses), NOT "what that
|
|
263
|
+
run would actually mount": on a deployment whose scenarios carry different MCP labels, or that decides
|
|
264
|
+
scenarios per principal, the two differ. There is no `?scenario=` on this face today — ask a run's own
|
|
265
|
+
manifest for another scenario. Per-principal authorization is decided at the MCP server by the injected
|
|
266
|
+
`principalHeader`, not filtered here.
|
|
267
|
+
|
|
268
|
+
🔴 ZERO DECLARATIONS = `200` with `servers: []` (an honest empty face — NOT a 404, NOT a 501). Probing is a
|
|
269
|
+
REAL DIAL, so the face is rate-limited per caller identity (12/60s per replica) with a 30s reuse window
|
|
270
|
+
(`ttlSec`; a cache hit does not count against the limit and returns the ORIGINAL `probedAt`). Gate on
|
|
271
|
+
`capabilities.mcpProbe` — absent means an older worker where this path does not exist (404
|
|
272
|
+
`not_found.route`), not "this deployment has no MCP".
|
|
273
|
+
responses:
|
|
274
|
+
'200':
|
|
275
|
+
description: 'The per-server readings (see `McpProbeFace`); an empty `servers` array = this deployment declared none.'
|
|
276
|
+
content:
|
|
277
|
+
application/json:
|
|
278
|
+
schema: { $ref: '#/components/schemas/McpProbeFace' }
|
|
279
|
+
'401':
|
|
280
|
+
description: >
|
|
281
|
+
THREE codes land here and they do NOT share one typed class, so branch on `errorCode`:
|
|
282
|
+
`auth.unauthorized` (the worker-wide service-credential gate, before this route),
|
|
283
|
+
`auth.principal_required` (the principal header is absent while `REQUIRE_PRINCIPAL` is on — same gate as
|
|
284
|
+
`GET /v1/sessions/{id}/mcp`), and `principal_unverified` (the header WAS sent and failed verification —
|
|
285
|
+
a DIFFERENT action: fix the token/signing, not the header). A consumer that only handles the `auth.*`
|
|
286
|
+
family silently misses the third.
|
|
287
|
+
content:
|
|
288
|
+
application/json:
|
|
289
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
290
|
+
'429': { $ref: '#/components/responses/RateLimited' } # limit.rate_exceeded — 12/60s per identity, OR the replica-wide in-flight cap (32, ACROSS identities: one caller can be refused because of another). Retry-After and the body's `retryAfterSec` are the same value; the body's absence means no header either
|
|
291
|
+
'500':
|
|
292
|
+
description: >
|
|
293
|
+
errorCode `internal.error` — the engine refused THIS DEPLOYMENT'S declaration SET as a whole (the one
|
|
294
|
+
documented cause: two servers would mount under the same tool-namespace prefix, refused BEFORE any
|
|
295
|
+
transport is touched). 🔴 WHOEVER WROTE THE DECLARATIONS OWNS THE ERROR: on this door they are the
|
|
296
|
+
deployment's, so this machine's configuration is broken and a 5xx is the honest direction — folding it
|
|
297
|
+
into a 4xx would report an operator's mistake as the caller's. The cause is NOT echoed (it comes from
|
|
298
|
+
the declaration set itself); it is in this worker's logs. Retrying does not change it.
|
|
299
|
+
content:
|
|
300
|
+
application/json:
|
|
301
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
302
|
+
'503':
|
|
303
|
+
description: >
|
|
304
|
+
errorCode `state.mcp_probe_incomplete` — the WHOLE walk did not settle within this face's bound
|
|
305
|
+
(derived from the declaration count: `ceil(n / 8) x 10s + 4s`, so 1 server = 14s and 32 = 44s). 🔴 THIS
|
|
306
|
+
FACE HAS NO `degraded` ARM ON PURPOSE: a probe that cannot answer must not return a normal-looking
|
|
307
|
+
empty list, which is exactly the false answer it exists to remove. A retry MAY succeed. ⚠️ The
|
|
308
|
+
abandoned dial can outlive the request (the engine's dial does not own cancellation).
|
|
309
|
+
headers:
|
|
310
|
+
Retry-After: { schema: { type: integer }, description: 'Suggested wait; a HINT, not a promise.' }
|
|
311
|
+
content:
|
|
312
|
+
application/json:
|
|
313
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
314
|
+
|
|
315
|
+
/v1/capabilities/mcp/probe:
|
|
316
|
+
parameters:
|
|
317
|
+
- $ref: '#/components/parameters/PrincipalHeader'
|
|
318
|
+
post:
|
|
319
|
+
tags: [metrics]
|
|
320
|
+
operationId: mcpProbe
|
|
321
|
+
x-status: ahead # server main 955fdbf0 routes/capabilities.ts `capability-mcp-probe`(server 7.93.0,未上 npm);门 = mcpInjectionHonored ⇒ 钉版抬到 ≥7.93.0 当天改 `gated`(不是 live:它有 501 腿)。SDK client.probeMcp() 消费。
|
|
322
|
+
summary: Probe the CALLER'S OWN MCP declarations (the shell's .mcp.json) — same body shape as a submission.
|
|
323
|
+
description: >
|
|
324
|
+
The caller-declared twin of `mcpCapabilities`: it dials the servers in the request body and answers with
|
|
325
|
+
the SAME body shape, so one consumer rendering reads both faces.
|
|
326
|
+
|
|
327
|
+
🔴 THE GATE IS `capabilities.mcpInjection` (the SAME predicate the task-submission leg uses): a
|
|
328
|
+
multi-tenant worker, an administratively locked `mcp` config key, or a compliance posture that denies
|
|
329
|
+
`mcp_servers` each refuse the whole call with 501 `capability.mcp_injection_required`. ⚠️ DELIBERATELY
|
|
330
|
+
UNLIKE the submission leg, which IGNORES an unhonored `mcpServers` (plus a user-facing notice) — a probe
|
|
331
|
+
asks "can I use these", so the answer must be loud.
|
|
332
|
+
|
|
333
|
+
🔴 THE BODY IS CLOSED AND A BAD ENTRY REFUSES THE WHOLE LIST. Top level accepts `mcpServers` only; entry
|
|
334
|
+
keys and both `transport` key sets are closed (the vocabulary is pinned to the engine's `McpServerSpec`
|
|
335
|
+
in both directions). An out-of-vocabulary key is 400 `request.body_shape` with `unknownKeys` naming the
|
|
336
|
+
KEY PATHS verbatim (`mcpServers[0].transport.header`); a bad value (a non-http(s) url, a `transport.kind`
|
|
337
|
+
outside the closed set, a `contentOrigin` outside its vocabulary, more than 32 declarations) is 400
|
|
338
|
+
`request.field_invalid`. ⚠️ Again unlike the submission leg, which names and DROPS a bad entry while
|
|
339
|
+
mounting its siblings: quietly removing the problem entry and returning a good-looking list is the false
|
|
340
|
+
answer this face exists to remove. A refusal may echo a url, always through the display-safe mint point
|
|
341
|
+
(no userinfo on the wire); a SUCCESS body carries no url at all (the row shape has no `url`/`headers`/`env`).
|
|
342
|
+
|
|
343
|
+
Rate limit, reuse window and the in-flight cap are shared with `mcpCapabilities` — see that operation.
|
|
344
|
+
requestBody:
|
|
345
|
+
required: true
|
|
346
|
+
content:
|
|
347
|
+
application/json:
|
|
348
|
+
schema:
|
|
349
|
+
type: object
|
|
350
|
+
description: >
|
|
351
|
+
The declaration list to probe — the SAME shape a task submits (`TaskRequest.mcpServers`), which is
|
|
352
|
+
why the key has that name and the element is the very same schema. Closed: any other top-level key
|
|
353
|
+
is a 400 `request.body_shape`.
|
|
354
|
+
required: [mcpServers]
|
|
355
|
+
additionalProperties: false
|
|
356
|
+
properties:
|
|
357
|
+
mcpServers:
|
|
358
|
+
type: array
|
|
359
|
+
maxItems: 32
|
|
360
|
+
items: { $ref: '#/components/schemas/McpServerSpec' }
|
|
361
|
+
description: 'At most 32; index-aligned with `McpProbeFace.servers`.'
|
|
362
|
+
responses:
|
|
363
|
+
'200':
|
|
364
|
+
description: 'The per-server readings — the SAME body as `mcpCapabilities` (see `McpProbeFace`).'
|
|
365
|
+
content:
|
|
366
|
+
application/json:
|
|
367
|
+
schema: { $ref: '#/components/schemas/McpProbeFace' }
|
|
368
|
+
'400': { $ref: '#/components/responses/BadRequest' } # request.body_shape (closed-key refusal; body carries `unknownKeys` = the offending KEY PATHS) / request.field_invalid (bad value, over the 32-declaration ceiling, or the engine refused the declaration SET — two servers under one tool-namespace prefix; that cause is NOT echoed) / request.rejected (the body is not parseable JSON — the status-derived coarse code from the body READER, which runs before the closed-key door, so it is NOT `request.body_shape`)
|
|
369
|
+
'401':
|
|
370
|
+
description: >
|
|
371
|
+
Same three codes as `mcpCapabilities`, same reason to branch on `errorCode`: `auth.unauthorized`
|
|
372
|
+
(service-credential gate), `auth.principal_required` (header absent under `REQUIRE_PRINCIPAL`), and
|
|
373
|
+
`principal_unverified` (header sent, verification failed — fix the token, not the header). They do not
|
|
374
|
+
share one typed class.
|
|
375
|
+
content:
|
|
376
|
+
application/json:
|
|
377
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
378
|
+
'413':
|
|
379
|
+
description: >
|
|
380
|
+
errorCode `request.payload_too_large` — the request body is over this worker's cap. It comes from the
|
|
381
|
+
body READER (before this route's closed-key door), so it carries no `unknownKeys`. Shorten the
|
|
382
|
+
declaration list and resend; resending the same bytes never succeeds.
|
|
383
|
+
content:
|
|
384
|
+
application/json:
|
|
385
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
386
|
+
'429': { $ref: '#/components/responses/RateLimited' } # limit.rate_exceeded — per-identity window OR the replica-wide in-flight cap; see mcpCapabilities
|
|
387
|
+
'501':
|
|
388
|
+
description: >
|
|
389
|
+
errorCode `capability.mcp_injection_required` — this deployment does not honor caller-supplied MCP
|
|
390
|
+
declarations (multi-tenant, or `mcp` is a locked config key, or the compliance posture denies
|
|
391
|
+
`mcp_servers`; the refusal names which). Read `capabilities.mcpInjection` BEFORE asking — do not
|
|
392
|
+
trial-by-501. `mcpCapabilities` (this deployment's OWN declared servers) stays available either way.
|
|
393
|
+
content:
|
|
394
|
+
application/json:
|
|
395
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
396
|
+
'503':
|
|
397
|
+
description: >
|
|
398
|
+
errorCode `state.mcp_probe_incomplete` — identical to `mcpCapabilities`'s 503 (see there).
|
|
399
|
+
headers:
|
|
400
|
+
Retry-After: { schema: { type: integer }, description: 'Suggested wait; a HINT, not a promise.' }
|
|
401
|
+
content:
|
|
402
|
+
application/json:
|
|
403
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
404
|
+
|
|
233
405
|
/v1/tasks:
|
|
234
406
|
parameters:
|
|
235
407
|
- $ref: '#/components/parameters/PrincipalHeader'
|
|
@@ -5371,17 +5543,20 @@ paths:
|
|
|
5371
5543
|
'429': { $ref: '#/components/responses/RateLimited' }
|
|
5372
5544
|
'503':
|
|
5373
5545
|
description: >
|
|
5374
|
-
errorCode `state.rule_write_failed` —
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5546
|
+
errorCode `state.rule_write_failed` — the write did not complete. 🔴 WHAT TO DO NEXT IS IN THE BODY'S
|
|
5547
|
+
`committed` KEY (server >= 7.93.0), and the two words say opposite things: `no` = the store answered
|
|
5548
|
+
and nothing landed ⇒ resend the same body verbatim; `unknown` = it cannot be proven ⇒ reconcile with
|
|
5549
|
+
`GET /v1/rules` FIRST. Do not derive it from the sentence — on servers <= 7.92.2 the two strengths are
|
|
5550
|
+
byte-identical here and the one sentence says "retry, then reconcile", which is the wrong order for
|
|
5551
|
+
`unknown`. Word table and per-word disposition: `RuleWriteFailedBody.committed`.
|
|
5552
|
+
⚠️ NARROWED AT server 7.92.2: "the write landed and was revoked
|
|
5378
5553
|
concurrently before the read-back" NO LONGER ARRIVES HERE — that shape has an answer (200 +
|
|
5379
5554
|
`stillLive: no`), and retrying it would re-erect a tightening someone just revoked. (Also `auth.service_token_required` on a deployment with no service credential
|
|
5380
5555
|
and no explicit `ALLOW_UNAUTHED_WRITES=true` — the write-door credential precondition, shared with
|
|
5381
|
-
the other rule write faces.)
|
|
5556
|
+
the other rule write faces; THAT refusal carries no `committed`, so read `errorCode` first.)
|
|
5382
5557
|
content:
|
|
5383
5558
|
application/json:
|
|
5384
|
-
schema: { $ref: '#/components/schemas/
|
|
5559
|
+
schema: { $ref: '#/components/schemas/RuleWriteFailedBody' }
|
|
5385
5560
|
'501': { $ref: '#/components/responses/NotImplemented' } # capability.rule_store_required
|
|
5386
5561
|
delete:
|
|
5387
5562
|
tags: [rules]
|
|
@@ -5409,9 +5584,11 @@ paths:
|
|
|
5409
5584
|
alias). Folding that into a plain
|
|
5410
5585
|
200 would let a client read an INCOMPLETE revocation as complete, which is precisely what a
|
|
5411
5586
|
governance surface must never get wrong. The `no-op` arm carries it for the same reason: the engine
|
|
5412
|
-
answers `no-op` off its initial snapshot
|
|
5587
|
+
answers `no-op` off its initial snapshot, so an approval landing between that snapshot and the response
|
|
5413
5588
|
leaves the rule alive while the verb says "nothing to remove". Both arms therefore ship the same key
|
|
5414
|
-
set and one consumer branch reads both.
|
|
5589
|
+
set and one consumer branch reads both — and since server 7.93.0 the same VALUE RANGE as well:
|
|
5590
|
+
`no-op` + `unknown` is REACHABLE (that arm now reads the same liveness read-back as `removed`, instead of
|
|
5591
|
+
the server taking its own and 5xx-ing when it failed). See `RuleRevokeResult.stillLive`.
|
|
5415
5592
|
|
|
5416
5593
|
OPERATOR OVERRIDE: `principal` revokes on ANOTHER tenant's behalf, gated exactly like the read half
|
|
5417
5594
|
(explicit `OPERATOR_PRINCIPALS`; empty list = nobody). The direction is what makes the override
|
|
@@ -5436,13 +5613,19 @@ paths:
|
|
|
5436
5613
|
'429': { $ref: '#/components/responses/RateLimited' }
|
|
5437
5614
|
'503':
|
|
5438
5615
|
description: >
|
|
5439
|
-
errorCode `state.rule_remove_failed` — the
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5616
|
+
errorCode `state.rule_remove_failed` — the tombstone did not land. 🔴 WHICH OF THE TWO STRENGTHS IT
|
|
5617
|
+
WAS IS IN THE BODY'S `committed` KEY (server >= 7.93.0 — the SAME key, word set and mint point as
|
|
5618
|
+
`rulesWrite`): `no` = the store answered and nothing landed ⇒ resend the same body verbatim;
|
|
5619
|
+
`unknown` = the apply threw or the read-back did not complete ⇒ reconcile with `GET /v1/rules` FIRST
|
|
5620
|
+
(a blind retry can re-erect a tightening somebody just revoked). Both are 503 rather than 500 because
|
|
5621
|
+
they speak about THIS attempt, not about whether a retry can ever succeed. Word table and per-word
|
|
5622
|
+
disposition: `RuleWriteFailedBody.committed`; on servers <= 7.92.2 the key is absent and the two
|
|
5623
|
+
strengths are indistinguishable here. (Also `auth.service_token_required` on a deployment with no
|
|
5624
|
+
service credential and no explicit `ALLOW_UNAUTHED_WRITES=true` — this verb is in the credential-gated
|
|
5625
|
+
write family too; THAT refusal carries no `committed`, so read `errorCode` first.)
|
|
5443
5626
|
content:
|
|
5444
5627
|
application/json:
|
|
5445
|
-
schema: { $ref: '#/components/schemas/
|
|
5628
|
+
schema: { $ref: '#/components/schemas/RuleWriteFailedBody' }
|
|
5446
5629
|
'501': { $ref: '#/components/responses/NotImplemented' } # capability.rule_store_required
|
|
5447
5630
|
|
|
5448
5631
|
# ── design/183 —— 身份收编面(form b:纯身份重绑)。operator-only 的一次性部署级动作,零模型工作。 ──
|
|
@@ -8036,6 +8219,20 @@ components:
|
|
|
8036
8219
|
projectContext: { type: boolean, description: "Project-context injection is wired." }
|
|
8037
8220
|
mcp: { type: boolean, description: "MCP servers can be attached." }
|
|
8038
8221
|
mcpInjection: { type: boolean, description: "Per-request MCP injection is accepted." }
|
|
8222
|
+
mcpProbe:
|
|
8223
|
+
type: boolean
|
|
8224
|
+
description: >
|
|
8225
|
+
server >= 7.93.0 (S-481) — the no-run per-server MCP status face is present: `GET /v1/capabilities/mcp`
|
|
8226
|
+
(this deployment's OWN declared servers) and `POST /v1/capabilities/mcp/probe` (the CALLER'S
|
|
8227
|
+
declarations). ALWAYS TRUE where it exists, for the same reason as `mcp`: the GET has no 501 arm
|
|
8228
|
+
(zero declarations answer an honest empty face), so "says yes" iff "the route works" is structural.
|
|
8229
|
+
PRESENT-IFF carries the version: ABSENT = an older worker with no such face (fall back to the
|
|
8230
|
+
session replay leg `McpStatusPanel.lastLegMcp`, or show the human the real cause row).
|
|
8231
|
+
🔴 TWO BITS, TWO QUESTIONS — DO NOT MERGE: whether the CALLER-declared door works is answered by
|
|
8232
|
+
`mcpInjection` (the same `mcpInjectionHonored` predicate IS that route's gate; multi-tenant, a
|
|
8233
|
+
locked `mcp` key, or a compliance posture denying `mcp_servers` each yield 501
|
|
8234
|
+
`capability.mcp_injection_required`). So `mcpProbe: true` beside `mcpInjection: false` is the
|
|
8235
|
+
NORMAL multi-tenant reading, not a contradiction.
|
|
8039
8236
|
mcpElicitation: { type: boolean, description: "MCP elicitation round-trips are supported." }
|
|
8040
8237
|
askUserQuestion: { type: boolean, description: "AskUserQuestion gate is available (durable when approvals are)." }
|
|
8041
8238
|
toolApproval: { type: boolean, description: "Tool-approval gating is active." }
|
|
@@ -12412,6 +12609,74 @@ components:
|
|
|
12412
12609
|
WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (and only here) — see the
|
|
12413
12610
|
enum note above.
|
|
12414
12611
|
|
|
12612
|
+
McpProbeFace:
|
|
12613
|
+
type: object
|
|
12614
|
+
description: >
|
|
12615
|
+
The body of the no-run per-server MCP status face — BYTE-FOR-BYTE THE SAME for `mcpCapabilities` (GET) and
|
|
12616
|
+
`mcpProbe` (POST), so one consumer rendering reads both (S-481, server >= 7.93.0).
|
|
12617
|
+
|
|
12618
|
+
🔴 THE ROWS ARE NOT MINTED BY THIS FACE: `servers[]` is `WiringManifestMcpEntry` — the engine's
|
|
12619
|
+
`probeMcpServers` entries through the SAME projection a prepared leg pushes onto `wiring_manifest.mcp[]`.
|
|
12620
|
+
🔴 `servers[]` IS INDEX-ALIGNED with the declaration list (same order, same length; engine contract
|
|
12621
|
+
`mcp.probe.index_aligned`): a failed, timed-out or never-dialed server STILL HOLDS ITS POSITION. Pair it
|
|
12622
|
+
with your own declaration list BY INDEX, never by `name` — names are not guaranteed unique.
|
|
12623
|
+
🔴 NO `degraded` ARM, DELIBERATELY (unlike `McpStatusPanel.degraded`): a probe that cannot answer returns a
|
|
12624
|
+
LOUD 503 `state.mcp_probe_incomplete`, never a normal-looking empty list. ⚠️ `servers: []` is a different
|
|
12625
|
+
thing entirely — an honest empty face (this deployment declared nothing).
|
|
12626
|
+
required: [probedAt, ttlSec, servers]
|
|
12627
|
+
additionalProperties: false
|
|
12628
|
+
properties:
|
|
12629
|
+
probedAt:
|
|
12630
|
+
type: string
|
|
12631
|
+
description: 'WHEN this reading is from (ISO). 🔴 A cache hit returns the ORIGINAL instant — a reused answer pretending to be fresh is a false fact.'
|
|
12632
|
+
ttlSec:
|
|
12633
|
+
type: integer
|
|
12634
|
+
description: 'How long this reading may be reused (seconds; 30s per identity + declaration set server-side). A hit inside the window does NOT count against the rate limit.'
|
|
12635
|
+
servers:
|
|
12636
|
+
type: array
|
|
12637
|
+
items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
|
|
12638
|
+
description: 'One row per declaration, SAME ROW SHAPE as the wiring manifest, index-aligned (see this schema''s description).'
|
|
12639
|
+
|
|
12640
|
+
McpLivenessState:
|
|
12641
|
+
type: string
|
|
12642
|
+
enum: [reachable, unreachable, unknown]
|
|
12643
|
+
description: >
|
|
12644
|
+
The engine's CLOSED three-word answer to ONE question, "can this client complete an exchange with this
|
|
12645
|
+
server" (core `MCP_LIVENESS_STATES`; the vocabulary's single owner is the engine, mirrored here the same way
|
|
12646
|
+
`McpDelivered` is): `reachable` = an exchange COMPLETED (handshake + list, a successful re-dial, or the
|
|
12647
|
+
server itself answered — an MCP-level rejection IS an answer, so it proves reach even though the call
|
|
12648
|
+
failed); `unreachable` = an exchange was attempted and LOST at the transport or the clock (refused, reset,
|
|
12649
|
+
closed, unspawnable, timed out); `unknown` = an exchange was attempted and what came back DOES NOT ANSWER
|
|
12650
|
+
the question (a status some gateway wrote, a non-MCP payload, a thrown value with no structure this table
|
|
12651
|
+
reads).
|
|
12652
|
+
🔴 ABSENCE OF THE WHOLE `liveness` RECORD is a FOURTH reading and is NOT one of these words — see
|
|
12653
|
+
`WiringManifestMcpEntry.liveness`. Do not fold an absent record into `unknown`.
|
|
12654
|
+
|
|
12655
|
+
McpServerLiveness:
|
|
12656
|
+
type: object
|
|
12657
|
+
description: >
|
|
12658
|
+
ONE liveness OBSERVATION about one MCP server (core 7.24.3 #874 `McpServerLiveness`). It says what the
|
|
12659
|
+
engine last LEARNED and WHEN — never what is true now, and NEVER a poll: the engine makes no probe of its
|
|
12660
|
+
own, so every record is a by-product of work the leg already did (the dial, a re-dial, a transport close).
|
|
12661
|
+
🔴 `state` + `observedAt` ARE BOTH LOAD-BEARING — together they are one sentence (what was seen, and when);
|
|
12662
|
+
the server's projection drops the WHOLE record when either half fails its shape check, so a record that
|
|
12663
|
+
arrives has both.
|
|
12664
|
+
🔴 `errorCode` RIDES `unreachable` ONLY: on `unknown` it is deliberately absent, because the whole point of
|
|
12665
|
+
that word is that the failure did NOT decide reachability — carrying a failure class there invites a
|
|
12666
|
+
consumer to key a reconnect action off evidence that says nothing about reach.
|
|
12667
|
+
required: [state, observedAt]
|
|
12668
|
+
additionalProperties: false
|
|
12669
|
+
properties:
|
|
12670
|
+
state:
|
|
12671
|
+
allOf: [{ $ref: '#/components/schemas/McpLivenessState' }]
|
|
12672
|
+
description: 'WHAT the engine last learned (the closed three-word table — see `McpLivenessState`).'
|
|
12673
|
+
observedAt: { type: integer, description: 'When that observation happened (epoch ms).' }
|
|
12674
|
+
errorCode:
|
|
12675
|
+
type: string
|
|
12676
|
+
description: >
|
|
12677
|
+
The failure class that decided `unreachable` (the engine's `McpFailureKind` word — deliberately NOT
|
|
12678
|
+
enumerated here; the vocabulary's owner is the engine). Absent on the other two words.
|
|
12679
|
+
|
|
12415
12680
|
McpDelivered:
|
|
12416
12681
|
type: string
|
|
12417
12682
|
description: >
|
|
@@ -14777,6 +15042,47 @@ components:
|
|
|
14777
15042
|
can never be accepted. The retry fetches the FINAL ROUTING INFO (poll until the 410 hands over the gate
|
|
14778
15043
|
coordinates). Carries NO gate coordinates — they are not written until the batch binds.
|
|
14779
15044
|
|
|
15045
|
+
RuleWriteFailedBody:
|
|
15046
|
+
allOf:
|
|
15047
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
15048
|
+
- type: object
|
|
15049
|
+
properties:
|
|
15050
|
+
committed:
|
|
15051
|
+
type: string
|
|
15052
|
+
enum: [no, unknown]
|
|
15053
|
+
description: >-
|
|
15054
|
+
DID THIS WRITE LAND? The engine's closed word pair, passed through VERBATIM (the server neither
|
|
15055
|
+
enumerates, folds, nor translates it), and THE TWO WORDS HAVE OPPOSITE DISPOSITIONS:
|
|
15056
|
+
`no` = the store answered and NOTHING landed (no write face / the shape test refused this target /
|
|
15057
|
+
the read before the write failed / preflight refused / an epoch seal / every compare-and-set lost)
|
|
15058
|
+
⇒ RESEND THE SAME BODY VERBATIM — no second dot can be minted and no just-revoked tightening can
|
|
15059
|
+
be re-erected, so you do NOT need to reconcile first.
|
|
15060
|
+
`unknown` = it cannot be proven — the apply THREW (the engine may have committed and then lost its
|
|
15061
|
+
acknowledgement), or the read-back that would have settled it could not be taken ⇒ 🔴 RECONCILE
|
|
15062
|
+
WITH `GET /v1/rules` FIRST, then decide whether to resend. A blind retry can mint a second dot and
|
|
15063
|
+
re-erect a tightening somebody just revoked.
|
|
15064
|
+
⚠️ A READ-BACK FAILURE DOES NOT FLIP THE WORD, it only fails to improve it (engine 7.26.0's own
|
|
15065
|
+
declaration): whatever the apply earned stands — a throw stays `unknown`, an ANSWERED
|
|
15066
|
+
compare-and-set stays `no`, "because an unreadable store cannot un-answer what the store already
|
|
15067
|
+
said". So `no` is never a guess about an unreadable store.
|
|
15068
|
+
🔴 BRANCH ON THIS KEY, NEVER REGEX THE MESSAGE: the two English sentences change with
|
|
15069
|
+
readability edits, this word does not. Both words are 503 (they say whether THIS write landed,
|
|
15070
|
+
not whether a retry can ever succeed).
|
|
15071
|
+
🔴 ABSENT ON SERVERS <= 7.92.2 (added at server 7.93.0 / core 7.26.0): on that generation the two
|
|
15072
|
+
strengths were BYTE-IDENTICAL on the wire and the single sentence said "retry, then reconcile" —
|
|
15073
|
+
which is the wrong order for `unknown`. The SDK forges no default for an absent key: read it
|
|
15074
|
+
through `APIError.extra` and treat absence as "this server cannot tell you".
|
|
15075
|
+
description: >
|
|
15076
|
+
The 503 body of `rulesWrite` and `rulesRevoke` — ONE shape for both verbs, because `committed` is one key
|
|
15077
|
+
off one closed word set minted in ONE place server-side (`sendRuleWriteFailure`), and a consumer branches
|
|
15078
|
+
on it identically for either verb.
|
|
15079
|
+
🔴 `committed` IS CONDITIONAL ON `errorCode`, hence NOT `required`: the same 503 slot also carries
|
|
15080
|
+
`auth.service_token_required` (both verbs sit in the credential-gated write family — a deployment with no
|
|
15081
|
+
service credential and no explicit `ALLOW_UNAUTHED_WRITES=true` never reaches the rule lane), and that
|
|
15082
|
+
refusal has no `committed` key. Read `errorCode` first.
|
|
15083
|
+
⚠️ `committed` answers "did THIS write land"; `stillLive` (on the 200 bodies) answers "is the rule
|
|
15084
|
+
standing NOW". Two different questions, two different keys, neither derives the other.
|
|
15085
|
+
|
|
14780
15086
|
ApprovalFrameEnvelope:
|
|
14781
15087
|
type: object
|
|
14782
15088
|
description: >
|
|
@@ -16237,6 +16543,18 @@ components:
|
|
|
16237
16543
|
core >= 7.6.0 (S6-B) — the status the endpoint answered with; rides ONLY with
|
|
16238
16544
|
`errorCode === "http_status"` (401/403 = re-authorize, 5xx = that end is down). ABSENT rather
|
|
16239
16545
|
than 0.
|
|
16546
|
+
liveness:
|
|
16547
|
+
allOf: [{ $ref: '#/components/schemas/McpServerLiveness' }]
|
|
16548
|
+
description: >
|
|
16549
|
+
core >= 7.24.3 (#874), server >= 7.91.2 — the engine's LAST OBSERVATION of reachability.
|
|
16550
|
+
🔴 SEPARATE FROM `status`: `status` is the verdict at dial time (the engine freezes it), this is what
|
|
16551
|
+
the connection layer last LEARNED — so `failed` beside `reachable` on one row is a REAL row (the server
|
|
16552
|
+
answered the handshake and answered with a protocol error = alive and misconfigured).
|
|
16553
|
+
🔴 ABSENCE IS NOT `unknown`: absent = NO USABLE OBSERVATION EXISTS (the leg never reached this server,
|
|
16554
|
+
the declaration was never dialed, an older mint, or the record failed its shape check), while `unknown`
|
|
16555
|
+
is the engine's STRONGER claim (it looked and the answer does not decide reach). Do not infer "was it
|
|
16556
|
+
dialed" from absence. Declared here as of sdk 11.2.0 — the key had been on the wire since server 7.91.2
|
|
16557
|
+
while this row schema stayed closed, so a strict consumer was rejecting a LEGITIMATE response.
|
|
16240
16558
|
|
|
16241
16559
|
ServerWiringGates:
|
|
16242
16560
|
type: object
|
|
@@ -17372,9 +17690,19 @@ components:
|
|
|
17372
17690
|
current state with `GET /v1/rules` instead.
|
|
17373
17691
|
|
|
17374
17692
|
|
|
17375
|
-
`unknown` —
|
|
17376
|
-
|
|
17377
|
-
|
|
17693
|
+
`unknown` — REACHABLE SINCE server 7.93.0: the write LANDED (the engine confirmed the add and handed
|
|
17694
|
+
back a real `rev`) and THE READ-BACK THAT WOULD ANSWER LIVENESS COULD NOT BE TAKEN (the store did not
|
|
17695
|
+
answer). `rule` is ABSENT. 🔴 SAME DISPOSITION AS `no` — DO NOT RETRY (a resend only answers `no-op`);
|
|
17696
|
+
reconcile with `GET /v1/rules`. Up to 7.92.2 this shape answered 500 `internal.error`, byte-identical
|
|
17697
|
+
to the one a PRE-write store failure produced — two opposite dispositions under one answer, which is
|
|
17698
|
+
exactly what these keys exist to separate. ⚠️ A store failure BEFORE the write is a different answer:
|
|
17699
|
+
503 `state.rule_write_failed` with `committed: "no"` (provably nothing landed ⇒ resending the same body
|
|
17700
|
+
is safe). Same store outage, opposite handling, decided by WHICH SIDE of the write it happened on.
|
|
17701
|
+
|
|
17702
|
+
|
|
17703
|
+
⇒ THE TWO ROWLESS WORDS SHARE ONE ARM (`RuleWriteResult_rowless`): identical key set, identical
|
|
17704
|
+
disposition; the WORD is what distinguishes "observed gone" from "nobody looked". The shared
|
|
17705
|
+
three-word table is narrowed per arm with an `enum` and is NEVER forked.
|
|
17378
17706
|
|
|
17379
17707
|
|
|
17380
17708
|
🔴 READ `stillLive` FIRST, THEN `rule`. The `rule` KEY LIVES ONLY ON THE `yes` ARM — it is not "present
|
|
@@ -17389,22 +17717,28 @@ components:
|
|
|
17389
17717
|
passed through verbatim). A shell that must span both generations reads an ABSENT `stillLive` AS `yes`
|
|
17390
17718
|
itself — that is its own judgement about its own deployment, not something this door said.
|
|
17391
17719
|
oneOf:
|
|
17392
|
-
- $ref: '#/components/schemas/
|
|
17393
|
-
- $ref: '#/components/schemas/
|
|
17720
|
+
- $ref: '#/components/schemas/RuleWriteResult_live'
|
|
17721
|
+
- $ref: '#/components/schemas/RuleWriteResult_rowless'
|
|
17394
17722
|
# 🔴 两支是**命名 schema**,不是内联形(codex 对抗复审 [medium],验真后修):OAS 3.1 的
|
|
17395
17723
|
# discriminator 明文**不考虑内联 schema**,而 mapping 指回父联合就等于「选不出臂」——
|
|
17396
17724
|
# 照它生成/解码的消费方拿不到分支,而普通 oneOf 校验照样绿,坏形因此不可见。
|
|
17725
|
+
# 🔴 **两支按「有没有活行可端」命名**(sdk 11.2.0 改名,codegen 面的名字变了、wire 逐字节不变):
|
|
17726
|
+
# `unknown` 自 server 7.93.0 起可达且与 `no` 同支,一个叫 `_no` 的臂承载两个词就是「名字与它所含的
|
|
17727
|
+
# 一半相反」——本仓明令禁止的那一形(server 侧 `indeterminate`→`failed` 改名同一条理由)。
|
|
17728
|
+
# mapping 必须给**每个**词一个键:少一个词,照 spec 解码的消费端对那个词选不出臂。
|
|
17397
17729
|
discriminator:
|
|
17398
17730
|
propertyName: stillLive
|
|
17399
17731
|
mapping:
|
|
17400
|
-
"yes": '#/components/schemas/
|
|
17401
|
-
"no": '#/components/schemas/
|
|
17732
|
+
"yes": '#/components/schemas/RuleWriteResult_live'
|
|
17733
|
+
"no": '#/components/schemas/RuleWriteResult_rowless'
|
|
17734
|
+
"unknown": '#/components/schemas/RuleWriteResult_rowless'
|
|
17402
17735
|
|
|
17403
|
-
|
|
17736
|
+
RuleWriteResult_live:
|
|
17404
17737
|
description: >
|
|
17405
17738
|
The LIVE-ROW arm of `rulesWrite`'s 200 (the ordinary outcome): the read-back saw the rule alive, so
|
|
17406
17739
|
there is a row to hand over. THE ONLY ARM THAT CARRIES `rule`. Never returned on its own — read it
|
|
17407
|
-
through `RuleWriteResult`.
|
|
17740
|
+
through `RuleWriteResult`. (Named `RuleWriteResult_yes` up to SDK 11.1.0 — renamed when its sibling
|
|
17741
|
+
started carrying two words; the wire is byte-identical, only codegen names change.)
|
|
17408
17742
|
type: object
|
|
17409
17743
|
additionalProperties: false
|
|
17410
17744
|
required: [status, rev, stillLive, rule]
|
|
@@ -17423,7 +17757,7 @@ components:
|
|
|
17423
17757
|
stillLive:
|
|
17424
17758
|
allOf: [{ $ref: '#/components/schemas/RemovalLiveness' }]
|
|
17425
17759
|
enum: ["yes"]
|
|
17426
|
-
description: 'The read-back SAW IT ALIVE — the ordinary outcome, and the only arm that carries `rule`. Same key, same word table as `rulesRevoke`;
|
|
17760
|
+
description: 'The read-back SAW IT ALIVE — the ordinary outcome, and the only arm that carries `rule`. Same key, same word table as `rulesRevoke`; since server 7.93.0 the same VALUE RANGE as well (all three words are reachable on this door too), and never a second table.'
|
|
17427
17761
|
rule:
|
|
17428
17762
|
allOf: [{ $ref: '#/components/schemas/PersistedRule' }]
|
|
17429
17763
|
description: >
|
|
@@ -17439,12 +17773,16 @@ components:
|
|
|
17439
17773
|
a human answered a card, `starter` = a deployment preset, `imported-cc` = a real import) — this
|
|
17440
17774
|
call wrote nothing, so it rewrites nothing either.
|
|
17441
17775
|
|
|
17442
|
-
|
|
17776
|
+
RuleWriteResult_rowless:
|
|
17443
17777
|
description: >
|
|
17444
|
-
The
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
`
|
|
17778
|
+
The NO-LIVE-ROW arm of `rulesWrite`'s 200 — TWO WORDS, ONE KEY SET, ONE DISPOSITION (named
|
|
17779
|
+
`RuleWriteResult_no` up to SDK 11.1.0; renamed at 11.2.0 when `unknown` became reachable, because an arm
|
|
17780
|
+
called `_no` that also carries `unknown` is a name that contradicts half of what it holds).
|
|
17781
|
+
The write LANDED (`rev` is real) and either a concurrent revoke reached the row before the read-back
|
|
17782
|
+
(`no`), or the read-back itself could not be taken (`unknown`, server >= 7.93.0). `rule` is ABSENT here
|
|
17783
|
+
BY CONSTRUCTION, and 🔴 RETRYING IS WRONG FOR BOTH WORDS (it re-erects a tightening someone just
|
|
17784
|
+
revoked, or simply answers `no-op`) — reconcile with `GET /v1/rules`. Never returned on its own — read
|
|
17785
|
+
it through `RuleWriteResult`.
|
|
17448
17786
|
type: object
|
|
17449
17787
|
additionalProperties: false
|
|
17450
17788
|
required: [status, rev, stillLive]
|
|
@@ -17459,12 +17797,18 @@ components:
|
|
|
17459
17797
|
rev: { type: integer, description: 'The rule bucket''s OCC revision after the call. REAL on this arm — the write landed; only the row''s liveness went the other way.' }
|
|
17460
17798
|
stillLive:
|
|
17461
17799
|
allOf: [{ $ref: '#/components/schemas/RemovalLiveness' }]
|
|
17462
|
-
enum: ["no"]
|
|
17463
|
-
description: >
|
|
17464
|
-
|
|
17465
|
-
between the engine minting the dot and this leg reading the
|
|
17466
|
-
|
|
17467
|
-
|
|
17800
|
+
enum: ["no", unknown]
|
|
17801
|
+
description: >
|
|
17802
|
+
WHICH KIND OF "no live row" this is. `no` = the read-back OBSERVED IT GONE: a legitimate concurrent
|
|
17803
|
+
revoke (yours, or an operator''s) landed between the engine minting the dot and this leg reading the
|
|
17804
|
+
row back. `unknown` = THE READ-BACK ITSELF COULD NOT BE TAKEN (the store did not answer; reachable
|
|
17805
|
+
since server 7.93.0, a 500 before that) — nobody observed the liveness, which is a WEAKER statement
|
|
17806
|
+
than `no` and must not be rendered as "gone".
|
|
17807
|
+
🔴 NEITHER IS an `indeterminate` and NEITHER IS a 503: the engine answered "it wrote" and handed
|
|
17808
|
+
back a real `rev` on both. Retrying is wrong for both (re-erecting a just-revoked tightening, or
|
|
17809
|
+
simply getting `no-op`) — reconcile with `GET /v1/rules`. `rule` is ABSENT on this arm by
|
|
17810
|
+
construction. ⚠️ Do not confuse `unknown` here with 503 `committed: "no"`: that is the SAME store
|
|
17811
|
+
failing BEFORE the write, where nothing landed and resending is safe.
|
|
17468
17812
|
|
|
17469
17813
|
RuleRevokeRequest:
|
|
17470
17814
|
type: object
|
|
@@ -17522,9 +17866,14 @@ components:
|
|
|
17522
17866
|
to prevent. 🔴 `unknown` is NOT "the revoke failed" — that is the 503 `state.rule_remove_failed` arm
|
|
17523
17867
|
(no tombstone written: retry, then reconcile). On `unknown` the tombstone IS written: do NOT retry
|
|
17524
17868
|
(a retry answers `no-op`, which reads as "nothing to remove" and only confuses); verify with
|
|
17525
|
-
`GET /v1/rules` instead. It rides the `no-op` arm too
|
|
17526
|
-
|
|
17527
|
-
|
|
17869
|
+
`GET /v1/rules` instead. It rides the `no-op` arm too, and 🔴 `no-op` + `unknown` IS A REACHABLE PAIR
|
|
17870
|
+
SINCE server 7.93.0: that arm now reads the SAME liveness read-back as `removed` (engine 7.26.0 gives
|
|
17871
|
+
`RemoveResult`'s `no-op` arm the word itself — one read, one bucket, one identity predicate), so the
|
|
17872
|
+
two arms are BYTE-FOR-BYTE identical in key set, word table and consumer disposition. Up to 7.92.2 the
|
|
17873
|
+
server took that read-back itself and 5xx'd when it failed, so the same fact ("the read that answers
|
|
17874
|
+
liveness did not complete") had two fates across the two arms. A client whose `no-op` switch only has
|
|
17875
|
+
`yes` / `no` (which the 7.92.x contract did promise) must add the third word — it no longer arrives as
|
|
17876
|
+
a 5xx. Migration: `true` → `yes`, `false` → `no`, anything else → `unknown`.
|
|
17528
17877
|
🔴 APPLICABILITY FLOOR: this schema describes server >= 7.92.0. A server <= 7.91.2 still sends a
|
|
17529
17878
|
boolean here, so validating such a response against this spec goes RED — that is EXPECTED, not a
|
|
17530
17879
|
defect (the SDK deliberately carries no union / dual-read); a shell that must span both generations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/sdk",
|
|
3
|
-
"version": "11.0
|
|
3
|
+
"version": "11.2.0",
|
|
4
4
|
"description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|