@sema-agent/sdk 9.5.0 → 9.7.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 +61 -1
- package/dist/errors.d.ts +20 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -2
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +43 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/approvals.d.ts +20 -2
- package/dist/resources/approvals.d.ts.map +1 -1
- package/dist/resources/approvals.js.map +1 -1
- package/dist/resources/assistant.d.ts +8 -1
- package/dist/resources/assistant.d.ts.map +1 -1
- package/dist/resources/assistant.js +8 -1
- package/dist/resources/assistant.js.map +1 -1
- package/dist/resources/fleet.d.ts +15 -1
- package/dist/resources/fleet.d.ts.map +1 -1
- package/dist/resources/fleet.js.map +1 -1
- package/dist/resources/ops.d.ts +29 -5
- package/dist/resources/ops.d.ts.map +1 -1
- package/dist/resources/ops.js +12 -1
- package/dist/resources/ops.js.map +1 -1
- package/dist/resources/sessions.d.ts +23 -1
- package/dist/resources/sessions.d.ts.map +1 -1
- package/dist/resources/sessions.js +27 -0
- package/dist/resources/sessions.js.map +1 -1
- package/dist/resources/tool-approvals.d.ts +29 -1
- package/dist/resources/tool-approvals.d.ts.map +1 -1
- package/dist/resources/tool-approvals.js +2 -0
- package/dist/resources/tool-approvals.js.map +1 -1
- package/dist/settings.d.ts +32 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/sse.d.ts +14 -3
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js.map +1 -1
- package/dist/types.d.ts +453 -9
- package/dist/types.d.ts.map +1 -1
- package/openapi.yaml +738 -35
- package/package.json +1 -1
package/openapi.yaml
CHANGED
|
@@ -83,6 +83,25 @@ info:
|
|
|
83
83
|
• New contract fields are OPTIONAL with no default (CORE-PRINCIPLES §6).
|
|
84
84
|
• Async results are delivered by POLL (`GET /v1/runs/:id`) or SSE (`/v1/runs/:id/events`).
|
|
85
85
|
There is NO generic webhook/callback and NO signing (SERVICE-CORE-CONTEXT §2.4).
|
|
86
|
+
• 🔴 STRING-LENGTH UNIT (applies to EVERY `maxLength`/`minLength` in this file). The producer's own
|
|
87
|
+
gates are JavaScript `.length`, i.e. **UTF-16 code units**; JSON Schema counts **code points**.
|
|
88
|
+
The two agree across the entire BMP and differ only on supplementary characters (emoji, rare CJK,
|
|
89
|
+
musical symbols), each of which costs 2 units but 1 code point. => near a bound, a value made of
|
|
90
|
+
supplementary characters can pass this schema and still be refused by the producer. THAT DIRECTION
|
|
91
|
+
IS DELIBERATE: a schema looser than the producer yields a loud, readable 4xx naming the field, while
|
|
92
|
+
a schema tighter than the producer makes a client refuse a request the producer would have accepted —
|
|
93
|
+
an invisible false refusal whose symptom is indistinguishable from a server rejection (this contract
|
|
94
|
+
shipped exactly that defect on `SkillSpec.content` for several releases). Halving every bound to be
|
|
95
|
+
safe for all-supplementary input would refuse roughly half of the legal range on ordinary text, so the
|
|
96
|
+
bounds here state the producer's number verbatim and this convention states the unit. A client that
|
|
97
|
+
must pre-check exactly counts UTF-16 units itself.
|
|
98
|
+
(`maxLength` is the only keyword involved: it counts code points and that is not configurable. A
|
|
99
|
+
`pattern` could count code units — but only on validators whose regex engine is UTF-16-based and
|
|
100
|
+
non-unicode-mode, which is implementation-specific rather than contract-portable, and it would cost a
|
|
101
|
+
full regex scan of a megabyte-scale payload. The unit is therefore stated, not encoded.)
|
|
102
|
+
• The same unit applies to every BYTE-sounding phrase in this file unless it says UTF-8 explicitly: the
|
|
103
|
+
producer's guards are character/unit counts, so a non-ASCII value is NOT twice as expensive against
|
|
104
|
+
them. Do not pre-check these bounds with a byte length.
|
|
86
105
|
x-sources:
|
|
87
106
|
- ../docs/SERVICE-CORE-CONTEXT.md # §2 wire answers, §4 verb surface (authoritative producer input)
|
|
88
107
|
- ../docs/CORE-PRINCIPLES.md # §2 secret discipline, §3 principal-first, §5 contract-as-anchor, §6 optional-no-default
|
|
@@ -2060,6 +2079,77 @@ paths:
|
|
|
2060
2079
|
schema: { $ref: '#/components/schemas/McpStatusPanel' }
|
|
2061
2080
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2062
2081
|
|
|
2082
|
+
/v1/sessions/{sessionId}/mcp/reconnect:
|
|
2083
|
+
parameters:
|
|
2084
|
+
- $ref: '#/components/parameters/PrincipalHeader'
|
|
2085
|
+
- in: path
|
|
2086
|
+
name: sessionId
|
|
2087
|
+
required: true
|
|
2088
|
+
schema: { type: string }
|
|
2089
|
+
post:
|
|
2090
|
+
tags: [sessions]
|
|
2091
|
+
operationId: sessionsMcpReconnect
|
|
2092
|
+
x-status: live # S-381 (server 7.85.0; core 7.22.0 #857); SDK sessions.mcpReconnect.
|
|
2093
|
+
summary: Re-dial ONE named MCP server inside a live session (a TRANSACTION, not a refresh button).
|
|
2094
|
+
description: >
|
|
2095
|
+
S-381 (server >= 7.85.0; core 7.22.0 #857) — re-dial the transport of ONE NAMED MCP server WITHOUT
|
|
2096
|
+
ending the task. Body is `{ server }`: required, non-empty, at most 190 characters, and the body gate
|
|
2097
|
+
is STRICT — a misspelled key is refused, never stripped-and-ignored, and there is NO all-servers form
|
|
2098
|
+
(re-dialing everything would CLOSE the healthy connections, and a stdio server that owns a device
|
|
2099
|
+
cannot be re-dialed while the old connection still holds it). A bad body ⇒ 400 `request.body_shape`.
|
|
2100
|
+
🔴 A TRANSACTION, NOT A REFRESH BUTTON (core's own words — this sentence belongs in the UI copy): the
|
|
2101
|
+
old connection is closed BEFORE the new dial, so a FAILED re-dial spends a working connection — the
|
|
2102
|
+
server stays disconnected, ITS TOOLS ARE WITHDRAWN from the model's roster, and in-flight calls fail.
|
|
2103
|
+
Offering "reconnect" on a HEALTHY server is offering a transaction.
|
|
2104
|
+
🔴 `outcome` IS THE ONLY DISCRIMINATOR AND ALL THREE STATES ARE HTTP 200 (the status answers only
|
|
2105
|
+
"did the verb reach the engine", the same law as steer's "branch on `delivery`, not the status"):
|
|
2106
|
+
`accepted` = core's `reconnected`; `refused` = core's `refused` OR `not_declared` (an unknown server
|
|
2107
|
+
name is a ROSTER fact, not an engine-capability fact); `unsupported` = THE SERVER'S OWN WORD (core has
|
|
2108
|
+
none) for "this run declared no MCP at all" (core throws `mcp.reconnect_unavailable`) — and that arm is
|
|
2109
|
+
an ALWAYS-FIVE-KEY body `{taskId, sessionId, server, outcome, reason}`: with no core result the server
|
|
2110
|
+
coins NO `prefix`/`toolCount`/`added`/`removed`/`toolNames`. Consumers MUST branch on `outcome` and
|
|
2111
|
+
must NOT read `body.added` unconditionally.
|
|
2112
|
+
🔴 `toolNames` is PRESENT-IFF core supplied `tools` (NOT keyed off `outcome`): `[]` (a failed dial) and
|
|
2113
|
+
ABSENT (an arm that never touched the connection) are two different sentences. It is the roster THIS
|
|
2114
|
+
re-dial asserted for its own `prefix` domain, NOT "what the model finally sees" — core filters again by
|
|
2115
|
+
the deployment's exclusion table before mounting, and a refused axis-fold mounts none, so this key is a
|
|
2116
|
+
SUPERSET in both shapes. For "what actually got mounted" read a roster face.
|
|
2117
|
+
Next action comes from `status` (machine-readable: `errorCode` is core's `McpFailureKind` word — do not
|
|
2118
|
+
narrow it, switch with a default arm; `httpStatus` 401 is a re-authorize door); `reason` is HUMAN TEXT
|
|
2119
|
+
(server-redacted, length-bounded) and must NOT be parsed.
|
|
2120
|
+
Gates, the same family as steer / capture-optout: 501 `capability.run_store_required` (no durable run
|
|
2121
|
+
ledger) · 501 `capability.session_ownership_required` (no `sessionStorage.ownerOf`) · 400
|
|
2122
|
+
`request.path_malformed` (bad percent-encoding in the id) · 404 `not_found.session` — unknown session
|
|
2123
|
+
AND someone else's session answer the SAME code and SAME message (deliberate: no existence oracle); an
|
|
2124
|
+
EXPLICIT operator may re-dial any tenant's session · 422 `steering.invalid_content` · 409
|
|
2125
|
+
`steering.not_running` with FOUR arms behind ONE code (parked awaiting a decision / already terminal /
|
|
2126
|
+
live on ANOTHER replica / no live steerable face here). A re-dial is LEG-LOCAL — a durable resume dials
|
|
2127
|
+
every declared server afresh — so "park a re-dial until resume" is structurally not a shape, and this
|
|
2128
|
+
face deliberately has no park leg. Probe with `capabilities.mcpReconnect`; absent ⇒ probe by 404/405.
|
|
2129
|
+
requestBody:
|
|
2130
|
+
required: true
|
|
2131
|
+
content:
|
|
2132
|
+
application/json:
|
|
2133
|
+
schema:
|
|
2134
|
+
type: object
|
|
2135
|
+
additionalProperties: false
|
|
2136
|
+
required: [server]
|
|
2137
|
+
properties:
|
|
2138
|
+
server: { type: string, minLength: 1, maxLength: 190, description: 'The ONE named MCP server to re-dial (the declared name). Required; there is no all-servers form. The cap is load-bearing: the string is relayed into an operator notice detail.' }
|
|
2139
|
+
responses:
|
|
2140
|
+
'200':
|
|
2141
|
+
description: >-
|
|
2142
|
+
The re-dial receipt. Branch on `outcome`; the `unsupported` arm is an always-five-key body.
|
|
2143
|
+
content:
|
|
2144
|
+
application/json:
|
|
2145
|
+
schema: { $ref: '#/components/schemas/SessionMcpReconnectResult' }
|
|
2146
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
2147
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2148
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
2149
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
2150
|
+
'422': { $ref: '#/components/responses/UnprocessableEntity' }
|
|
2151
|
+
'501': { $ref: '#/components/responses/NotImplemented' }
|
|
2152
|
+
|
|
2063
2153
|
/v1/sessions/{sessionId}/memory-status:
|
|
2064
2154
|
parameters:
|
|
2065
2155
|
- $ref: '#/components/parameters/PrincipalHeader'
|
|
@@ -2559,9 +2649,24 @@ paths:
|
|
|
2559
2649
|
`data.type` — proxy-safe dispatch. Cross-replica by
|
|
2560
2650
|
construction (polls the SHARED checkpoint table). 15-min cap + heartbeats; a DB blip retries, never
|
|
2561
2651
|
kills the stream. Scope = the caller's principal (operator/trace token = fleet-wide).
|
|
2652
|
+
🔴 TWO MORE EVENTS (server >= 7.87.0 / S-454 P1; cli L-397): this leg now has TWO diff sources, not one.
|
|
2653
|
+
`live_pending` = a LIVE (in-stream) ask entered or its wire projection changed (UPSERT semantics;
|
|
2654
|
+
payload = a `LivePendingRow` spread plus `serverNowMs`, AND IT CARRIES `frame`, so a consumer can render
|
|
2655
|
+
the card straight from the push). `live_resolved` = a live ask left (settled / window expired / entry
|
|
2656
|
+
gone — ALL THREE ARE ONE FRAME: "why it left" is for the consumer to fetch, the push does not conclude
|
|
2657
|
+
it; payload = `{type, approvalId, serverNowMs}`). First-tick order: `pending…` → `live_pending…` →
|
|
2658
|
+
`synced` (and `synced.count` still counts ONLY the durable queue).
|
|
2659
|
+
🔴 THE TWO WORDS DELIBERATELY DO NOT REUSE `pending`/`resolved`: those are keyed by
|
|
2660
|
+
`(sessionId, toolCallId)` and decided at `POST /v1/approvals/{sessionId}/decide`, while a live row is
|
|
2661
|
+
keyed by `approvalId` and decided at `POST /v1/tool-approvals/{approvalId}/respond`. Sharing one word
|
|
2662
|
+
would eventually send a live card to the decide door — a 404 with no wire evidence of the mistake.
|
|
2663
|
+
⚠️ Both live words come from an IN-PROCESS window projection, so their cross-replica reach is exactly
|
|
2664
|
+
that of `GET /v1/approvals`'s `livePending` — narrower than the durable pair above. The deployment
|
|
2665
|
+
without a live approval face (coordinator unwired) emits neither, and the stream's bytes are identical
|
|
2666
|
+
to 7.86.0's (capability absent = zero change, not an empty event).
|
|
2562
2667
|
responses:
|
|
2563
2668
|
'200':
|
|
2564
|
-
description: text/event-stream of meta/pending/resolved frames.
|
|
2669
|
+
description: text/event-stream of meta/pending/resolved/live_pending/live_resolved frames.
|
|
2565
2670
|
content:
|
|
2566
2671
|
text/event-stream:
|
|
2567
2672
|
schema: { type: string }
|
|
@@ -2602,9 +2707,15 @@ paths:
|
|
|
2602
2707
|
string; `remember` not `"session"`; `checkpointToken`/`boundCallId`/`boundInputHash` not a string)
|
|
2603
2708
|
· "request.field_conflict" (`remember` without `decision:"approve"`; TC-5.4 answer coherence —
|
|
2604
2709
|
approving a pending AskUserQuestion REQUIRES `answer`, and `answer` is only valid on that approve)
|
|
2605
|
-
· "remember_not_in_proof" / "updated_input_not_in_proof"
|
|
2606
|
-
direct-door worker — not covered by the
|
|
2607
|
-
|
|
2710
|
+
· "remember_not_in_proof" / "updated_input_not_in_proof" / "checkpoint_token_not_in_proof"
|
|
2711
|
+
(`remember` / `updatedInput` / `checkpointToken` on a direct-door worker — not covered by the
|
|
2712
|
+
decision proof; the third one is server >= 7.87.0 / S-452, and its message points at the
|
|
2713
|
+
replacement: bind the decision with `boundCallId` + `boundInputHash`. Refusing it loses nothing —
|
|
2714
|
+
the token was only ever a staleness check, and on the direct door that check is already covered by
|
|
2715
|
+
the proof's binding. A compliant client never sends it: this SDK deleted the field at 1.0.0)
|
|
2716
|
+
· "request.field_invalid" ALSO covers an unrecognised key inside `answer` (server >= 7.87.0 /
|
|
2717
|
+
S-452: the closed answer shape — the message names the key paths and the body carries `unknownKeys`)
|
|
2718
|
+
· "remember_requires_binding" (`remember` without the D-1 binding echo). PARKED variant (server 5.12.0, core 5.7.0 RB-459):
|
|
2608
2719
|
`decide.parked_answer_required` — the parked child's pending action is an AskUserQuestion and the
|
|
2609
2720
|
approve carried no `answer` (the ONLY pre-claim rejection left on that leg; the retired codes
|
|
2610
2721
|
`decide.parked_answer_unsupported` / `decide.parked_question_unsupported` are gone — an answer HAS
|
|
@@ -2996,8 +3107,15 @@ paths:
|
|
|
2996
3107
|
schema: { $ref: '#/components/schemas/AssistantTaskStatus' }
|
|
2997
3108
|
'400':
|
|
2998
3109
|
description: >
|
|
2999
|
-
|
|
3000
|
-
|
|
3110
|
+
`ErrorResponse`-shaped. Codes: "request.body_shape" (bad/missing `decision`, OR an unrecognised
|
|
3111
|
+
top-level key — server >= 7.86.0 / S-438 key closure, the body carries `unknownKeys` /
|
|
3112
|
+
`unsupportedKeys`) · "request.field_conflict" (`edit` without a non-empty `editedPlan`;
|
|
3113
|
+
`editedPlan` present on approve/reject; `permissionModeAfter` sent with `edit`/`reject`;
|
|
3114
|
+
`permissionModeAfter` on a task that was not submitted with `permissionMode:"plan"`) ·
|
|
3115
|
+
"request.field_invalid" (non-string `reason`; `permissionModeAfter` outside the two-word closed
|
|
3116
|
+
set — zero widening arm) · "request.path_malformed" (a bad percent-encoded path segment —
|
|
3117
|
+
server >= 7.86.0 / S-430 turned that family from 500 `internal.error` into a 400 across 16 doors).
|
|
3118
|
+
413 `reason_too_large` rides its own status.
|
|
3001
3119
|
content:
|
|
3002
3120
|
application/json:
|
|
3003
3121
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -5014,11 +5132,16 @@ paths:
|
|
|
5014
5132
|
tags: [metrics]
|
|
5015
5133
|
operationId: metricsSummary
|
|
5016
5134
|
x-status: draft # ops surface; uses a metricsToken (NOT a principal). Shape loose.
|
|
5017
|
-
x-sdk: none # DELIBERATELY not in @sema-agent/sdk (2026-07-27 定性): ops/observability face, credential = metricsToken not a principal; README walks curl. Revocable — delete this key + the gate exemption to bring it in.
|
|
5018
5135
|
summary: Worker health/cost summary (ops).
|
|
5019
5136
|
description: >
|
|
5020
|
-
Uses `metricsToken` (operator), not `x-agent-principal`.
|
|
5021
|
-
|
|
5137
|
+
Uses `metricsToken` (operator) OR a full authToken, not `x-agent-principal`.
|
|
5138
|
+
⚠️ The `x-sdk: none` key that used to sit here ("deliberately not in @sema-agent/sdk", 2026-07-27) was
|
|
5139
|
+
STALE and is deleted at sdk 9.7.0: the SDK has wrapped this face since the 2026-07-17 coverage batch
|
|
5140
|
+
(`client.metrics.summary()`), and since 9.7.0 the two S-451 keys are named on the returned type
|
|
5141
|
+
(`MetricsSummaryOps`). The path stays on the spec-path gate's spec-only exemption list for a DIFFERENT
|
|
5142
|
+
reason, now written there: that gate's SDK-side extractor only collects `/v1/...` literals, so a
|
|
5143
|
+
non-`/v1` path can never match — the exemption compensates for the EXTRACTOR's scope, not for a
|
|
5144
|
+
missing verb.
|
|
5022
5145
|
security:
|
|
5023
5146
|
- metricsToken: []
|
|
5024
5147
|
responses:
|
|
@@ -5026,11 +5149,9 @@ paths:
|
|
|
5026
5149
|
description: Metrics summary (shape draft).
|
|
5027
5150
|
content:
|
|
5028
5151
|
application/json:
|
|
5029
|
-
# 🔴 census 批2
|
|
5030
|
-
#
|
|
5031
|
-
#
|
|
5032
|
-
# untouched (still deliberately outside the SDK's wrapped surface) — closing the SHAPE and
|
|
5033
|
-
# keeping it un-wrapped are independent axes.
|
|
5152
|
+
# 🔴 census 批2 五段把它按真铸点 CLOSED(`{ model, ...deps.metrics.summarize() }` 是定键集,
|
|
5153
|
+
# 不是 ops free-for-all)。sdk 9.7.0 **改开**并补上生产方早已多出来的三键 —— 理由与 receipts
|
|
5154
|
+
# 在 `MetricsSummaryOps` 的 description 里(投影型响应体开集;请求体仍封闭)。
|
|
5034
5155
|
schema: { $ref: '#/components/schemas/MetricsSummaryOps' }
|
|
5035
5156
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
5036
5157
|
|
|
@@ -5297,6 +5418,20 @@ components:
|
|
|
5297
5418
|
waitMs: { type: integer }
|
|
5298
5419
|
decision: { type: string }
|
|
5299
5420
|
toolName: { type: string }
|
|
5421
|
+
toolCallId:
|
|
5422
|
+
type: string
|
|
5423
|
+
description: >-
|
|
5424
|
+
core 7.23.0 (#897), server >= 7.87.0 — the engine's own call id, passed through verbatim.
|
|
5425
|
+
🔴 ABSENT MEANS "NOT JOINABLE", NOT "no call": three of the four mint sites (an inherited
|
|
5426
|
+
grant being reused, the durable-resume leg, the park self-check's synthetic row) may have
|
|
5427
|
+
no call identity to give. 🔴 JOIN BY THIS KEY — never by row count or position: a
|
|
5428
|
+
multi-leg aggregation (verify / cascade / repair-loop) pushes each leg's gates as a
|
|
5429
|
+
BLOCK, so the order is "leg completion order concatenated", and a consumer's own
|
|
5430
|
+
permission ledger legitimately has a DIFFERENT number of rows. A right-hand key existing
|
|
5431
|
+
is not a reason to mint a parallel ledger elsewhere: the engine's is authoritative.
|
|
5432
|
+
⚠️ Since server 7.84.0 (S-394) the model-authored `toolArg` here goes through the SAME
|
|
5433
|
+
redactor as `result`, so credential-shaped BYTES CHANGE (anything else is byte-identical)
|
|
5434
|
+
— a consumer hashing these fields for identity must move off them.
|
|
5300
5435
|
required: [turns, tokens]
|
|
5301
5436
|
# 🔴 OPEN: the engine may carry fields not named here (live-observed, e.g. extra costBreakdown axes) — the SDK
|
|
5302
5437
|
# passes them through rather than dropping, so consumers must not assume this list is exhaustive.
|
|
@@ -5418,11 +5553,16 @@ components:
|
|
|
5418
5553
|
Server cap 16384 chars → 400.
|
|
5419
5554
|
skills:
|
|
5420
5555
|
type: array
|
|
5421
|
-
maxItems: 10
|
|
5422
5556
|
description: >
|
|
5423
5557
|
Per-request user skills (LIVE, service 170c384) — progressive disclosure, same
|
|
5424
5558
|
mechanism as scenario skills. Merge: scenario WINS on name collision (user's dropped, warn-logged).
|
|
5425
|
-
|
|
5559
|
+
🔴 server >= 7.78.1 (hard BREAKING, no alias): the ITEM-COUNT cap (was 10), the `description` 1024-char
|
|
5560
|
+
cap and the `name` 64-char cap are GONE — and so are their three refusal messages, so a consumer branch
|
|
5561
|
+
anchored on them can be deleted (a refusal turned into an acceptance: no harm in the other direction).
|
|
5562
|
+
The only size gate left on this face is `SkillSpec.content` <= 1 MiB (the engine's own load gate);
|
|
5563
|
+
bulk is otherwise bounded by the NON-skill-specific 8 MiB request-body cap. Per-turn cost belongs to the
|
|
5564
|
+
LISTING RENDERER, not to this gate: turn it down with `settings.skillListingBudgetFraction` /
|
|
5565
|
+
`settings.skillListingMaxDescChars`.
|
|
5426
5566
|
items: { $ref: '#/components/schemas/SkillSpec' }
|
|
5427
5567
|
reasoningEffort:
|
|
5428
5568
|
type: string
|
|
@@ -5681,6 +5821,33 @@ components:
|
|
|
5681
5821
|
`memory.capture_optout_denied` (terminal). A worker older than 7.70.0 IGNORES the key silently, so
|
|
5682
5822
|
it is not a capability probe. Rides the persisted body onto resume legs.
|
|
5683
5823
|
Mid-run flip: POST /v1/runs/{taskId}/memory/capture-optout (the same record, a second ingress).
|
|
5824
|
+
memory:
|
|
5825
|
+
type: string
|
|
5826
|
+
enum: [off]
|
|
5827
|
+
description: >
|
|
5828
|
+
server >= 7.84.0 (S-405 / core #863 W3; mint `src/http/wire-types.ts:309`) — the REQUEST-level
|
|
5829
|
+
"no memory face at all" declaration: "off" means THIS RUN mounts no memory face (no `# Memory`
|
|
5830
|
+
section, no index, none of the three memory tools, no write admission). A ONE-MEMBER closed set:
|
|
5831
|
+
there is NO "on" spelling, absence is the only way to say "memory as usual".
|
|
5832
|
+
🔴 THREE ORTHOGONAL AXES, do not mix them up: this key = "this RUN mounts no memory face";
|
|
5833
|
+
`memoryCapture:"off"` = "memory stays mounted, but THIS SESSION's content never enters long-term
|
|
5834
|
+
memory"; `memoryWrite:false` = "this run reads memory as usual, the harvest just does not commit".
|
|
5835
|
+
🔴 SENDING BOTH IS NOT BUYING BOTH (contract §12.7, verified against core `prepare-memory.js:48/:55`):
|
|
5836
|
+
core conjoins the capture leg with the engine mount, so on a turn carrying `memory:"off"` the
|
|
5837
|
+
capture opt-out's ONE-WAY session record DOES NOT LAND ⇒ the next turn without this key captures as
|
|
5838
|
+
usual. To make the session permanently uncaptured, do NOT send this key on that turn.
|
|
5839
|
+
⚠️ Honest scope: this is "no memory face", NOT a filesystem ban — if the memory root sits inside the
|
|
5840
|
+
workspace this run was granted, ordinary Read/Write still go through the ordinary fences.
|
|
5841
|
+
⚠️ The FACT IS KEPT, not erased: the server passes it through as `TaskSpec.memory.enabled:false`
|
|
5842
|
+
(it does not drop the spec) ⇒ the terminal observation face reads `disabled`, not `no-spec`.
|
|
5843
|
+
⚠️ Same name, different thing from the legacy `GET /v1/capabilities.memory` bit (always false — the
|
|
5844
|
+
RETIRED MemoryStore verb family). Do not read that bit to decide whether this key is available.
|
|
5845
|
+
🔴 NO CAPABILITY BIT: 7.78.1–7.83.x can trial-by-400 (the spelling gate refuses); older workers
|
|
5846
|
+
cannot be probed (they ignore it silently).
|
|
5847
|
+
🔴 The spelling gate is loud on BOTH legs: "OFF" / a boolean / anything else is a 400
|
|
5848
|
+
`request.field_invalid` (fresh admission and the resume replay share ONE predicate,
|
|
5849
|
+
`singleMemberOffFieldIssue`; on the resume leg it folds to 409 `resume_blocked_by_policy`).
|
|
5850
|
+
Rides the persisted body onto resume legs.
|
|
5684
5851
|
settings:
|
|
5685
5852
|
type: object
|
|
5686
5853
|
description: >
|
|
@@ -5690,7 +5857,24 @@ components:
|
|
|
5690
5857
|
service ACTIVELY interprets this bundle (task-settings.ts: permissions + permissions.defaultMode +
|
|
5691
5858
|
model + outputStyle + env shipped in the 1.26.0 v1 scope; hooks was deferred at that point and wired
|
|
5692
5859
|
in a later batch — both are live and interpreted today, not a raw uninterpreted passthrough);
|
|
5693
|
-
`permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`.
|
|
5860
|
+
`permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`.
|
|
5861
|
+
🔴 ON THIS SUBMIT FACE THE KEY SET IS CLOSED (server >= 7.57.0 for the sub-tree): accepted top-level keys
|
|
5862
|
+
are `env` / `hooks` / `model` / `outputStyle` / `permissions` / `skillListingBudgetFraction` /
|
|
5863
|
+
`skillListingMaxDescChars` / `ultracode` / `webSearch`, and accepted `permissions.*` keys are
|
|
5864
|
+
`allow` / `ask` / `defaultMode` / `deny` / `disableAutoMode`; anything else is 400 `request.body_shape`
|
|
5865
|
+
with the names in the refusal's `unknownKeys` / `unsupportedKeys` arrays (REFUSED, not dropped — a dropped
|
|
5866
|
+
key reads as "in effect" to the caller). The object stays OPEN here because the same `SemaSettings`
|
|
5867
|
+
schema also serves the local `settings.json` wiring, where deferred CC keys are legal.
|
|
5868
|
+
🔴 `skillListingBudgetFraction` (number in (0,1]) / `skillListingMaxDescChars` (positive integer),
|
|
5869
|
+
server >= 7.78.1: the SKILL-LISTING RENDER BUDGET, passed to the engine with ZERO processing
|
|
5870
|
+
(no clamping, no conversion, no folding) — ABSENT means the key is not written at all (the server does
|
|
5871
|
+
NOT restate the engine defaults, CC-identical `0.01` / `1536`), and an out-of-domain value is
|
|
5872
|
+
400 `request.field_invalid` on the fresh leg (the resume replay leg drops it instead of 4xx).
|
|
5873
|
+
⚠️ `skillListingBudgetFraction` is a ONE-WAY knob — turning it DOWN works, turning it UP does not:
|
|
5874
|
+
the engine's listing budget is `min(window x fraction, 8000 bytes)` and 8000 is a STRUCTURAL ceiling of
|
|
5875
|
+
the delivery lane, so raising it only helps a model whose declared window is below 200 000 tokens.
|
|
5876
|
+
The engine's own twin code `config.skill_listing_budget_invalid` (400) is NOT reachable through this
|
|
5877
|
+
submit leg today — it belongs to callers that build a `TaskSpec` directly.
|
|
5694
5878
|
additionalProperties: true
|
|
5695
5879
|
cwd: { type: string, description: Working directory for the execution env. }
|
|
5696
5880
|
selfOrchestration: { type: boolean, description: Permit S8 self-orchestration (run_workflow) inside this task. }
|
|
@@ -6620,9 +6804,106 @@ components:
|
|
|
6620
6804
|
properties:
|
|
6621
6805
|
id: { type: string }
|
|
6622
6806
|
status: { type: string, enum: [running, completed, failed, needs_human] }
|
|
6623
|
-
result:
|
|
6807
|
+
result:
|
|
6808
|
+
allOf: [{ $ref: '#/components/schemas/LeaderRunResult' }]
|
|
6809
|
+
description: >
|
|
6810
|
+
The leader run's terminal payload. sdk 9.7.0 declares the two segments a consumer actually renders
|
|
6811
|
+
(`conflict` / `salvaged`) and leaves the rest open — see `LeaderRunResult`. (The SDK's TS side keeps
|
|
6812
|
+
`LeaderRecord.result: unknown` for one more version: pointing it at the named type is a NARROWING,
|
|
6813
|
+
which RELEASE.md reserves for a major. Cast it: `record.result as LeaderRunResult`.)
|
|
6624
6814
|
error: { type: string }
|
|
6625
6815
|
|
|
6816
|
+
LeaderRunResult:
|
|
6817
|
+
type: object
|
|
6818
|
+
additionalProperties: true
|
|
6819
|
+
description: >
|
|
6820
|
+
The `result` of a leader run (server `leader/leader.ts` `LeaderResult`). Only the segments a consumer
|
|
6821
|
+
really renders are declared; the rest passes through (leader is a gated v2 lane and its result carries
|
|
6822
|
+
server-internal shapes — mirroring all of them here would just create a second copy that drifts).
|
|
6823
|
+
🔴 S-113 / clay C-R63 ④ (server >= 7.83.0): A MERGE THAT DOES NOT APPLY IS HANDED TO THE USER (the CC
|
|
6824
|
+
shape). The apply conflict no longer starts a conflict-resolver model: `ok:false` plus the `conflict`
|
|
6825
|
+
segment, and the run's status is the EXISTING third terminal `needs_human` (no new status word). The
|
|
6826
|
+
user takes `workers[].branch` + `files` and merges/cherry-picks. SAME BATCH, DELETED WITH ZERO ALIASES:
|
|
6827
|
+
`merge.conflictsResolved`, `conflictResolverCostUsd`, and the `merge-conflict` member of
|
|
6828
|
+
`replan.trigger` — a consumer that read them goes tsc-red, and that IS the notice.
|
|
6829
|
+
properties:
|
|
6830
|
+
ok: { type: boolean, description: 'Did the whole wave succeed (always false on a conflict terminal).' }
|
|
6831
|
+
conflict:
|
|
6832
|
+
type: object
|
|
6833
|
+
additionalProperties: true
|
|
6834
|
+
description: >
|
|
6835
|
+
The wave that would not merge (server >= 7.83.0; ONE mint — `conflictSegment` — shared by the main
|
|
6836
|
+
path and the collapse path, so "the same situation, two different terminals" is structurally gone).
|
|
6837
|
+
ABSENT = no conflict on this wave, OR an older server — the two look the same, so do not infer a
|
|
6838
|
+
version from absence.
|
|
6839
|
+
required: [baseSha, files, workers]
|
|
6840
|
+
properties:
|
|
6841
|
+
baseSha: { type: string, description: "The user's merge base = this wave's durable base." }
|
|
6842
|
+
files: { type: array, items: { type: string }, description: 'Paths carrying conflict markers / .rej (CONTENTS NOT INCLUDED).' }
|
|
6843
|
+
filesTruncated:
|
|
6844
|
+
type: boolean
|
|
6845
|
+
enum: [true]
|
|
6846
|
+
description: >-
|
|
6847
|
+
Present only when the list hit the CAP. 🔴 ABSENCE DOES NOT MEAN "this is all of it" (corrected
|
|
6848
|
+
at sdk 9.7.0 after a codex finding was verified against the server's own probe arm in
|
|
6849
|
+
`leader/merge.ts`): this flag reflects the CAP and nothing else. The list is assembled by two
|
|
6850
|
+
shell probes (`grep -rIl` for conflict markers + `find -name '*.rej'`), and when either leg
|
|
6851
|
+
fails the server honestly returns a possibly EMPTY or partial `files` while logging one line
|
|
6852
|
+
(`merge_conflict_files_probe_failed`) — there is NO discriminator for that shape on the wire.
|
|
6853
|
+
⇒ A `conflict` segment with an empty (or implausibly short) `files` must NOT be rendered as
|
|
6854
|
+
"no conflicting files"; render "the list may be incomplete" and point the user at `rejHead`
|
|
6855
|
+
and `workers[].branch`. A bit that separates "probe failed" from "there really are no more"
|
|
6856
|
+
would have to come from the server; this SDK does not mint a second judge for it.
|
|
6857
|
+
workers:
|
|
6858
|
+
type: array
|
|
6859
|
+
description: 'EVERY completed worker. `applied:false` = that worker''s patch is not in the tree (the conflicting one, or one queued behind it — apply is sequential and stops at the first failure). The branch comes from the provisioning registry, so it always has a value: the user checks it out.'
|
|
6860
|
+
items:
|
|
6861
|
+
type: object
|
|
6862
|
+
additionalProperties: true
|
|
6863
|
+
required: [workerId, branch, applied]
|
|
6864
|
+
properties:
|
|
6865
|
+
workerId: { type: string }
|
|
6866
|
+
branch: { type: string }
|
|
6867
|
+
applied: { type: boolean }
|
|
6868
|
+
rejHead: { type: string, description: 'Head of the .rej (through the server''s existing artifact redaction, <= 2 KiB) — FOR A HUMAN TO LOOK AT, not to parse.' }
|
|
6869
|
+
salvaged:
|
|
6870
|
+
type: array
|
|
6871
|
+
description: >
|
|
6872
|
+
Patches of the workers that did not merge (best-effort; present only when non-empty). 🔴 Since
|
|
6873
|
+
7.83.0 a CONFLICT terminal carries EVERY worker's patch (it used to carry only the unfinished ones):
|
|
6874
|
+
the integration sandbox is destroyed in a `finally`, so the branches are not guaranteed to still be
|
|
6875
|
+
on a remote — the patch itself is the deliverable, and the consumer should give the user a way to
|
|
6876
|
+
save it. ⚠️ `patch` is WORKER-AUTHORED CODE (untrusted text): render it, save it, never feed it back
|
|
6877
|
+
to a model.
|
|
6878
|
+
items:
|
|
6879
|
+
type: object
|
|
6880
|
+
additionalProperties: true
|
|
6881
|
+
required: [workerId, sessionId, patch]
|
|
6882
|
+
properties:
|
|
6883
|
+
workerId: { type: string }
|
|
6884
|
+
sessionId: { type: string }
|
|
6885
|
+
patch: { type: string }
|
|
6886
|
+
suspended:
|
|
6887
|
+
type: array
|
|
6888
|
+
description: 'Resume handles of suspended workers (C4) — their checkpoints and paused envs are intact.'
|
|
6889
|
+
items:
|
|
6890
|
+
type: object
|
|
6891
|
+
additionalProperties: true
|
|
6892
|
+
required: [workerId, sessionId]
|
|
6893
|
+
properties:
|
|
6894
|
+
workerId: { type: string }
|
|
6895
|
+
sessionId: { type: string }
|
|
6896
|
+
route: { type: string, description: 'single = a one-subtask plan; fanout = N.' }
|
|
6897
|
+
error: { type: string, description: 'Planning/provisioning failed before fan-out.' }
|
|
6898
|
+
replan:
|
|
6899
|
+
type: object
|
|
6900
|
+
additionalProperties: true
|
|
6901
|
+
description: 'Replan-lite: which trigger won arbitration and what was done. 🔴 The closed set LOST `merge-conflict` in 7.83.0 (S-113, hard breaking, zero aliases): a merge that does not apply is no longer replanned, it is handed to the user.'
|
|
6902
|
+
properties:
|
|
6903
|
+
trigger: { type: string, description: 'suspended | budget | verify-fail | worker-failed.' }
|
|
6904
|
+
redispatched: { type: array, items: { type: string } }
|
|
6905
|
+
collapsed: { type: string }
|
|
6906
|
+
|
|
6626
6907
|
ApprovalDecision:
|
|
6627
6908
|
type: object
|
|
6628
6909
|
description: HITL decision (`POST /v1/approvals/:sessionId/decide`).
|
|
@@ -6634,12 +6915,23 @@ components:
|
|
|
6634
6915
|
AskUserQuestion answer — TRUE WIRE SHAPE (worker-validated, 400 otherwise; found in prod
|
|
6635
6916
|
integration, service-pinned): {answers:[{header, selected:string[], note?}]} where header matches
|
|
6636
6917
|
the question's header. Omit for a plain F4 approve/deny.
|
|
6918
|
+
🔴 CLOSED SHAPE (server >= 7.87.0 / S-452): an unrecognised key ANYWHERE in this object (including
|
|
6919
|
+
inside `answers[]`) is REFUSED — 400 `request.field_invalid`, naming the KEY PATHS in the message
|
|
6920
|
+
and in the `unknownKeys` machine table. Refused rather than dropped, because a misspelled
|
|
6921
|
+
`selected` would silently drop part of the operator's own typed answer with a 200 on top (#157
|
|
6922
|
+
family). Accepted: `answers`, `answers[].header`, `answers[].selected`, `answers[].note`.
|
|
6923
|
+
⚠️ THE TWO LEGS DIFFER IN STRICTNESS and the live one is not the contract: on this DURABLE leg
|
|
6924
|
+
`answers[]` must be non-empty and each `selected` non-empty (an empty array is a 400), while the
|
|
6925
|
+
LIVE leg (`POST /v1/questions/{id}/respond`) only checks that it is an array (an empty one has a
|
|
6926
|
+
headless-default meaning there).
|
|
6637
6927
|
type: object
|
|
6928
|
+
additionalProperties: false
|
|
6638
6929
|
properties:
|
|
6639
6930
|
answers:
|
|
6640
6931
|
type: array
|
|
6641
6932
|
items:
|
|
6642
6933
|
type: object
|
|
6934
|
+
additionalProperties: false
|
|
6643
6935
|
required: [header, selected]
|
|
6644
6936
|
properties:
|
|
6645
6937
|
header: { type: string }
|
|
@@ -7287,6 +7579,66 @@ components:
|
|
|
7287
7579
|
`WriteProtectionCapability`). `null` = this process cannot say; an ABSENT key means an OLDER worker,
|
|
7288
7580
|
and "no key" is NOT "no table" — an absent seat on the engine side is precisely the DEFAULT TABLE
|
|
7289
7581
|
BEING IN PLACE.
|
|
7582
|
+
webSearch:
|
|
7583
|
+
type: object
|
|
7584
|
+
additionalProperties: true
|
|
7585
|
+
description: >
|
|
7586
|
+
server >= 7.82.1 (S-382; mint `routes/capabilities.ts:236` =
|
|
7587
|
+
`projectWebSearchCapability(deps.webSearchProvider)`) — THIS DEPLOYMENT'S DEFAULT WEBSEARCH BACKEND.
|
|
7588
|
+
The closed set is TYPED FROM the server's `WEB_SEARCH_PROVIDERS` tuple (`plugins/web-search.ts:70`,
|
|
7589
|
+
the repo's single provider word table) plus `"none"`; the predicate is the very
|
|
7590
|
+
`webSearchConfigFromEnv()` product boot USED TO BUILD the backend, so "the bit says brave while
|
|
7591
|
+
tavily is wired" is structurally impossible.
|
|
7592
|
+
🔴 `backend:"none"` AND AN ABSENT KEY ARE OPPOSITE DISPOSITIONS — never fold them: `"none"` = this
|
|
7593
|
+
box has NO search backend (render "unavailable"); ABSENT = an older worker (< 7.82.1) ⇒ render
|
|
7594
|
+
"cannot tell". Reading absence as `"none"` renders a correctly-configured older deployment as
|
|
7595
|
+
having no search.
|
|
7596
|
+
🔴 NO INTERNALS: the endpoint (a SearXNG instance URL), the API key and the quota never ride this
|
|
7597
|
+
wire, and the per-request `settings.webSearch` override is deliberately NOT echoed — this bit
|
|
7598
|
+
advertises the DEPLOYMENT DEFAULT and must not drift with the caller.
|
|
7599
|
+
required: [backend]
|
|
7600
|
+
properties:
|
|
7601
|
+
backend: { type: string, description: 'brave | tavily | searxng | none — closed set typed from the server''s provider tuple; unknown words are a newer server, not an error.' }
|
|
7602
|
+
mcpReconnect:
|
|
7603
|
+
type: boolean
|
|
7604
|
+
description: >
|
|
7605
|
+
server >= 7.85.0 (S-381 / core 7.22.0 #857; mint `routes/capabilities.ts:264` =
|
|
7606
|
+
`Boolean(deps.runStore) && Boolean(deps.sessionStorage?.ownerOf)`) — presence of
|
|
7607
|
+
`POST /v1/sessions/{sessionId}/mcp/reconnect`. FACILITY DEPENDENCY, two conjuncts: a durable run
|
|
7608
|
+
ledger (the verb reaches the live stream through the run row) AND the session-ownership face (the
|
|
7609
|
+
inbound owner gate is fail-closed; without it the whole door is 501). Both 501s derive from the SAME
|
|
7610
|
+
deps as this bit, so "says yes but 501s" is structurally impossible.
|
|
7611
|
+
🔴 True only promises the ENDPOINT IS THERE: not that there is a live run right now (otherwise 409
|
|
7612
|
+
`steering.not_running`), and not that this run declared any MCP server at all (undeclared ⇒ HTTP 200
|
|
7613
|
+
with `outcome:"unsupported"`, which is not an error). ABSENT = an older worker ⇒ probe by 404/405,
|
|
7614
|
+
do not render a dead affordance.
|
|
7615
|
+
executionLane:
|
|
7616
|
+
type: object
|
|
7617
|
+
additionalProperties: true
|
|
7618
|
+
description: >
|
|
7619
|
+
server >= 7.86.0 (S-426; mint `routes/capabilities.ts:602` =
|
|
7620
|
+
`projectExecutionLaneCapability(deps.config)`) — THE EXECUTION LANE, SELF-REPORTED. Before it, "do
|
|
7621
|
+
this engine's tools run on THIS machine" could only be guessed from a three-way circumstantial
|
|
7622
|
+
conjunction (shell spawned it AND no remote URL AND `REMOTE_EXEC` in {unset, host}) — none of which
|
|
7623
|
+
the engine said, and which drifts the moment the shell talks to a remote engine (it reads ITS OWN
|
|
7624
|
+
box's env).
|
|
7625
|
+
`provider` is typed from the server's `RemoteExecProvider` (`execution-lane-caps.ts:50`, derived
|
|
7626
|
+
from the `ServiceConfigFlat["remoteExec"]` discriminated union); `REMOTE_EXEC` UNSET ⇒ `"host"` is
|
|
7627
|
+
CONTRACT, not a fallback. Honest cost: the server internally has a finer word (`in-process` — that
|
|
7628
|
+
lane mounts NO hands at all) which this bit folds into `host`, so a consumer cannot tell "hands on
|
|
7629
|
+
this box" from "no hands at all"; that is a DIFFERENT AXIS, not a reason to re-point the lane word.
|
|
7630
|
+
`toolsOnThisHost` is owned by `task-cwd.ts`'s `toolsRunOnThisHost` — the SAME FUNCTION that decides
|
|
7631
|
+
whether a self-scanned skill gets a `baseDir`.
|
|
7632
|
+
🔴 THE IMPLICATION IS ONE-WAY: `toolsOnThisHost:false` ⇒ `skills[].baseDir` is never sent (that is
|
|
7633
|
+
the promise); the converse does NOT hold — `true` is only necessary (a flat `skills/<name>.md` has
|
|
7634
|
+
no directory of its own; plugin-sourced skills go through a loader that never passes the switch),
|
|
7635
|
+
so under `true` read `baseDir` as PRESENT-IFF.
|
|
7636
|
+
🔴 NO INTERNALS: k8s apiUrl / ssh host / adb serial / image / credentials / mountPath never ride here.
|
|
7637
|
+
An ABSENT key must NOT be read as either side — that is exactly the state this bit was minted to end.
|
|
7638
|
+
required: [provider, toolsOnThisHost]
|
|
7639
|
+
properties:
|
|
7640
|
+
provider: { type: string, description: 'host | e2b | k8s | ssh | adb | local-docker | device — typed from the server''s RemoteExecProvider; `REMOTE_EXEC` unset ⇒ `host`.' }
|
|
7641
|
+
toolsOnThisHost: { type: boolean, description: 'The tools run on the process that answered this request (server-absolute paths mean something to the model''s tools).' }
|
|
7290
7642
|
WriteProtectionCapability:
|
|
7291
7643
|
type: object
|
|
7292
7644
|
additionalProperties: false
|
|
@@ -7413,12 +7765,19 @@ components:
|
|
|
7413
7765
|
|
|
7414
7766
|
SkillSpec:
|
|
7415
7767
|
type: object
|
|
7416
|
-
description:
|
|
7768
|
+
description: >-
|
|
7769
|
+
A per-request skill (passed as an object; core-native TaskSpec.skills shape).
|
|
7770
|
+
🔴 server >= 7.78.1: `name` / `description` carry NO length cap any more (the engine's listing renderer
|
|
7771
|
+
trims descriptions and falls back to name-only within its own byte budget — it never REFUSES a skill for
|
|
7772
|
+
being long). `content` keeps the one real gate: 1 MiB, the twin of the engine's load gate (over it the
|
|
7773
|
+
engine refuses the skill outright at load time, so the HTTP face answers first where the caller can read it).
|
|
7774
|
+
The bound is 1 048 576 **UTF-16 code units** (`MAX_SKILL_CONTENT_CHARS`); see the STRING-LENGTH UNIT
|
|
7775
|
+
convention in `info.description` for why this `maxLength` states that number verbatim.
|
|
7417
7776
|
required: [name, description, content]
|
|
7418
7777
|
properties:
|
|
7419
|
-
name: { type: string,
|
|
7420
|
-
description: { type: string
|
|
7421
|
-
content: { type: string, maxLength:
|
|
7778
|
+
name: { type: string, minLength: 1 }
|
|
7779
|
+
description: { type: string }
|
|
7780
|
+
content: { type: string, minLength: 1, maxLength: 1048576 }
|
|
7422
7781
|
|
|
7423
7782
|
McpServerSpec:
|
|
7424
7783
|
type: object
|
|
@@ -7623,16 +7982,24 @@ components:
|
|
|
7623
7982
|
MetricsSummaryOps:
|
|
7624
7983
|
type: object
|
|
7625
7984
|
description: >
|
|
7626
|
-
200 body of GET /metrics/summary — `{ model, ...MetricsSummary }` (server
|
|
7627
|
-
observability/metrics.ts `MetricsSummary`
|
|
7628
|
-
|
|
7629
|
-
|
|
7985
|
+
200 body of GET /metrics/summary — `{ model, ...MetricsSummary }` (server `boot/stage-10-http-server`
|
|
7986
|
+
+ `observability/metrics.ts` `MetricsSummary` / `summarize()`; every key unconditional).
|
|
7987
|
+
🔴 OPEN as of sdk 9.7.0, and that is a RULING with a receipt: this schema was closed at the 2026-07-30
|
|
7988
|
+
census and by 7.87.0 the producer had moved THREE keys ahead of it (`limitCeilingAfterAnswer` since
|
|
7989
|
+
#677, `httpErrorsByClass` / `httpErrorsByRoute` since S-451) — i.e. a REAL summary body from a current
|
|
7990
|
+
server failed this schema outright, which is the same disease `LivePendingRow` shipped with. A
|
|
7991
|
+
RESPONSE PROJECTION is therefore declared key-by-key AND left open (the producer ships independently
|
|
7992
|
+
and adds keys additively between SDK releases); REQUEST bodies stay closed, because there the enforcer
|
|
7993
|
+
is the server's own strict gate.
|
|
7994
|
+
⚠️ The SDK DOES wrap this face (`client.metrics.summary()` → `MetricsSummaryOps`, since the
|
|
7995
|
+
2026-07-17 coverage batch). The old `x-sdk: none` key on the path said otherwise and was simply stale;
|
|
7996
|
+
it is gone. What remains true is the CREDENTIAL: a `metricsToken` (or a full authToken), not a principal.
|
|
7630
7997
|
required:
|
|
7631
7998
|
[model, runsActive, tasks, tokensTotal, costUsd, costUsdByModel, costUnknown, unpricedCalls,
|
|
7632
7999
|
taskDurationAvgSec, brainFirstTokenAvgMs, brainCallAvgMs, cacheHitRateAvg, promptCacheLowHit,
|
|
7633
8000
|
rateLimited, costQuotaRejected, budgetExceeded, degraded, degenerate, memoryConsolidationOps,
|
|
7634
8001
|
planCacheRecurrence, cascade, verifications, councilRuns, toolErrors]
|
|
7635
|
-
additionalProperties:
|
|
8002
|
+
additionalProperties: true
|
|
7636
8003
|
properties:
|
|
7637
8004
|
model: { type: string }
|
|
7638
8005
|
runsActive: { type: integer }
|
|
@@ -7667,6 +8034,33 @@ components:
|
|
|
7667
8034
|
verifications: { type: object, additionalProperties: { type: integer } }
|
|
7668
8035
|
councilRuns: { type: object, additionalProperties: { type: integer } }
|
|
7669
8036
|
toolErrors: { type: object, additionalProperties: { type: integer } }
|
|
8037
|
+
limitCeilingAfterAnswer:
|
|
8038
|
+
type: object
|
|
8039
|
+
additionalProperties: { type: integer }
|
|
8040
|
+
description: >-
|
|
8041
|
+
server #677 — answer-time ceiling hits (walltime/turns) by origin. ⚠️ A DIFFERENT DENOMINATOR from
|
|
8042
|
+
`budgetExceeded`: this one is minted on the deps-level notice seat and covers EVERY engine run
|
|
8043
|
+
(background children, delegated nested legs, the leader lane), while `budgetExceeded` is only
|
|
8044
|
+
incremented by the HTTP submit leg's `finalizeTaskResult`. Adding the two is only meaningful inside
|
|
8045
|
+
that narrower total.
|
|
8046
|
+
httpErrorsByClass:
|
|
8047
|
+
type: object
|
|
8048
|
+
additionalProperties: { type: integer }
|
|
8049
|
+
description: >-
|
|
8050
|
+
S-451 (server >= 7.87.0) — HTTP FAILURES in the window, aggregated by status CLASS. The keys are
|
|
8051
|
+
only `"4xx"` / `"5xx"` (2xx/3xx never enter). ⚠️ `"4xx"` INCLUDES `499` (the server's label for
|
|
8052
|
+
"client disconnected before the response finished", which is normal on SSE long-connections) — to
|
|
8053
|
+
look at a real storm, read `httpErrorsByRoute` alongside it. An EMPTY OBJECT means zero failures in
|
|
8054
|
+
the window (the key is always present; absence is NOT how zero is spelled). The whole key absent =
|
|
8055
|
+
an older server.
|
|
8056
|
+
httpErrorsByRoute:
|
|
8057
|
+
type: object
|
|
8058
|
+
additionalProperties: { type: integer }
|
|
8059
|
+
description: >-
|
|
8060
|
+
S-451 (server >= 7.87.0) — the TOP 10 route labels by failures (4xx+5xx combined), descending. The
|
|
8061
|
+
label is the low-cardinality form (ids folded to `:id`; anything off the route table lands on
|
|
8062
|
+
`"other"`). This is the "where did those 180 404s actually land" cell. Empty object = zero failures;
|
|
8063
|
+
key absent = an older server.
|
|
7670
8064
|
|
|
7671
8065
|
ModelInfo:
|
|
7672
8066
|
type: object
|
|
@@ -8480,7 +8874,18 @@ components:
|
|
|
8480
8874
|
elapsedMs: { type: integer }
|
|
8481
8875
|
tokens: { type: integer }
|
|
8482
8876
|
# tokenDir REMOVED (engine ruling 2026-06-28): dead field, 0 producer / 0 consumer — spec caught up 2026-07-26.
|
|
8483
|
-
queuedCount:
|
|
8877
|
+
queuedCount:
|
|
8878
|
+
type: integer
|
|
8879
|
+
description: >-
|
|
8880
|
+
🔴 PHANTOM KEY — NO server version ever emits it (verified against the published 7.87.0 bytes:
|
|
8881
|
+
the row type declares it and the fleet route's per-key table forwards it, but NO producer writes it
|
|
8882
|
+
— not `publishTask`'s call sites, not core's bg-sink tick, not the durable reconcile leg — and the
|
|
8883
|
+
wire contract does not mention it either). The server deleted it as a DEAD KEY (S-459, zero
|
|
8884
|
+
behavioural impact since `!== undefined` was always false). It stays declared here only because
|
|
8885
|
+
removing an exported field is BREAKING = major-only; it is registered with its deletion condition
|
|
8886
|
+
in `test/_keyset-divergence.ts` (`FLEET_TASK_ROW_PHANTOM_KEYS`) and goes with the next major.
|
|
8887
|
+
DO NOT read it as "queue depth is already reported" — a permanently absent optional key is a FALSE
|
|
8888
|
+
LEAD. Real queue depth needs a NEW key WITH a producer, not this one resurrected.
|
|
8484
8889
|
awaitingPlanApproval: { type: boolean }
|
|
8485
8890
|
currentAction: { type: string, description: "The child's most recent tool intent as one human-readable line (UNTRUSTED argument head, redacted server-side)." }
|
|
8486
8891
|
currentTool:
|
|
@@ -8835,9 +9240,21 @@ components:
|
|
|
8835
9240
|
description: >-
|
|
8836
9241
|
A live (in-stream) pending ask inside its streamApproval window (#315, server ≥7.43). Decide it via
|
|
8837
9242
|
POST /v1/tool-approvals/{approvalId}/respond — NOT the /decide route (that is the durable rows' door).
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
9243
|
+
Optional flags are only-if-true (absent is NEVER encoded as false).
|
|
9244
|
+
🔴 THE "tool input/args are NOT listed" NARROWING IS GONE (server >= 7.87.0 / S-454 P1). The old reason
|
|
9245
|
+
was "details ride the stream frame" — but a SUSPENDED ask never has a stream frame (at mint time it had
|
|
9246
|
+
no delivery target at all, so it suspends for a 1h window), and for it that reason is structurally
|
|
9247
|
+
false: without the card body on this row a consumer can list the ask and still not render it. The row
|
|
9248
|
+
therefore carries `frame` — the very card frame that ask WOULD have emitted — for EVERY live row (the
|
|
9249
|
+
rule set does not fork on `suspended`: an ask that does have a live stream carrying one extra copy of
|
|
9250
|
+
its own card is harmless).
|
|
9251
|
+
🔴 THIS SCHEMA IS OPEN (`additionalProperties: true`) as of sdk 9.7.0, and that is a RULING, not a slip:
|
|
9252
|
+
it was closed, and when 7.87.0 minted `frame` every strict consumer stopped accepting a perfectly legal
|
|
9253
|
+
row — the whole `livePending` array became unreadable rather than "one field short". A producer that
|
|
9254
|
+
ships independently of this SDK adds keys additively between SDK releases, so a RESPONSE projection is
|
|
9255
|
+
declared key-by-key AND left open; the REQUEST bodies stay closed (there the enforcer is the server's
|
|
9256
|
+
own strict gate, so closing is description, not guesswork).
|
|
9257
|
+
additionalProperties: true
|
|
8841
9258
|
required: [approvalId, toolName, ts, expiresAtMs]
|
|
8842
9259
|
properties:
|
|
8843
9260
|
approvalId: { type: string, description: 'The SAME id the respond endpoint takes (wire uuidv7).' }
|
|
@@ -8849,6 +9266,20 @@ components:
|
|
|
8849
9266
|
governanceForced: { type: boolean, enum: [true] }
|
|
8850
9267
|
fromSubagent: { type: boolean, enum: [true], description: 'The ask originates from a delegated subagent.' }
|
|
8851
9268
|
originTaskId: { type: string, description: 'S-52 C2 (server ≥7.52, A-075.87): the delegated child taskId (= the child''s core runSourceTaskId, uuid) — correlates this live ask to the child run row. Minted by the SAME conditional spread as `fromSubagent`: the two keys are always both present or both absent (三键恒等亲证在案).' }
|
|
9269
|
+
contentKind: { type: string, description: 'S-62 (server >= 7.6x): content-question kind — `content_ask` when the gated tool is AskUserQuestion (the SAME word as the two durable read faces and core''s summarize face). Absent = an ordinary tool ask. Display/triage only; it NEVER takes part in decision routing.' }
|
|
9270
|
+
frame:
|
|
9271
|
+
allOf: [{ $ref: '#/components/schemas/ToolApprovalFrame' }]
|
|
9272
|
+
description: >-
|
|
9273
|
+
server >= 7.87.0 (S-454 P1; cli L-397) — THE CARD FRAME THIS ASK WOULD HAVE EMITTED. It is the SAME
|
|
9274
|
+
OBJECT as the server's `PendingApproval.frame` (the one construction site in `askBroadcast`, already
|
|
9275
|
+
redacted / byte-capped / window-keys filled), shared by the suspended and the reachable path — so
|
|
9276
|
+
this is NOT a second card projection computed for the list, and there is no second redaction surface.
|
|
9277
|
+
🔴 INVARIANT: `frame.approvalId === row.approvalId` (verified at 7.87.0's merge review). When one ask
|
|
9278
|
+
was registered as pending more than once and the FIRST registration was cancelled, the frame's id
|
|
9279
|
+
used to point at the dead registration (responding by it 404s); the server now hands out a shallow
|
|
9280
|
+
copy with the id aligned to THIS row whenever the two differ. Always decide by the row's id.
|
|
9281
|
+
🔴 ABSENT = an older server (<= 7.86.0); from 7.87.0 it is ALWAYS present (a required slot on the
|
|
9282
|
+
entry) and is never minted as `null`.
|
|
8852
9283
|
|
|
8853
9284
|
ApprovalStaleCurrentPending:
|
|
8854
9285
|
type: object
|
|
@@ -9454,12 +9885,38 @@ components:
|
|
|
9454
9885
|
description: >
|
|
9455
9886
|
ASSISTANT-WIRE-CONTRACT §4c — `POST /v1/assistant/tasks/:id/plan_review` body (3-state). `editedPlan` is
|
|
9456
9887
|
REQUIRED iff `decision==="edit"` (FORBIDDEN otherwise → 400).
|
|
9888
|
+
🔴 THE BODY KEY SET IS CLOSED (server >= 7.86.0 / S-438): an unrecognised top-level key is refused with
|
|
9889
|
+
400 `request.body_shape` plus the `unknownKeys` / `unsupportedKeys` machine tables — refused rather
|
|
9890
|
+
than dropped, because a misspelled `permissionModeAfter` would otherwise silently leave the task
|
|
9891
|
+
read-only after its plan was approved.
|
|
9457
9892
|
required: [decision]
|
|
9458
9893
|
additionalProperties: false
|
|
9459
9894
|
properties:
|
|
9460
9895
|
decision: { type: string, enum: [approve, edit, reject] }
|
|
9461
9896
|
editedPlan: { type: string, description: 'the operator''s revised plan — required iff decision==="edit".' }
|
|
9462
9897
|
reason: { type: string }
|
|
9898
|
+
permissionModeAfter:
|
|
9899
|
+
type: string
|
|
9900
|
+
enum: [default, acceptEdits]
|
|
9901
|
+
description: >
|
|
9902
|
+
server >= 7.86.0 (S-433, P1; mints `routes/approvals-assistant.ts:1021/1024/1148`) — WHICH
|
|
9903
|
+
PERMISSION MODE THIS TASK CONTINUES IN once the plan is approved. Before this key, approving a plan
|
|
9904
|
+
was a BLANK CHEQUE: the plan was approved while the read-only clamp stayed on and every write kept
|
|
9905
|
+
asking. `acceptEdits` = writes stop asking; `default` = keep asking (AND is the folded value when
|
|
9906
|
+
the key is absent, so an older shell executes plans unchanged).
|
|
9907
|
+
🔴 THREE 400s, each with its own code: a value outside the two-word closed set ⇒
|
|
9908
|
+
`request.field_invalid` (ZERO widening arm — this bit decides whether the model has hands after
|
|
9909
|
+
approval, so folding a misspelling into either mode is a silent downgrade on the security axis);
|
|
9910
|
+
sending it with `edit`/`reject` ⇒ `request.field_conflict` (core: those two send the plan back to be
|
|
9911
|
+
redone and the read-only clamp stays — dropping it there would let the caller believe the mode was
|
|
9912
|
+
settled); sending it for a task that was NOT submitted with `permissionMode:"plan"` ⇒
|
|
9913
|
+
`request.field_conflict` (approving the plan lifts no read-only restriction there).
|
|
9914
|
+
⚠️ DIRECT-DOOR deployments: this key rides the HMAC envelope's TRAILING EXTENSION OBJECT (server
|
|
9915
|
+
7.87.0: `{answer?, permissionModeAfter?}`, present keys in lexicographic order; 7.86.0's bare
|
|
9916
|
+
trailing slot is DELETED with zero double-read) ⇒ a signer must sign the new shape, and mixed
|
|
9917
|
+
versions are 401 in both directions.
|
|
9918
|
+
⚠️ The key name is final: `permissionModeAfter` (the build brief once said `nextPermissionMode`,
|
|
9919
|
+
which never shipped — zero aliases).
|
|
9463
9920
|
|
|
9464
9921
|
SessionRecord:
|
|
9465
9922
|
type: object
|
|
@@ -9734,11 +10191,18 @@ components:
|
|
|
9734
10191
|
description: >
|
|
9735
10192
|
`GET /v1/sessions/{sessionId}/memory-status` 200 body (server >=7.53, S-53 seam①): `sessionId` + the
|
|
9736
10193
|
five `SessionMemoryStatus` keys copied ONE BY ONE when present (the server forbids spreading and forbids
|
|
9737
|
-
defaults — the five conditional copies ARE the closed set)
|
|
10194
|
+
defaults — the five conditional copies ARE the closed set), plus the DEPLOYMENT bit
|
|
10195
|
+
`autoConsolidationArmed` (server >= 7.85.0, always present). Per-key absence meaning = exactly
|
|
9738
10196
|
`SessionMemoryStatus` (a healthy zero-history session omits `optOutSource` and `lastCaptureAt`). Keys
|
|
9739
|
-
are mirrored locally rather than via allOf so the
|
|
9740
|
-
|
|
9741
|
-
|
|
10197
|
+
are mirrored locally rather than via allOf so the schema stays self-describing (spec rule: a closed
|
|
10198
|
+
schema plus allOf must mirror every key).
|
|
10199
|
+
🔴 OPEN as of sdk 9.7.0 — same ruling, same receipt as `MetricsSummaryOps` / `LivePendingRow`: this was
|
|
10200
|
+
closed at S-53 and server 7.85.0 then added a sixth key, so a REAL body from a current server failed the
|
|
10201
|
+
schema. A response PROJECTION is declared key-by-key AND left open (the producer ships independently and
|
|
10202
|
+
adds keys additively between SDK releases); request bodies stay closed.
|
|
10203
|
+
⚠️ SCOPE OF THAT RULING IN THIS BATCH: only the schemas this batch had to GROW were opened (the growth
|
|
10204
|
+
itself is the proof). Re-judging every other closed response schema is a separate car.
|
|
10205
|
+
additionalProperties: true
|
|
9742
10206
|
required: [sessionId]
|
|
9743
10207
|
properties:
|
|
9744
10208
|
sessionId: { type: string, description: 'The session the status is about (echo of the path segment, percent-decoded).' }
|
|
@@ -9747,6 +10211,87 @@ components:
|
|
|
9747
10211
|
foldedCount: { type: integer, description: 'See SessionMemoryStatus.foldedCount.' }
|
|
9748
10212
|
optOutSource: { type: string, enum: [record, fault], description: 'See SessionMemoryStatus.optOutSource — absent = no opt-out record (healthy), never a fault by itself.' }
|
|
9749
10213
|
lastCaptureAt: { type: integer, description: 'See SessionMemoryStatus.lastCaptureAt (ms epoch) — absent on a session with no committed contribution (healthy) as well as on an unreadable ledger.' }
|
|
10214
|
+
autoConsolidationArmed:
|
|
10215
|
+
type: boolean
|
|
10216
|
+
description: >-
|
|
10217
|
+
server >= 7.85.0 (S-403 / core 7.22.0 #863 W1; mint `routes/sessions.ts:744` =
|
|
10218
|
+
`deps.memoryAutoConsolidationArmed`, computed ONCE at boot through core's single reader
|
|
10219
|
+
`autoRunOnRecommendationEffective`) — IS AUTOMATIC MEMORY CONSOLIDATION ARMED ON THIS DEPLOYMENT.
|
|
10220
|
+
🔴 NOT one of the five core-derived session keys: those are per-SESSION derivations whose absence
|
|
10221
|
+
is meaningful per key; this one is a DEPLOYMENT fact and is ALWAYS PRESENT on >= 7.85.0. Absent =
|
|
10222
|
+
an older worker ⇒ "unknown"; NEVER fold it to `false` (that renders a box which is auto-egressing
|
|
10223
|
+
its whole memory store to the consolidation model as one that is not).
|
|
10224
|
+
🔴 `true` = a terminal harvest that crosses the threshold auto-runs one host consolidation (memory
|
|
10225
|
+
content auto-egresses to the configured model). A DIFFERENT QUESTION from `captureOptedOut` (does
|
|
10226
|
+
THIS SESSION enter long-term memory) — do not read them together.
|
|
10227
|
+
The same fact is readable by an operator as `wiring_manifest.autoConsolidation` (present-iff armed);
|
|
10228
|
+
one fact, one source — the tenant face has only this key.
|
|
10229
|
+
|
|
10230
|
+
McpReconnectServerStatus:
|
|
10231
|
+
type: object
|
|
10232
|
+
additionalProperties: true
|
|
10233
|
+
description: >
|
|
10234
|
+
The `status` segment of `POST /v1/sessions/{sessionId}/mcp/reconnect` (server >= 7.85.0 / S-381; mint
|
|
10235
|
+
`routes/mcp-reconnect.ts` `projectStatus`) — core's `McpServerStatus` projected BY THIS FACE.
|
|
10236
|
+
⚠️ The SAME core type is projected TWICE with DIFFERENT key sets and the two are deliberately NOT
|
|
10237
|
+
merged: the `/mcp` panel projects `{name, status, serverInfo?, toolNames?, error?}` only, while this
|
|
10238
|
+
face adds `errorCode` / `httpStatus` / `delivered` / `transportClosed`. Folding them would make the
|
|
10239
|
+
panel's consumers wait for four keys that never come.
|
|
10240
|
+
🔴 BRANCH ON THE MACHINE FIELDS, NEVER PARSE `reason`: `errorCode` is core's `McpFailureKind` word
|
|
10241
|
+
(owner is the engine — switch with a default arm, do not narrow), `httpStatus` 401 is a re-authorize
|
|
10242
|
+
door, `spawn_failed` is a missing binary.
|
|
10243
|
+
required: [name, status]
|
|
10244
|
+
properties:
|
|
10245
|
+
name: { type: string }
|
|
10246
|
+
status: { type: string, description: 'connected | failed (open set — the word table owner is the engine).' }
|
|
10247
|
+
errorCode: { type: string, description: 'core `McpFailureKind`. Open set; switch with a default arm.' }
|
|
10248
|
+
httpStatus: { type: integer, description: 'Only alongside errorCode `http_status` (401/403 = re-authorize; 5xx = that end is down). ABSENT rather than 0.' }
|
|
10249
|
+
delivered: { type: string, enum: [yes, no, unknown], description: 'Did the request reach that server — read together with errorCode.' }
|
|
10250
|
+
serverInfo:
|
|
10251
|
+
type: object
|
|
10252
|
+
properties:
|
|
10253
|
+
name: { type: string }
|
|
10254
|
+
version: { type: string }
|
|
10255
|
+
error: { type: string, description: 'Remote-authored text: server-redacted and truncated to 200 chars. HUMAN-READ ONLY, never parsed.' }
|
|
10256
|
+
transportClosed: { type: boolean, description: 'Whether that transport is already closed (the old connection is always closed BEFORE the new dial — see the "one transaction" note on the operation).' }
|
|
10257
|
+
|
|
10258
|
+
SessionMcpReconnectResult:
|
|
10259
|
+
type: object
|
|
10260
|
+
additionalProperties: true
|
|
10261
|
+
description: >
|
|
10262
|
+
200 body of `POST /v1/sessions/{sessionId}/mcp/reconnect` (server >= 7.85.0 / S-381 / core 7.22.0 #857;
|
|
10263
|
+
mint `routes/mcp-reconnect.ts` `projectResult`). Branch on `outcome` — all three states are HTTP 200.
|
|
10264
|
+
🔴 The `unsupported` arm (this run declared NO MCP) is an ALWAYS-FIVE-KEY body
|
|
10265
|
+
`{taskId, sessionId, server, outcome, reason}`: with no core result the server coins no
|
|
10266
|
+
`prefix`/`toolCount`/`added`/`removed`/`toolNames`, so reading `added` unconditionally is a bug.
|
|
10267
|
+
🔴 `toolNames` is PRESENT-IFF core supplied `tools` (not keyed off `outcome`) and is a SUPERSET of what
|
|
10268
|
+
the model finally sees (core filters again before mounting) — see the operation description.
|
|
10269
|
+
required: [taskId, sessionId, server, outcome]
|
|
10270
|
+
properties:
|
|
10271
|
+
taskId: { type: string, description: 'The live run this re-dial rode (this session''s active run) — follow it via GET /v1/runs/{taskId}/events.' }
|
|
10272
|
+
sessionId: { type: string, description: 'Echo of the path segment (percent-decoded).' }
|
|
10273
|
+
server: { type: string, description: 'The named server that was re-dialed.' }
|
|
10274
|
+
outcome: { type: string, description: 'accepted | refused | unsupported — the ONLY discriminator (all three are 200). `unsupported` is the SERVER''s word, not core''s.' }
|
|
10275
|
+
prefix: { type: string, description: 'The tool-name prefix domain of that server. Absent on the `unsupported` arm.' }
|
|
10276
|
+
toolCount: { type: integer, description: 'Tool count in that domain after the dial. Absent (never 0) on the `unsupported` arm.' }
|
|
10277
|
+
added: { type: array, items: { type: string } }
|
|
10278
|
+
removed: { type: array, items: { type: string } }
|
|
10279
|
+
toolNames:
|
|
10280
|
+
type: array
|
|
10281
|
+
items: { type: string }
|
|
10282
|
+
description: 'The roster THIS re-dial asserted for its own prefix domain (present-iff core supplied `tools`). A SUPERSET of what gets mounted; `[]` (failed dial) and ABSENT (never touched the connection) are different sentences.'
|
|
10283
|
+
reason: { type: string, description: 'Human sentence (server-redacted, 200-char bound). NEVER parse it — the machine face is `status`.' }
|
|
10284
|
+
status: { $ref: '#/components/schemas/McpReconnectServerStatus' }
|
|
10285
|
+
listingIncomplete:
|
|
10286
|
+
type: object
|
|
10287
|
+
additionalProperties: true
|
|
10288
|
+
description: 'The listing walk did not finish ⇒ do NOT treat `toolNames` as complete. `reason` is core''s word; `pages` is how many pages were walked.'
|
|
10289
|
+
required: [reason, pages]
|
|
10290
|
+
properties:
|
|
10291
|
+
reason: { type: string }
|
|
10292
|
+
pages: { type: integer }
|
|
10293
|
+
budgetMs: { type: integer }
|
|
10294
|
+
error: { type: string }
|
|
9750
10295
|
|
|
9751
10296
|
# ── 2c session-sync (P1d) — the cloud-as-a-SYNC-PEER schemas ──────────────────────────────────────────────
|
|
9752
10297
|
SyncEntry:
|
|
@@ -9994,6 +10539,12 @@ components:
|
|
|
9994
10539
|
The two 501 families are deliberately NOT interchangeable: `capability.*` means this deployment did
|
|
9995
10540
|
not wire that surface (change the deployment / hide the entry point), `feature.*` means the surface
|
|
9996
10541
|
exists but its switch is off (ask an admin to turn it on).
|
|
10542
|
+
🔴 `request.path_malformed` IS NOW THE WHOLE FAMILY'S ANSWER (server >= 7.86.0 / S-430): an id
|
|
10543
|
+
segment that is not valid percent-encoding answers 400 on EVERY id-bearing door. Twelve of those
|
|
10544
|
+
doors used to let the bare `decodeURIComponent` throw a `URIError` straight through the top-level
|
|
10545
|
+
catch and answered 500 `internal.error` — a caller-side mistake reported as a server fault, which
|
|
10546
|
+
also made it a retry-worthy-looking code. A consumer that branched on 500 for those paths must move
|
|
10547
|
+
the branch to the 400 (the fix is the caller's either way: re-encode the id).
|
|
9997
10548
|
error: { type: string }
|
|
9998
10549
|
errorMessage: { type: string }
|
|
9999
10550
|
activeTaskId:
|
|
@@ -11130,6 +11681,41 @@ components:
|
|
|
11130
11681
|
that wires none reports nothing at all. The server never invents `mounted: false` when it cannot
|
|
11131
11682
|
read the bit — the whole section goes absent instead (a minted false would read as "really not
|
|
11132
11683
|
mounted").
|
|
11684
|
+
writeProtection:
|
|
11685
|
+
allOf: [{ $ref: '#/components/schemas/WiringManifestWriteProtection' }]
|
|
11686
|
+
description: >
|
|
11687
|
+
core 7.20.1 (#853 / clay C-R55 丙), server >= 7.80.1 — TENANT-visible: which TARGET READING this
|
|
11688
|
+
leg's write-protection judge got. EFFECTIVE half only.
|
|
11689
|
+
🔴 ABSENCE IS A THIRD STATE (core present-iff a judge was compiled; a deployment with
|
|
11690
|
+
`writeProtectedPaths: []` omits the whole section) — NEVER fold it to `"spelling-only"`, which
|
|
11691
|
+
means "a judge IS there and only reads spellings".
|
|
11692
|
+
⚠️ Same name, different thing from `capabilities.writeProtection` / the diagnostics endpoint's
|
|
11693
|
+
`writeProtection`: those are THIS SERVER'S assembled NAME TABLE (a boot product); this is core's
|
|
11694
|
+
per-leg SELF-REPORT of the judge's reading.
|
|
11695
|
+
readDeny:
|
|
11696
|
+
allOf: [{ $ref: '#/components/schemas/WiringManifestReadDeny' }]
|
|
11697
|
+
description: >
|
|
11698
|
+
core 7.22.0 (#889 / C-R60), server >= 7.85.0 — OPERATOR face only (stripped on every tenant
|
|
11699
|
+
stream, and deliberately NOT carried by `GET /v1/diagnostics/wiring` either): which BUILT-IN
|
|
11700
|
+
sensitive-path read-deny TIERS materially activate rows on this deployment.
|
|
11701
|
+
🔴 PRESENT IFF at least one built-in row judges this leg. Absence = no built-in row judges it (a
|
|
11702
|
+
POSITIVE fact), NOT "no read is deny-judged" — a deployment's own `READ_DENY_PATTERNS` is
|
|
11703
|
+
deliberately never reported (pattern TEXT is deployment material, not an enum).
|
|
11704
|
+
🔴 AN EMPTY ARRAY IS NEVER EMITTED ("off" and "on to nothing" are one fact) ⇒ treat `[]` as a bad
|
|
11705
|
+
producer. The tier vocabulary is owned by core — do not narrow it on the consumer side.
|
|
11706
|
+
⚠️ Deployment semantics flipped in core 7.22.0: `READ_DENY_BUILTIN_TIERS` UNSET = OFF.
|
|
11707
|
+
autoConsolidation:
|
|
11708
|
+
allOf: [{ $ref: '#/components/schemas/WiringManifestAutoConsolidation' }]
|
|
11709
|
+
description: >
|
|
11710
|
+
core 7.21.0 (#761), server >= 7.82.0 — OPERATOR face only (stripped on every tenant stream): is
|
|
11711
|
+
automatic memory consolidation ARMED.
|
|
11712
|
+
🔴 PRESENT IFF armed, and the only present shape is `{ onRecommendation: true }` (core's construct
|
|
11713
|
+
gate refuses an armed-but-unrunnable state). ABSENT = NOT ARMED (a positive fact) — never read it
|
|
11714
|
+
as `false`, never as "unknown". Armed means a terminal harvest over the threshold auto-runs one
|
|
11715
|
+
host consolidation: memory content auto-egresses to the configured model, which is why it sits in
|
|
11716
|
+
the operator family beside `governance`.
|
|
11717
|
+
⚠️ The tenant-readable twin of the same fact is `GET /v1/sessions/{sessionId}/memory-status`'s
|
|
11718
|
+
`autoConsolidationArmed` (always present, a real boolean). One fact, one source, two faces.
|
|
11133
11719
|
governance:
|
|
11134
11720
|
type: object
|
|
11135
11721
|
additionalProperties: false
|
|
@@ -12140,6 +12726,41 @@ components:
|
|
|
12140
12726
|
frame and the inbox row; the `card_json` card deliberately does NOT carry it (it travels with `origin`,
|
|
12141
12727
|
which is not on the card). Absence = `origin` is not that word — NOT an assertion that the rule store
|
|
12142
12728
|
is healthy.
|
|
12729
|
+
readRootCandidate:
|
|
12730
|
+
type: object
|
|
12731
|
+
required: [dir, clearsThisAsk]
|
|
12732
|
+
additionalProperties: false
|
|
12733
|
+
properties:
|
|
12734
|
+
dir:
|
|
12735
|
+
type: string
|
|
12736
|
+
minLength: 1
|
|
12737
|
+
maxLength: 1024
|
|
12738
|
+
description: >-
|
|
12739
|
+
The ABSOLUTE, lexically-folded directory to add to this session's read roots, spelled by the read
|
|
12740
|
+
boundary itself — THE STRING SHOWN IS THE STRING TO ADD. A shell may render a shortened / `~` form,
|
|
12741
|
+
but must echo THIS string back verbatim. The server carries it whole or drops the whole key
|
|
12742
|
+
(a malformed shape, or a `dir` over 1024 UTF-16 code units ⇒ the key is ABSENT — never truncated,
|
|
12743
|
+
because a truncated directory is a DIFFERENT directory and adding it would not clear this ask;
|
|
12744
|
+
see the STRING-LENGTH UNIT convention in `info.description` for this `maxLength`'s counting unit).
|
|
12745
|
+
clearsThisAsk:
|
|
12746
|
+
type: boolean
|
|
12747
|
+
enum: [true]
|
|
12748
|
+
description: >-
|
|
12749
|
+
Shape, not value — the domain is the literal `true` only, so this seat cannot be quietly widened
|
|
12750
|
+
into a "might help" hint.
|
|
12751
|
+
description: >-
|
|
12752
|
+
server >= 7.78.1 (core 7.19.0), ADDITIVE, LIVE `tool_approval` FRAME ONLY — the clearing directory for
|
|
12753
|
+
an OUT-OF-ROOT READ ask: add `dir` to this session's read roots (the shell's existing `/add-dir`) and
|
|
12754
|
+
the same call stops asking.
|
|
12755
|
+
🔴 READ PRESENCE ONLY, NEVER ABSENCE. Absence is NOT "nothing can be done": it also covers sensitive-path
|
|
12756
|
+
deny rows (pattern-judged — no root changes them), commands the read boundary cannot parse (heredoc /
|
|
12757
|
+
newline / command substitution / an argument used as the program), unexpanded globs, recursive walks,
|
|
12758
|
+
and every ask NOT raised by the read boundary — every ask on server <= 7.78.0 has this shape too.
|
|
12759
|
+
⚠️ THIS FACE ONLY: `card_json`, the §2 inbox row, the `/v1/approvals` rich row and the park face carry
|
|
12760
|
+
NO twin of this seat in this version — a PARKED out-of-root read ask still renders that single card.
|
|
12761
|
+
⚠️ Known shapes (upstream-registered, so a shell must NOT render this option as a guarantee):
|
|
12762
|
+
`cd X && cat Y` yields X's PARENT directory; and on a deployment that normalizes declared read roots
|
|
12763
|
+
(e.g. `/tmp/x` stored as `/private/tmp/x`) the same call may ask once more after the root is added.
|
|
12143
12764
|
ApprovalRequestFrame:
|
|
12144
12765
|
type: object
|
|
12145
12766
|
description: >
|
|
@@ -14022,6 +14643,16 @@ components:
|
|
|
14022
14643
|
WiringManifest:
|
|
14023
14644
|
type: object
|
|
14024
14645
|
description: >
|
|
14646
|
+
🔴 ONE RULE FOR EVERY "EFFECTIVE HALF ONLY" KEY (sdk 9.7.0, after a codex adversarial-review [high]):
|
|
14647
|
+
this schema's only consumer today is `GET /v1/diagnostics/wiring`'s `static` half, and every key marked
|
|
14648
|
+
"effective half only" (`leg`, `ask.effective`, `configFingerprint`, `tools`, `hooks`, `lsp`,
|
|
14649
|
+
`modelGate`, `autoMode`, `writeProtection`, `readDeny`, `autoConsolidation`) is ALWAYS ABSENT on that
|
|
14650
|
+
wire ⇒ THEIR ABSENCE HERE CARRIES NO INFORMATION. The per-key absence readings ("absent = not armed",
|
|
14651
|
+
"absent = a third state", …) hold for the LIVE FRAME (`Event_wiring_manifest`) and must not be carried
|
|
14652
|
+
over to this response. For the facts behind those keys: a tenant reads
|
|
14653
|
+
`GET /v1/sessions/{sessionId}/memory-status` (`autoConsolidationArmed`); an operator subscribes to that
|
|
14654
|
+
leg's live stream (the governance segment and these three sections exist only on a projection that
|
|
14655
|
+
KNOWS the caller's identity).
|
|
14025
14656
|
core's assembly self-evidence manifest (core >=5.14.0 `WiringManifest`). TWO HALVES, ONE shape (a
|
|
14026
14657
|
consumer must line the two faces up, so they are not split into separate types) — but which keys
|
|
14027
14658
|
belong to which half is DETERMINATE, not "usually": `leg`, `ask.effective` and `configFingerprint`
|
|
@@ -14165,6 +14796,78 @@ components:
|
|
|
14165
14796
|
core 7.18.0 (#789), server >= 7.78.0 — the LSP seam. EFFECTIVE half only; present iff a manager
|
|
14166
14797
|
seat resolved. `mounted` and `lane` are INDEPENDENT facts — never fold them into one.
|
|
14167
14798
|
Full per-key contract: see Event_wiring_manifest.lsp.
|
|
14799
|
+
writeProtection:
|
|
14800
|
+
allOf: [{ $ref: '#/components/schemas/WiringManifestWriteProtection' }]
|
|
14801
|
+
description: >
|
|
14802
|
+
core 7.20.1 (#853), server >= 7.80.1 — TENANT-visible: which target reading this leg's
|
|
14803
|
+
write-protection judge got. EFFECTIVE HALF ONLY ⇒ ALWAYS ABSENT ON THIS ENDPOINT, and absence
|
|
14804
|
+
HERE carries no information (see this schema's own description for the one rule that governs
|
|
14805
|
+
every effective-half-only key). The three-state reading ("absence = no judge at all, never fold
|
|
14806
|
+
it to `spelling-only`") is the LIVE FRAME's reading — do not apply it to this response.
|
|
14807
|
+
Full per-key contract: see Event_wiring_manifest.writeProtection.
|
|
14808
|
+
readDeny:
|
|
14809
|
+
allOf: [{ $ref: '#/components/schemas/WiringManifestReadDeny' }]
|
|
14810
|
+
description: >
|
|
14811
|
+
core 7.22.0 (#889), server >= 7.85.0 — OPERATOR face only, effective half only, AND the server
|
|
14812
|
+
explicitly rules it off this endpoint ⇒ always absent here; absence HERE says nothing about the
|
|
14813
|
+
deployment's tiers. The present-iff / never-empty-array reading belongs to the operator LIVE
|
|
14814
|
+
projection. Full per-key contract: see Event_wiring_manifest.readDeny.
|
|
14815
|
+
autoConsolidation:
|
|
14816
|
+
allOf: [{ $ref: '#/components/schemas/WiringManifestAutoConsolidation' }]
|
|
14817
|
+
description: >
|
|
14818
|
+
core 7.21.0 (#761), server >= 7.82.0 — OPERATOR face only, EFFECTIVE HALF ONLY ⇒ ALWAYS ABSENT ON
|
|
14819
|
+
THIS ENDPOINT. 🔴 DO NOT read absence here as "not armed": that renders a deployment which IS
|
|
14820
|
+
auto-egressing its whole memory store as one that is not. The present-iff-armed reading belongs to
|
|
14821
|
+
the operator LIVE projection; for "is this deployment armed", read
|
|
14822
|
+
`GET /v1/sessions/{sessionId}/memory-status`'s `autoConsolidationArmed` (always present, a real
|
|
14823
|
+
boolean). Full per-key contract: see Event_wiring_manifest.autoConsolidation.
|
|
14824
|
+
WiringManifestWriteProtection:
|
|
14825
|
+
# sdk 9.7.0:与 `WiringManifestMcpEntry` / `WiringManifestLspSeam` 同一条裁定 —— 同一段形被 live 帧与
|
|
14826
|
+
# 静态诊断面两处引用,内联就是两份会各自漂的镜像,所以具名单源。
|
|
14827
|
+
type: object
|
|
14828
|
+
additionalProperties: false
|
|
14829
|
+
description: >
|
|
14830
|
+
The `wiring_manifest.writeProtection` section (core 7.20.1 #853 / clay C-R55 丙) — which TARGET
|
|
14831
|
+
READING this leg's write-protection judge got. `"target"` = the judge holds the execution environment
|
|
14832
|
+
and re-judges on the RESOLVED target when the spelling missed (a write that reaches a protected row
|
|
14833
|
+
through an alias or a symlink is cleared); `"spelling-only"` = no environment, the spelling IS the
|
|
14834
|
+
whole verdict. ABSENCE (the section itself) is a THIRD state — no judge was compiled at all.
|
|
14835
|
+
required: [targetView]
|
|
14836
|
+
properties:
|
|
14837
|
+
targetView: { type: string, description: "core's two-word closed set (`spelling-only` / `target`); read as open — the vocabulary owner is the engine." }
|
|
14838
|
+
|
|
14839
|
+
WiringManifestReadDeny:
|
|
14840
|
+
type: object
|
|
14841
|
+
additionalProperties: false
|
|
14842
|
+
description: >
|
|
14843
|
+
The `wiring_manifest.readDeny` section (core 7.22.0 #889 / C-R60) — the BUILT-IN sensitive-path
|
|
14844
|
+
read-deny tiers that MATERIALLY activate rows on this leg, in core's own tier order (NOT the tiers the
|
|
14845
|
+
operator typed: one whose every row the exclude list removed is not named). OPERATOR audience.
|
|
14846
|
+
required: [builtinTiers]
|
|
14847
|
+
properties:
|
|
14848
|
+
builtinTiers:
|
|
14849
|
+
type: array
|
|
14850
|
+
minItems: 1
|
|
14851
|
+
items: { type: string }
|
|
14852
|
+
description: >
|
|
14853
|
+
Tier names, core-owned vocabulary (today `credentials` / `shell-history` / `browser` / `wallet` /
|
|
14854
|
+
`agent-config`) — passed through verbatim, never narrowed here. AN EMPTY ARRAY IS NEVER EMITTED.
|
|
14855
|
+
|
|
14856
|
+
WiringManifestAutoConsolidation:
|
|
14857
|
+
type: object
|
|
14858
|
+
additionalProperties: false
|
|
14859
|
+
description: >
|
|
14860
|
+
The `wiring_manifest.autoConsolidation` section (core 7.21.0 #761) — PRESENT IFF automatic memory
|
|
14861
|
+
consolidation is EFFECTIVELY armed on this deployment. OPERATOR audience: armed means memory content
|
|
14862
|
+
auto-egresses to the configured consolidation model after a qualifying terminal harvest, and it is not
|
|
14863
|
+
observable to the caller at all (the run happens host-side, fire-and-forget, after the harvest).
|
|
14864
|
+
required: [onRecommendation]
|
|
14865
|
+
properties:
|
|
14866
|
+
onRecommendation:
|
|
14867
|
+
type: boolean
|
|
14868
|
+
enum: [true]
|
|
14869
|
+
description: "core's ONLY present shape — the construct gate refuses an armed-but-unrunnable state, so this is a SHAPE, not a value."
|
|
14870
|
+
|
|
14168
14871
|
WiringManifestHookEntry:
|
|
14169
14872
|
# sdk 9.5.0:与 `WiringManifestMcpEntry` 同一条裁定 —— 同一份行形被 live 帧与静态诊断面两处引用,
|
|
14170
14873
|
# 内联就是两份会各自漂的镜像,所以一开始就具名。
|