@sema-agent/client-core 0.20.0 → 0.21.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 +2 -2
- package/dist/adapt/arms.js +23 -9
- package/dist/adapt/turnFlags.d.ts +14 -0
- package/dist/adapt/turnFlags.js +16 -4
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +12 -1
- package/dist/adapter/downstream/eventToSdkMessage.js +82 -15
- package/dist/hitl/toolApprovalWire.d.ts +43 -2
- package/dist/hitl/toolApprovalWire.js +45 -1
- package/dist/retryStatus.d.ts +63 -4
- package/dist/retryStatus.js +69 -5
- package/dist/seatContract.d.ts +29 -2
- package/dist/seatContract.js +14 -0
- package/dist/skillsWireCaps.d.ts +31 -4
- package/dist/skillsWireCaps.js +23 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
23
23
|
|
|
24
24
|
## Scope
|
|
25
25
|
|
|
26
|
-
**Version:** 0.
|
|
26
|
+
**Version:** 0.21.0
|
|
27
27
|
|
|
28
28
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
29
29
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
|
@@ -53,7 +53,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
53
53
|
`SseIdleError`, `probeHealth`, `APIError` and `TaskStopConflictError` are imported as values in
|
|
54
54
|
five modules, and the browser bundle really bundles the SDK through (the portability guard would
|
|
55
55
|
exit 3 rather than quietly mark it external).
|
|
56
|
-
- The declared floor is `>=6.
|
|
56
|
+
- The declared floor is `>=6.9.0`, and it is *witnessed*: the guard checks that an actually
|
|
57
57
|
installed SDK at that line still exports every value-level symbol this package imports and still
|
|
58
58
|
declares `TaskStats.costMicroUsd` (the key `costOrNull` reads). A floor nobody ever ran is a
|
|
59
59
|
promise, not a contract.
|
package/dist/adapt/arms.js
CHANGED
|
@@ -643,15 +643,29 @@ const retryStatusArm = function* (m, { ctx, flags }) {
|
|
|
643
643
|
// #47 矩阵 #5:子代 lane 帧绝不骑主 spinner(无判别守卫的无条件 store 写=默认可疑)。
|
|
644
644
|
if (typeof m.parentToolCallId === 'string')
|
|
645
645
|
return;
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
|
|
646
|
+
// 🔴 六键全转(#3004 跟修批 2026-08-08):此前这里只挑 phase/detail/retryInSec 三键,attempt /
|
|
647
|
+
// maxRetries / retryInMs 被剥在本层 —— 而 server 两腿的 brainStatusEventData 是全发的,
|
|
648
|
+
// `RetryStatus` 上也早就留好了 attempt/maxRetries 两位。键集真源 =
|
|
649
|
+
// `BRAIN_STATUS_PAYLOAD_KEYS`(engine-vocab G2-c 对实装 core 对账)。
|
|
650
|
+
const status = mapBrainStatusToRetry({
|
|
651
|
+
phase: typeof m.phase === 'string' ? m.phase : '',
|
|
652
|
+
...(typeof m.detail === 'string' ? { detail: m.detail } : {}),
|
|
653
|
+
...(typeof m.retryInSec === 'number' ? { retryInSec: m.retryInSec } : {}),
|
|
654
|
+
...(typeof m.retryInMs === 'number' ? { retryInMs: m.retryInMs } : {}),
|
|
655
|
+
...(typeof m.attempt === 'number' ? { attempt: m.attempt } : {}),
|
|
656
|
+
...(typeof m.maxRetries === 'number' ? { maxRetries: m.maxRetries } : {}),
|
|
657
|
+
}, ctx.now());
|
|
658
|
+
// `status === null` = 引擎直报 `recovered`(重试成功)。这一拍是**清除腿之一** —— 但间接腿
|
|
659
|
+
// (TurnFlags 的「主 lane 内容帧到达即清」)可能已经清过了:已安装 core 在内容流跑完的 finally
|
|
660
|
+
// 里发 `recovered`,所以 `retrying → 内容 → recovered` 才是常态顺序。故用 `takeRetryOverlay()`
|
|
661
|
+
// 的**原子读落**决定要不要发:旗还在 ⇒ 发这一发并落旗;旗已落 ⇒ 沉默(没有覆盖层可摘)。
|
|
662
|
+
// 首版写成「无条件发 + clearRetryOverlay」,在常态顺序上双清(codex 复审命中)。
|
|
663
|
+
if (status === null) {
|
|
664
|
+
if (flags.takeRetryOverlay())
|
|
665
|
+
yield chrome({ kind: 'retry_status', laneProof: MAIN, status: null });
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
yield chrome({ kind: 'retry_status', laneProof: MAIN, status });
|
|
655
669
|
// ⟨帧序耦合 6/7⟩ 挂旗;下一个主 lane 内容帧到达时由 TurnFlags 的帧前置清掉(恢复即清)。
|
|
656
670
|
flags.noteRetryOverlay();
|
|
657
671
|
};
|
|
@@ -33,5 +33,19 @@ export interface TurnFlags {
|
|
|
33
33
|
emitResultEndIfNeeded(readOutputTokens: () => number | undefined): Generator<AdapterOutput>;
|
|
34
34
|
/** A15 `retry_status`:挂旗(下一帧主 lane 内容到达即清)。 */
|
|
35
35
|
noteRetryOverlay(): void;
|
|
36
|
+
/**
|
|
37
|
+
* A15 `retry_status` 的反向动作(#3004 跟修批 2026-08-08):**引擎直报恢复**(`recovered` 相)那一拍
|
|
38
|
+
* 调它 —— 返回「本次是否**仍需**发清除」并同时落旗(**原子**:读+落一步完成)。
|
|
39
|
+
*
|
|
40
|
+
* 🔴 为什么必须是「take」而不是「clear」(codex 复审命中的双清):两条清除腿共用同一个布尔位,
|
|
41
|
+
* 而它们的触发顺序两个方向都真实存在 ——
|
|
42
|
+
* · `retrying → recovered`:直报腿先到,旗还在 ⇒ 本次要发清除,发完落旗;间接腿之后看不到旗。
|
|
43
|
+
* · `retrying → 主lane内容 → recovered`:**间接腿先到**(已发过一次 null 并落了旗),直报腿这一拍
|
|
44
|
+
* 就**不该再发第二发** —— 已安装 core 在内容流跑完的 finally 里发 `recovered`,所以这个顺序
|
|
45
|
+
* 才是常态路径。首版写成无条件发 + `clearRetryOverlay()`,于是这条常态路径上双清。
|
|
46
|
+
* 返回 false ⇒ 本臂这一拍不 yield(没有覆盖层可摘,沉默才是诚实的);状态机仍只有本文件一处
|
|
47
|
+
* 持有那个布尔位。
|
|
48
|
+
*/
|
|
49
|
+
takeRetryOverlay(): boolean;
|
|
36
50
|
}
|
|
37
51
|
export declare function createTurnFlags(ctx: AdapterContext, turnStartAt: number): TurnFlags;
|
package/dist/adapt/turnFlags.js
CHANGED
|
@@ -4,9 +4,15 @@ export function createTurnFlags(ctx, turnStartAt) {
|
|
|
4
4
|
let endEmitted = false;
|
|
5
5
|
/**
|
|
6
6
|
* R1(clay 五报 07-31):retry 覆盖层的四个清除点全在 turn 边界,缺「重试成功→清」的转移 ——
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* 当时引擎重试成功那一刻什么都不发,所以恢复只能由「主 lane 又有内容在流」这个事实来证明。
|
|
8
|
+
* 挂旗后首个主 lane 内容帧即清,CC 同位 = 重试循环退出即 onRetryStatus(null)(pretty220 finally 清)。
|
|
9
|
+
*
|
|
10
|
+
* 🔴 现势更正(2026-08-08,#3004 跟修批):上面括号里原本写着「BrainStatusPhase 无恢复终态,候
|
|
11
|
+
* core [2163]」—— core 早已兑现,`BrainStatusPhase` 现有 `recovered` / `gave_up` 两个终态相。于是
|
|
12
|
+
* 本位这条**间接推断腿**不再是唯一证据:`retry_status` 臂拿到 `recovered` 时走 `takeRetryOverlay()`
|
|
13
|
+
* 直报清除。两条腿共用同一个布尔位、且都靠 take 的**原子读落**决定要不要发帧,所以无论到达顺序
|
|
14
|
+
* 如何(直报先 / 内容帧先),一次恢复合计**恰好一发** `status:null`(pure 门 R1b 段两个方向都钉)。
|
|
15
|
+
* 间接腿保留 —— 老引擎不发 `recovered`,它仍是那些部署上唯一的证据。
|
|
10
16
|
*/
|
|
11
17
|
let retryOverlayActive = false;
|
|
12
18
|
return {
|
|
@@ -20,7 +26,8 @@ export function createTurnFlags(ctx, turnStartAt) {
|
|
|
20
26
|
// stream_event 的子流标记在 event 里(thinking 臂同款判位),帧顶层没有。
|
|
21
27
|
typeof m.event?.parentToolCallId !== 'string') {
|
|
22
28
|
// 恢复即清:主 lane 内容帧到达 = 引擎那次重试已成功;子流帧(parentToolCallId)不算证据
|
|
23
|
-
// (主 brain 仍可能在退避等待,#47 矩阵 #5 同族判别)
|
|
29
|
+
// (主 brain 仍可能在退避等待,#47 矩阵 #5 同族判别)。落旗后 `takeRetryOverlay()` 会对随后
|
|
30
|
+
// 到达的 `recovered` 直报帧返回 false ⇒ 不补第二发(两条腿合计恰好一发)。
|
|
24
31
|
retryOverlayActive = false;
|
|
25
32
|
yield chrome({ kind: 'retry_status', laneProof: MAIN, status: null });
|
|
26
33
|
}
|
|
@@ -51,5 +58,10 @@ export function createTurnFlags(ctx, turnStartAt) {
|
|
|
51
58
|
noteRetryOverlay: () => {
|
|
52
59
|
retryOverlayActive = true;
|
|
53
60
|
},
|
|
61
|
+
takeRetryOverlay: () => {
|
|
62
|
+
const wasActive = retryOverlayActive;
|
|
63
|
+
retryOverlayActive = false;
|
|
64
|
+
return wasActive;
|
|
65
|
+
},
|
|
54
66
|
};
|
|
55
67
|
}
|
|
@@ -58,7 +58,18 @@ export type EventProjectionDropReason =
|
|
|
58
58
|
* ⚠️ 「筛完是空的」不算畸形(那是 `empty_payload`,见上;ADAPTER-F6)。 */
|
|
59
59
|
'malformed'
|
|
60
60
|
/** 类型面根本不认识这条臂(引擎比本包新)。 */
|
|
61
|
-
| 'unknown_arm'
|
|
61
|
+
| 'unknown_arm'
|
|
62
|
+
/**
|
|
63
|
+
* 类型面**认得**这条臂、它也带着用户可见的决策内容,但本包**今天没有任何消费口**
|
|
64
|
+
* (2026-08-08 codex 复审第 4/5 轮命中,首例 = `approval_request`)。
|
|
65
|
+
*
|
|
66
|
+
* 为什么不能并进 `not_in_slice`:那一档的语义是「本切片不投影,**因为别处接了/本来就没有可渲染物**」,
|
|
67
|
+
* 它走 `kind:'none'` —— 而 `none` 是**静默**的,只有 `dropped` 会经 `reportDroppedFrame` 走宿主
|
|
68
|
+
* sink + console 留痕。把一条「有内容、没人接」的帧塞进 `none`,等于把一个真实能力缺口做成静默
|
|
69
|
+
* fail-open(本仓 P0 共性病的同款形)。判据方向:宁可每次都吼一行,也不要缺口无声。
|
|
70
|
+
* 为什么也不能并进 `unknown_arm`:那是假话 —— 我们认得它,缺的是消费链,不是类型。
|
|
71
|
+
*/
|
|
72
|
+
| 'unsupported_arm';
|
|
62
73
|
/**
|
|
63
74
|
* 投影结果三态(REF-CC-058 / xlate-04,A1 禁哨兵值双义 + C6 降级必须可分辨)。
|
|
64
75
|
*
|
|
@@ -63,18 +63,21 @@ function assertNeverArm(_ev) {
|
|
|
63
63
|
* 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
|
|
64
64
|
*/
|
|
65
65
|
export function eventToSdkMessage(ev, ctx) {
|
|
66
|
-
// ── raw 预分派 —— `human_input`(core 5.14.0 design/171 / server 7.4.0 SSE,黑板 [3017]/[3020])──
|
|
67
|
-
// 🔴 为什么必须在 switch **之前**:这条臂**还没进** SDK 6.3.0 的 `AgentEvent` union
|
|
68
|
-
// (`dist/events.d.ts` 实扫:33 条臂里没有它),`case 'human_input'` 在 `ev.type` 上是编译错。
|
|
69
|
-
// 在 union 里的那天就把它搬进 switch —— 与 `workflow_complete` 当年那条 raw 预分派臂同样的
|
|
70
|
-
// 退役条件(那一条已于 sdk 6.2.0 兑现退役,先例在本文件 `case 'workflow_complete'` 头注)。
|
|
71
|
-
// 处置:server 侧对本帧的投影明写「**本帧不带正文**(core 的臂里就没有 text 字段),所以没有
|
|
72
|
-
// 内容脱敏面」。因此它是**生命周期账本帧**,不是转录物 —— 投成中性内部臂交给 chrome 面记账,
|
|
73
|
-
// 绝不铸 transcript 行(铸一行就是替引擎编一条它没说过的话)。
|
|
74
|
-
if (ev.type === 'human_input') {
|
|
75
|
-
return humanInputProjection(ev, ctx);
|
|
76
|
-
}
|
|
77
66
|
switch (ev.type) {
|
|
67
|
+
// ── `human_input`(core 5.14.0 design/171 / server 7.4.0 SSE,黑板 [3017]/[3020])────────────
|
|
68
|
+
// 🔴 **到期复核已兑现(sdk 6.9.0 提货,2026-08-08)**:本臂此前是 switch **之前**的一条 raw
|
|
69
|
+
// 预分派(理由 = 它还没进 SDK 6.3.0 的 `AgentEvent` union,`case 'human_input'` 在 `ev.type`
|
|
70
|
+
// 上是编译错),头注写着「在 union 里的那天就把它搬进 switch」。sdk 6.9.0 的 events.d.ts
|
|
71
|
+
// 已声明该臂 ⇒ 按原定计划搬进来。这不是可选的整理:预分派用的是 `(ev as {type?:unknown})`
|
|
72
|
+
// 形,**不收窄** `ev`,所以臂一进 union,switch 的 `default` 就还看得见它 ⇒ B5 穷举断言
|
|
73
|
+
// (assertNeverArm)编译期真红。搬进来后本函数重回「每一条臂都受编译期穷举保护」。
|
|
74
|
+
// 处置不变:server 侧对本帧的投影明写「**本帧不带正文**(core 的臂里就没有 text 字段),所以
|
|
75
|
+
// 没有内容脱敏面」。因此它是**生命周期账本帧**,不是转录物 —— 投成中性内部臂交给 chrome 面
|
|
76
|
+
// 记账,绝不铸 transcript 行(铸一行就是替引擎编一条它没说过的话)。
|
|
77
|
+
// `humanInputProjection` 仍按 raw 记录读(逐键防御 + 白名单),不改口径:wire 是 JSON,引擎可能
|
|
78
|
+
// 比本包类型新一版,类型收窄不构成「字段一定合法」的保证。
|
|
79
|
+
case 'human_input':
|
|
80
|
+
return humanInputProjection(ev, ctx);
|
|
78
81
|
// service 1.75 — `workflow_complete`: an out-of-band background-workflow completion push, ridden onto
|
|
79
82
|
// the session's next stream open (BEFORE the run's own events). Projects to a NEUTRAL internal arm;
|
|
80
83
|
// the upstream bridge turns it into CC's `<task-notification>` injection.
|
|
@@ -189,20 +192,47 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
189
192
|
: {}),
|
|
190
193
|
})));
|
|
191
194
|
}
|
|
192
|
-
// §E3/§E10 — BrainStatus liveness
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
+
// §E3/§E10 — BrainStatus liveness. Phases: rate_limited/retrying/reconnecting/circuit_open +
|
|
196
|
+
// (core 5.x) recovered/gave_up — the two TERMINAL phases; `recovered` means the retry SUCCEEDED,
|
|
197
|
+
// so the overlay must be TORN DOWN, not rendered as an error (see retryStatus.ts). Project to a
|
|
198
|
+
// NEUTRAL internal `retry_status` arm forwarding the provider-neutral fields verbatim —
|
|
195
199
|
// NOT a renderable CC SDKMessage. The upstream bridge (sema layer) maps phase→SpinnerRetryStatus +
|
|
196
200
|
// publishes to the sema retry-status store the spinner override reads (187's per-agent
|
|
197
201
|
// `afa(agentId).retryStatus` app-state analog, ui-modules/0157_036_ui_Klo.js:31). The seam stays a
|
|
198
202
|
// dumb forwarder: the provider-neutral→UI-shape mapping is the renderer layer's job (retryStatusStore).
|
|
199
|
-
case 'status':
|
|
203
|
+
case 'status': {
|
|
204
|
+
// 🔴 开集 + 防御读(#3004 跟修批 2026-08-08,与上面 `tool_end.errorCode` 同族姿势):core
|
|
205
|
+
// `BrainStatus` 自 5.x 起还带 attempt / maxRetries / retryInMs 三键,server 两腿的
|
|
206
|
+
// `brainStatusEventData`(dist/trace/project.js)照发,而 SDK 的 `status` 臂类型只声明
|
|
207
|
+
// phase/detail/retryInSec(SDK 面尚未跟上 core)。类型面缺席不等于 wire 上缺席 —— 按
|
|
208
|
+
// unknown 读、按 number 窄化后原样透传,否则引擎真发的量在这一层就被剥掉了。
|
|
209
|
+
// 键集真源 = retryStatus.ts 的 `BRAIN_STATUS_PAYLOAD_KEYS`(engine-vocab G2-c 对账)。
|
|
210
|
+
const st = ev;
|
|
211
|
+
const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
212
|
+
const attempt = num(st.attempt);
|
|
213
|
+
const maxRetries = num(st.maxRetries);
|
|
214
|
+
const retryInMs = num(st.retryInMs);
|
|
200
215
|
return projected(stamp(ctx, armBody({
|
|
201
216
|
type: 'retry_status',
|
|
202
217
|
phase: ev.phase,
|
|
203
218
|
...(ev.detail !== undefined ? { detail: ev.detail } : {}),
|
|
204
219
|
...(ev.retryInSec !== undefined ? { retryInSec: ev.retryInSec } : {}),
|
|
220
|
+
...(retryInMs !== undefined ? { retryInMs } : {}),
|
|
221
|
+
...(attempt !== undefined ? { attempt } : {}),
|
|
222
|
+
...(maxRetries !== undefined ? { maxRetries } : {}),
|
|
223
|
+
// 🔴 §E2 lane 身份必须透传(2026-08-08 codex 二轮复审命中的**跨 lane 状态破坏**)。
|
|
224
|
+
// `status` 臂本来就是 `& EventIdentity`(SDK events.d.ts),server 两腿共用的
|
|
225
|
+
// `brainStatusEventData` 也经 `identityFields` 发 eventId/parentToolCallId —— 而本层此前
|
|
226
|
+
// 把两键都剥掉了。后果:`arms.ts` 的 `retryStatusArm` 首行那条子代断闸
|
|
227
|
+
// (`if (typeof m.parentToolCallId === 'string') return`,#47 矩阵 #5)在 wire 路径上**恒不命中**,
|
|
228
|
+
// 子代的 status 一直骑着主 spinner。旧代际下这只是显示噪声;`recovered` 相落地后它升级成
|
|
229
|
+
// 真状态破坏:**子代**的 recovered 会去 `takeRetryOverlay()` 消费掉**宿主**仍在重试的覆盖层,
|
|
230
|
+
// 向用户谎报「宿主已恢复」,而宿主随后真正的 recovered 反被判成「没有覆盖层」而沉默。
|
|
231
|
+
// 两键防御读(旧 server 缺席 ⇒ 键不 stamp,形状不变)。
|
|
232
|
+
...(typeof ev.eventId === 'string' ? { eventId: ev.eventId } : {}),
|
|
233
|
+
...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
|
|
205
234
|
})));
|
|
235
|
+
}
|
|
206
236
|
// MF-10 (design/99, events.d.ts task_progress) — a SUBAGENT PROGRESS TICK: the delegated sub-run's
|
|
207
237
|
// CUMULATIVE usage at each turn boundary (LIVE/EPHEMERAL, never persisted/replayed). Project to a
|
|
208
238
|
// NEUTRAL internal `task_progress` arm — NOT a renderable CC SDKMessage. The upstream bridge binds it
|
|
@@ -406,7 +436,44 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
406
436
|
// 臂投)——本切片不投影。⚠️ 「压缩失败让用户看见」是 chrome/HUD 面的活,今天两端都还没接
|
|
407
437
|
// (adapt 臂表同样无此臂),缺口记 0.13.0 候选,不许由本切片顺手编一个假 transcript 形。
|
|
408
438
|
case 'compaction_outcome':
|
|
439
|
+
// ── sdk 6.9.0 提货批(2026-08-08)新入 union 的四条臂 ─────────────────────────────────────
|
|
440
|
+
// `wiring_manifest`:引擎的接线自述(leg/ask/tools 指纹)—— 可观测面,CC transcript 无对位物。
|
|
441
|
+
case 'wiring_manifest':
|
|
442
|
+
// `tool_approval` / `tool_approval_complete`:HITL 审批帧。**#185a 起它们是 `AgentEvent` 的臂**
|
|
443
|
+
// (此前只是 named SSE frame),但本切片不是它们的消费口 —— 它们由
|
|
444
|
+
// `hitl/toolApprovalWire.isToolApprovalFrame` + `hitl/frameRouter` 走审批卡那条链。在这里投一
|
|
445
|
+
// 条 transcript 行只会让同一只 ask 出现两次;`nothing('not_in_slice')` 是如实陈述,不是丢帧。
|
|
446
|
+
// ⚠️ 它们**必须**出现在这个 switch 里(而不是靠 default 兜):进 switch 才受 B5 编译期穷举
|
|
447
|
+
// 保护,才能在下一次 union 变形时把「谁不投影」这件事重新摆到人眼前。
|
|
448
|
+
case 'tool_approval':
|
|
449
|
+
case 'tool_approval_complete':
|
|
409
450
|
return nothing('not_in_slice');
|
|
451
|
+
/**
|
|
452
|
+
* `approval_request`(design/172 流内审批协议,server ≥7.3.0、7.5.0 起默认开)。
|
|
453
|
+
*
|
|
454
|
+
* 🔴 与上面两条**不同源**,所以单列(2026-08-08 codex 复审第 4 轮命中:首版把三条并成一句
|
|
455
|
+
* 「都由 frameRouter 消费」—— 而 `isToolApprovalFrame` 只认 tool_approval/tool_approval_complete,
|
|
456
|
+
* 这条帧在**包内今天零消费口**,那句话是假的;pure 门 058⑬ 段把这个事实钉住了)。
|
|
457
|
+
*
|
|
458
|
+
* 🔴 分类 = `dropped/unsupported_arm` 而不是 `none/not_in_slice`(第 5 轮命中):`none` 是**静默**的,
|
|
459
|
+
* 只有 `dropped` 会经 `reportDroppedFrame` 走宿主 sink + console 留痕。本帧带着用户可见的决策内容
|
|
460
|
+
* (风险两轴 irreversible/egress/requiresRealApproval、窗口倒计时、`askId` 的持久 CAS 决策语义),
|
|
461
|
+
* 却没人接 —— 把这种缺口做成静默 none 就是本仓 P0 共性病那个形。宁可每次都吼一行。
|
|
462
|
+
* (本批之前它落 `default:` 的 `dropped/unknown_arm`,也是有痕的;#185a 把它加进 union 之后必须进
|
|
463
|
+
* switch 才编译得过,那一步不该顺手把痕迹弄丢。)
|
|
464
|
+
*
|
|
465
|
+
* 📋 缺口如实记账(**不**在本批修 —— 那是一条新卡链,是功能不是修复):
|
|
466
|
+
* · 存量路径今天仍能决断:SDK 头注(tool-approvals.d.ts)载明开关打开时同一只 ask 出两帧、顺序
|
|
467
|
+
* 钉死「先 legacy `tool_approval`、后 `approval_request`」,两帧同带 `approvalId`,legacy 腿在。
|
|
468
|
+
* · 但**降级路径不保证**:legacy respond 是 live-only + same-replica(错副本 404),而重连时
|
|
469
|
+
* pending 卡是靠 `approval_request` preamble 对账的 —— 「收到 legacy 帧后断线、按 Last-Event-ID
|
|
470
|
+
* 重连只再看到 approval_request」以及非粘性多副本部署这两条路上,今天只能等窗口到期→park/deny。
|
|
471
|
+
* · 接法(留给专门的一车):`isApprovalRequestFrameV1` 校形 → 按 approvalId/askId 去重并处理重连
|
|
472
|
+
* preamble → 带幂等键的 `decideAsk` 持久 CAS → 仅在能力位关闭/新帧不可用时回落 legacy respond。
|
|
473
|
+
* 在那条链落地之前,**不许**由本切片顺手编一个假 transcript 形来假装接上了。
|
|
474
|
+
*/
|
|
475
|
+
case 'approval_request':
|
|
476
|
+
return dropped('unsupported_arm', 'approval_request');
|
|
410
477
|
default:
|
|
411
478
|
// B5 穷举:SDK union 加成员必须在**编译期**打红这一行(而不是让新臂在用户面静默丢帧)。
|
|
412
479
|
assertNeverArm(ev);
|
|
@@ -128,6 +128,16 @@ export interface ApprovalCardRequest {
|
|
|
128
128
|
/** wire 层降级说明(卡 content 上方 dim 行,ToolUseConfirm.wireNote 超集位)——argsOmitted 等
|
|
129
129
|
* 不可注入 args 的提示走这里([1543]③ 记账的 note 位,2026-07-23 落位)。 */
|
|
130
130
|
wireNote?: string;
|
|
131
|
+
/**
|
|
132
|
+
* 治理强制位(server ≥7.5.0,2026-08-08 补透传)——原样来自
|
|
133
|
+
* {@link ToolApprovalFrame.governanceForced}。**缺席 ≠ false**:只在为真时在场,缺席 = 无治理来源
|
|
134
|
+
* 的证据。壳应据此把门呈成**表态掀不掉**(而不是引导用户去改 `permissionMode`);呈现形是壳半场,
|
|
135
|
+
* 本包只保证这一位到得了卡口 —— 此前它在帧类型上就不存在,于是宿主无论怎么写都读不到。
|
|
136
|
+
*/
|
|
137
|
+
governanceForced?: true;
|
|
138
|
+
/** 委派出处链(core 5.9.0 W1)——原样来自 {@link ToolApprovalFrame.delegation};只读展示增强,
|
|
139
|
+
* `agentName` UNTRUSTED-for-display。子代 ask 才在场。 */
|
|
140
|
+
delegation?: ToolApprovalDelegation;
|
|
131
141
|
}
|
|
132
142
|
/**
|
|
133
143
|
* 🔴 **拆缝口** —— 弹「三选卡」并等人的决断。壳 = vendored CC `PermissionRequest`;
|
|
@@ -190,13 +200,41 @@ export interface ToolApprovalFrame {
|
|
|
190
200
|
fromSubagent?: boolean;
|
|
191
201
|
/** core 1.378 RB-39②:展示身份(UNTRUSTED-for-display,server redact 后上帧)——徽章名一手源。 */
|
|
192
202
|
sourceAgentName?: string;
|
|
203
|
+
/**
|
|
204
|
+
* server ≥7.5.0([2942]/[2943],ADDITIVE;≤7.4.0 恒缺席)——**治理强制**位:此门来自运维治理层
|
|
205
|
+
* (部署侧 `AUTONOMY` / `commandPolicy` / `MANUAL_MODE_SHELL_GATE` / `SENSITIVE_WRITE_PATTERNS`),
|
|
206
|
+
* 不是模型默认门、也不是本次请求的客户端表态。它回答「我都开了 `bypassPermissions` 为什么还在问」。
|
|
207
|
+
*
|
|
208
|
+
* 🔴 **缺席 ≠ `false`**(与风险两轴同族纪律):本键**只在为真时在场**。缺席 = 「没有治理来源的
|
|
209
|
+
* 证据」,它既覆盖真的非治理 ask,也覆盖 server 侧判据够不着的形(治理 shell 门停在 `classify`
|
|
210
|
+
* 档时,一次 shell ask 出自分类器还是别的门无从分辨——server 选择让键缺席而不是猜)。所以缺席
|
|
211
|
+
* **不得**被渲染成「这门可以用表态绕开」。
|
|
212
|
+
*
|
|
213
|
+
* 分工:本包只做**透传**(帧 → {@link ApprovalCardRequest.governanceForced}),徽标呈现是壳半场。
|
|
214
|
+
*/
|
|
215
|
+
governanceForced?: true;
|
|
216
|
+
/**
|
|
217
|
+
* core 5.9.0 W1([2535],ADDITIVE):ask 的**委派出处链**(最内层孙代帧胜出)。与
|
|
218
|
+
* {@link fromSubagent} 同门在场(只有子代 ask 带)。只读展示增强 —— 卡可以说清这只 ask 是从哪
|
|
219
|
+
* 一层浮上来的。`agentName` 与 {@link sourceAgentName} 同属 UNTRUSTED-for-display。
|
|
220
|
+
*/
|
|
221
|
+
delegation?: ToolApprovalDelegation;
|
|
222
|
+
}
|
|
223
|
+
/** {@link ToolApprovalFrame.delegation} 的形(命名形,不用内联匿名 —— typeshape 门 B4 棘轮口径)。 */
|
|
224
|
+
export interface ToolApprovalDelegation {
|
|
225
|
+
/** 宿主侧那次委派调用的 tool-call id —— 卡的归属锚。 */
|
|
226
|
+
parentToolCallId: string;
|
|
227
|
+
/** 委派深度(宿主直接子代 = 1)。 */
|
|
228
|
+
depth: number;
|
|
229
|
+
/** 子代 agent 名。**UNTRUSTED**:spawning model 的自由文本(server 已脱敏+限长)。 */
|
|
230
|
+
agentName?: string;
|
|
193
231
|
}
|
|
194
232
|
/**
|
|
195
233
|
* 本地值拷贝的**运行时键集镜像**([1950]② 建议形):对账测试拿它与 SDK 0.1.6+ 的同源锚
|
|
196
234
|
* `TOOL_APPROVAL_FRAME_KEYS` 比对——SDK additive 增键时对账当天红,不再人肉追平。
|
|
197
235
|
* 下面两个类型钉保证镜像与 interface 本身不可能漂移(少键/多键都是编译错)。
|
|
198
236
|
*/
|
|
199
|
-
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "outcome"];
|
|
237
|
+
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "delegation", "outcome"];
|
|
200
238
|
/** 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
201
239
|
* (server 1.258 [1549]①3,旧代际兼容)。 */
|
|
202
240
|
export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
|
|
@@ -243,7 +281,10 @@ export interface ToolApprovalFrameOutcome {
|
|
|
243
281
|
/** server 的 200 ack。**缺席 = 未知**(注入面回 void / 旧 server / respond 失败),绝不当成 false。 */
|
|
244
282
|
ack?: ToolApprovalRespondAck;
|
|
245
283
|
}
|
|
246
|
-
/** 结构性识别流上的 tool_approval 帧(named SSE frame,payload.type ===
|
|
284
|
+
/** 结构性识别流上的 tool_approval 帧(named SSE frame,payload.type === 帧名)。
|
|
285
|
+
* ⚠️ 口径更正(2026-08-08):此处原写「非 AgentEvent arm」—— SDK #185a 起这两个帧**是**
|
|
286
|
+
* `AgentEvent` 的臂了(durable 腿也回放),所以结构识别与 union 收窄两条路都成立;本函数仍按
|
|
287
|
+
* 结构读(不依赖类型收窄),因为它同时服务 raw SSE 与 durable 回放两条入口。 */
|
|
247
288
|
export declare function isToolApprovalFrame(ev: unknown): ev is ToolApprovalFrame;
|
|
248
289
|
/**
|
|
249
290
|
* 消费一张 `tool_approval` 帧:弹三选卡 → respond。引擎此刻同步阻塞在本帧上(fail-closed 全腿:
|
|
@@ -301,10 +301,39 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
301
301
|
'message',
|
|
302
302
|
'args',
|
|
303
303
|
'argsOmitted',
|
|
304
|
+
// sdk 6.9.0 追平(2026-08-08,#3004 跟修批):server 7.5.0 起真发 `governanceForced`,core 5.9.0 W1
|
|
305
|
+
// 起真发 `delegation` —— 镜像滞后了两代,于是这两键在包内**零消费面**(治理徽标端到端不可达,
|
|
306
|
+
// [1947] 那次 `toolCallId` 滞后的同款形)。本门(run-approval-frame-keys-test.mjs)对 SDK 运行期
|
|
307
|
+
// 锚逐元素相等,所以这次滞后是被门抓到的,不是人肉发现的。
|
|
308
|
+
'governanceForced',
|
|
309
|
+
'delegation',
|
|
304
310
|
'outcome',
|
|
305
311
|
];
|
|
306
312
|
const _mirrorPin = [true, true];
|
|
307
313
|
void _mirrorPin;
|
|
314
|
+
/**
|
|
315
|
+
* `delegation` 的结构读(wire 是 JSON:注入面/旧 server/比本包新一版的 server 都可能给别的形)。
|
|
316
|
+
* 坏形一律降**缺席** —— 与 `readToolApprovalRespondAck` 同族纪律:「拿到一个不好用的值」不如
|
|
317
|
+
* 「诚实地什么都没有」。`parentToolCallId` 是卡的归属锚、`depth` 决定「第几层」的措辞,两者任一
|
|
318
|
+
* 不成形就整体不透传(半个链比没有链更容易被渲成错话);`agentName` 非串时单独降缺席。
|
|
319
|
+
*
|
|
320
|
+
* **不导出**(2026-08-08 收货修):它是本模块的边界窄化器,唯一消费点就在下面那个卡口调用;导出会
|
|
321
|
+
* 给公面加一个 `unknown` 入参签名(typeshape 门的 unknown-出境棘轮 +1),而它的行为在 pure 门里由
|
|
322
|
+
* `surfaceToolApprovalFrameAndRespond` 的坏形/正形素材端到端覆盖 —— 真消费路径比直调守卫更强。
|
|
323
|
+
*/
|
|
324
|
+
function isToolApprovalDelegation(v) {
|
|
325
|
+
if (v === null || typeof v !== 'object')
|
|
326
|
+
return false;
|
|
327
|
+
const d = v;
|
|
328
|
+
// 归属锚:必须是**非空白**串(`' '` 当 tool-call id 用会让卡按一个空锚归属)。
|
|
329
|
+
if (typeof d.parentToolCallId !== 'string' || d.parentToolCallId.trim().length === 0)
|
|
330
|
+
return false;
|
|
331
|
+
// 深度:上游语义是「宿主直接子代 = 1」⇒ **正安全整数**。0 / 负数 / 小数都不是可渲染的层数,
|
|
332
|
+
// 而 `Number.isFinite` 会把它们全放过(codex 复审命中:`depth: 0` / `-1` / `1.5` 曾一律判真)。
|
|
333
|
+
if (typeof d.depth !== 'number' || !Number.isSafeInteger(d.depth) || d.depth < 1)
|
|
334
|
+
return false;
|
|
335
|
+
return d.agentName === undefined || typeof d.agentName === 'string';
|
|
336
|
+
}
|
|
308
337
|
/** 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
309
338
|
* (server 1.258 [1549]①3,旧代际兼容)。 */
|
|
310
339
|
export function isFromSubagent(frame) {
|
|
@@ -348,7 +377,10 @@ export function readToolApprovalRespondAck(v) {
|
|
|
348
377
|
...(typeof o.updatedInputForwarded === 'boolean' ? { updatedInputForwarded: o.updatedInputForwarded } : {}),
|
|
349
378
|
};
|
|
350
379
|
}
|
|
351
|
-
/** 结构性识别流上的 tool_approval 帧(named SSE frame,payload.type ===
|
|
380
|
+
/** 结构性识别流上的 tool_approval 帧(named SSE frame,payload.type === 帧名)。
|
|
381
|
+
* ⚠️ 口径更正(2026-08-08):此处原写「非 AgentEvent arm」—— SDK #185a 起这两个帧**是**
|
|
382
|
+
* `AgentEvent` 的臂了(durable 腿也回放),所以结构识别与 union 收窄两条路都成立;本函数仍按
|
|
383
|
+
* 结构读(不依赖类型收窄),因为它同时服务 raw SSE 与 durable 回放两条入口。 */
|
|
352
384
|
export function isToolApprovalFrame(ev) {
|
|
353
385
|
const e = ev;
|
|
354
386
|
return (!!e &&
|
|
@@ -426,6 +458,9 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
426
458
|
// [1535]:子代的写审批浮到宿主卡(server 1.258 broker 腿)——卡头渲归属徽章(`· @name`),
|
|
427
459
|
// 用户知道在替谁批。判别=fromSubagent 显式键(core 1.378)优先、sourceTaskId 在场性权宜式兜底;
|
|
428
460
|
// 宿主自身 ask 两键恒缺席=徽章缺席,卡形不变。
|
|
461
|
+
// 🔴 治理位与委派链透传(2026-08-08):两键在帧上**只在为真/在场时**出现,所以这里也按在场性
|
|
462
|
+
// 条件 stamp —— 绝不 `governanceForced: frame.governanceForced === true`(那会把「缺席」折成
|
|
463
|
+
// 显式 `false`,而缺席的语义是「没有治理来源的证据」,不是「这门可以被表态掀掉」)。
|
|
429
464
|
const card = await surfaceApprovalCard({
|
|
430
465
|
toolName,
|
|
431
466
|
args: args,
|
|
@@ -433,6 +468,15 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
433
468
|
...(signal ? { signal } : {}),
|
|
434
469
|
...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
|
|
435
470
|
...(wireNote !== undefined ? { wireNote } : {}),
|
|
471
|
+
...(frame.governanceForced === true ? { governanceForced: true } : {}),
|
|
472
|
+
// 🔴 跨字段不变量(codex 复审命中):`delegation` 只在**子代 ask** 的门上在场(SDK 头注:
|
|
473
|
+
// "Present on the same door as fromSubagent (child asks only)")。只校对象形不校这一条 ⇒
|
|
474
|
+
// 一个**宿主自己**的破坏性工具审批也能带着 delegation 到卡口,被呈成「某层子代发起的」——
|
|
475
|
+
// 把归属这件安全展示位渲错,正是本键存在的反面。`isToolApprovalFrame` 只验 type/approvalId,
|
|
476
|
+
// 所以 raw SSE / durable 回放里的坏形到得了这里,必须在本层挡住。
|
|
477
|
+
...(isFromSubagent(frame) && isToolApprovalDelegation(frame.delegation)
|
|
478
|
+
? { delegation: frame.delegation }
|
|
479
|
+
: {}),
|
|
436
480
|
});
|
|
437
481
|
const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
|
|
438
482
|
if (card.kind === 'failed') {
|
package/dist/retryStatus.d.ts
CHANGED
|
@@ -6,16 +6,46 @@
|
|
|
6
6
|
* retrying → error → '✻ API error · Retrying in Ns'(非终态;187 硬编码 'API error')
|
|
7
7
|
* rate_limited → error → '✻ Usage limit reached · Retrying in Ns'(rateLimits 真 ⇒ 终态)
|
|
8
8
|
* circuit_open → error → '✻ <detail|Service temporarily unavailable> · Retrying in Ns'
|
|
9
|
-
*
|
|
9
|
+
* recovered → null → 覆盖层摘掉(重试**成功**,不是错误 —— 见下)
|
|
10
|
+
* gave_up → error+terminal → '✻ <detail>'(重试用尽的终态;与 recovered 反向。**打 terminal 位**,
|
|
11
|
+
* 渲染面据此不得再接「· Retrying in Ns」——已经没有下一次了)
|
|
12
|
+
* 绝不捏造 attempt 计数——只用引擎真给的 phase / detail / retryInSec / retryInMs / attempt / maxRetries。
|
|
13
|
+
*
|
|
14
|
+
* 🔴 员数与字段补全(2026-08-08,#3004 跟修批)。此前本文件只列 4 相 + 3 字段,而引擎侧
|
|
15
|
+
* (core `BrainStatusPhase` / `BrainStatus`,dist/core/types.d.ts)是 **6 相 + 6 字段**,server 两腿的
|
|
16
|
+
* `brainStatusEventData`(dist/trace/project.js)把 attempt/maxRetries/retryInMs 全发。两处后果:
|
|
17
|
+
* · `recovered`(引擎重试**成功**的终态)落 `default:` 臂 ⇒ 被渲成 `kind:'error'`,即
|
|
18
|
+
* 「刚刚恢复」被渲成「API error · Retrying」—— 方向完全相反的谎报;
|
|
19
|
+
* · attempt/maxRetries/retryInMs 在本层被剥掉 ⇒ `RetryStatus` 上早就留好的 attempt/maxRetries
|
|
20
|
+
* 两位恒缺席。这不是「不许编造」(那条纪律没错),是**供给方给了却没接**。
|
|
21
|
+
* 反漂移腿:`scripts/run-engine-vocab-floor-test.mjs` G2-b/G2-c 拿下面两张运行期镜像对**实装
|
|
22
|
+
* @sema-agent/core** 的 `BrainStatusPhase` / `BrainStatus` 逐词逐键对账 —— 引擎再加相/加键,那边先红。
|
|
10
23
|
*/
|
|
11
|
-
export type RetryStatus =
|
|
24
|
+
export type RetryStatus =
|
|
25
|
+
/** 等 API 响应(`reconnecting`)。**计数两位在场**:实装 core 的 reconnecting 帧与 retrying/
|
|
26
|
+
* rate_limited 同形,六字段全发(`stream-engine.js` 三处 `sleepAnnouncingRetry` 逐处实测)——
|
|
27
|
+
* 丢掉它们正好是在「用户最需要看重连进度」的那一刻把进度藏起来(2026-08-08 codex 复审命中)。 */
|
|
28
|
+
{
|
|
12
29
|
kind: 'stalled';
|
|
13
30
|
deadline: number;
|
|
31
|
+
attempt?: number;
|
|
32
|
+
maxRetries?: number;
|
|
14
33
|
} | {
|
|
15
34
|
kind: 'error';
|
|
16
35
|
deadline: number;
|
|
17
36
|
attempt?: number;
|
|
18
37
|
maxRetries?: number;
|
|
38
|
+
/**
|
|
39
|
+
* 🔴 **终态位**(2026-08-08 codex 复审命中):`true` ⇔ 引擎**不会再重试了**(`gave_up` 相)。
|
|
40
|
+
* 缺席 = 仍在重试循环里(retrying / rate_limited / circuit_open)。
|
|
41
|
+
*
|
|
42
|
+
* 为什么必须有这一位:`deadline` 的语义是「距下次重试」,而 gave_up 根本没有下次 —— 实装 core
|
|
43
|
+
* 的 finally 只发 `{phase:'gave_up', detail:'retries exhausted'}`(**不带** attempt/maxRetries,
|
|
44
|
+
* 所以消费方也无法靠「3/3」推出耗尽)。没有这一位,187 那套「<detail> · Retrying in Ns」句式
|
|
45
|
+
* 会把一次**已经放弃**渲成「正在重试,还剩 0 秒」——终帧稍有延迟,这句谎就一直挂着。
|
|
46
|
+
* 渲染措辞是壳半场(本包不渲染),本位只保证那个事实到得了壳。
|
|
47
|
+
*/
|
|
48
|
+
terminal?: true;
|
|
19
49
|
error: {
|
|
20
50
|
formatted: string;
|
|
21
51
|
isNetworkDown?: boolean;
|
|
@@ -28,9 +58,38 @@ export type RetryStatus = {
|
|
|
28
58
|
} | null;
|
|
29
59
|
};
|
|
30
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* core `BrainStatusPhase` 的**运行期值拷贝**(engine-vocab 门 G2-b 拿它对实装 core 对账)。
|
|
63
|
+
* 引擎侧是闭集,**读时按开集处理** —— 一个部署可以在自己的通道上冒出别的相,认得的分支照走,
|
|
64
|
+
* 认不得的落 `default:` 兜底(见 {@link mapBrainStatusToRetry} 末臂)。
|
|
65
|
+
*/
|
|
66
|
+
export declare const BRAIN_STATUS_PHASES: readonly ["rate_limited", "retrying", "reconnecting", "circuit_open", "recovered", "gave_up"];
|
|
67
|
+
export type BrainStatusPhase = (typeof BRAIN_STATUS_PHASES)[number];
|
|
68
|
+
/** wire 上 `status` 臂的载荷(= core `BrainStatus`;server 两腿白名单原样转发这 6 键)。 */
|
|
31
69
|
export interface BrainStatusPayload {
|
|
32
|
-
|
|
70
|
+
/** 闭集 + `(string & {})`:未知相仍可携带(开集读),不必先改类型再解析。 */
|
|
71
|
+
phase: BrainStatusPhase | (string & {});
|
|
72
|
+
/** 中性人话提示(无 provider/HTTP 细节;server 已脱敏 + 限 300 字)。 */
|
|
33
73
|
detail?: string;
|
|
74
|
+
/** 距下次重试的秒数(= `ceil(retryInMs/1000)`,core circuit-breaker 与 ms 位同发)。 */
|
|
34
75
|
retryInSec?: number;
|
|
76
|
+
/** 距下次重试的毫秒数 —— **更精的同一个量**,在场时优先于 `retryInSec`。 */
|
|
77
|
+
retryInMs?: number;
|
|
78
|
+
/** 引擎真实的第几次尝试(1-based)。缺席 = 引擎没给,绝不折成 0。 */
|
|
79
|
+
attempt?: number;
|
|
80
|
+
/** 引擎这一轮的重试上限。与 `attempt` 一起才能渲「2/5」。 */
|
|
81
|
+
maxRetries?: number;
|
|
35
82
|
}
|
|
36
|
-
|
|
83
|
+
/**
|
|
84
|
+
* 上面那个 interface 的**运行期键镜像**(照 `TOOL_APPROVAL_FRAME_KEYS_MIRROR` 先例):
|
|
85
|
+
* engine-vocab 门 G2-c 拿它与 core `BrainStatus` 的键集逐元素比 ⇒ 引擎 additive 增键当天红。
|
|
86
|
+
* 下面两个类型钉保证镜像与 interface 之间不可能漂移(少键/多键都是编译错)。
|
|
87
|
+
*/
|
|
88
|
+
export declare const BRAIN_STATUS_PAYLOAD_KEYS: readonly ["phase", "detail", "retryInSec", "retryInMs", "attempt", "maxRetries"];
|
|
89
|
+
/**
|
|
90
|
+
* BrainStatus 载荷 → spinner 行状态。
|
|
91
|
+
*
|
|
92
|
+
* 返回 **`null` = 没有覆盖层**(不是「不知道」):今天只有 `recovered` 走这条 —— 引擎明说这次重试
|
|
93
|
+
* 已经成功,行就该摘掉。此前本函数返回型钉死非空,于是 `recovered` 只能被塞进 error 臂。
|
|
94
|
+
*/
|
|
95
|
+
export declare function mapBrainStatusToRetry(p: BrainStatusPayload, nowMs: number): RetryStatus | null;
|
package/dist/retryStatus.js
CHANGED
|
@@ -6,23 +6,87 @@
|
|
|
6
6
|
* retrying → error → '✻ API error · Retrying in Ns'(非终态;187 硬编码 'API error')
|
|
7
7
|
* rate_limited → error → '✻ Usage limit reached · Retrying in Ns'(rateLimits 真 ⇒ 终态)
|
|
8
8
|
* circuit_open → error → '✻ <detail|Service temporarily unavailable> · Retrying in Ns'
|
|
9
|
-
*
|
|
9
|
+
* recovered → null → 覆盖层摘掉(重试**成功**,不是错误 —— 见下)
|
|
10
|
+
* gave_up → error+terminal → '✻ <detail>'(重试用尽的终态;与 recovered 反向。**打 terminal 位**,
|
|
11
|
+
* 渲染面据此不得再接「· Retrying in Ns」——已经没有下一次了)
|
|
12
|
+
* 绝不捏造 attempt 计数——只用引擎真给的 phase / detail / retryInSec / retryInMs / attempt / maxRetries。
|
|
13
|
+
*
|
|
14
|
+
* 🔴 员数与字段补全(2026-08-08,#3004 跟修批)。此前本文件只列 4 相 + 3 字段,而引擎侧
|
|
15
|
+
* (core `BrainStatusPhase` / `BrainStatus`,dist/core/types.d.ts)是 **6 相 + 6 字段**,server 两腿的
|
|
16
|
+
* `brainStatusEventData`(dist/trace/project.js)把 attempt/maxRetries/retryInMs 全发。两处后果:
|
|
17
|
+
* · `recovered`(引擎重试**成功**的终态)落 `default:` 臂 ⇒ 被渲成 `kind:'error'`,即
|
|
18
|
+
* 「刚刚恢复」被渲成「API error · Retrying」—— 方向完全相反的谎报;
|
|
19
|
+
* · attempt/maxRetries/retryInMs 在本层被剥掉 ⇒ `RetryStatus` 上早就留好的 attempt/maxRetries
|
|
20
|
+
* 两位恒缺席。这不是「不许编造」(那条纪律没错),是**供给方给了却没接**。
|
|
21
|
+
* 反漂移腿:`scripts/run-engine-vocab-floor-test.mjs` G2-b/G2-c 拿下面两张运行期镜像对**实装
|
|
22
|
+
* @sema-agent/core** 的 `BrainStatusPhase` / `BrainStatus` 逐词逐键对账 —— 引擎再加相/加键,那边先红。
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* core `BrainStatusPhase` 的**运行期值拷贝**(engine-vocab 门 G2-b 拿它对实装 core 对账)。
|
|
26
|
+
* 引擎侧是闭集,**读时按开集处理** —— 一个部署可以在自己的通道上冒出别的相,认得的分支照走,
|
|
27
|
+
* 认不得的落 `default:` 兜底(见 {@link mapBrainStatusToRetry} 末臂)。
|
|
28
|
+
*/
|
|
29
|
+
export const BRAIN_STATUS_PHASES = [
|
|
30
|
+
'rate_limited',
|
|
31
|
+
'retrying',
|
|
32
|
+
'reconnecting',
|
|
33
|
+
'circuit_open',
|
|
34
|
+
'recovered',
|
|
35
|
+
'gave_up',
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* 上面那个 interface 的**运行期键镜像**(照 `TOOL_APPROVAL_FRAME_KEYS_MIRROR` 先例):
|
|
39
|
+
* engine-vocab 门 G2-c 拿它与 core `BrainStatus` 的键集逐元素比 ⇒ 引擎 additive 增键当天红。
|
|
40
|
+
* 下面两个类型钉保证镜像与 interface 之间不可能漂移(少键/多键都是编译错)。
|
|
41
|
+
*/
|
|
42
|
+
export const BRAIN_STATUS_PAYLOAD_KEYS = [
|
|
43
|
+
'phase',
|
|
44
|
+
'detail',
|
|
45
|
+
'retryInSec',
|
|
46
|
+
'retryInMs',
|
|
47
|
+
'attempt',
|
|
48
|
+
'maxRetries',
|
|
49
|
+
];
|
|
50
|
+
const _brainStatusKeyPin = [true, true];
|
|
51
|
+
void _brainStatusKeyPin;
|
|
52
|
+
/**
|
|
53
|
+
* BrainStatus 载荷 → spinner 行状态。
|
|
54
|
+
*
|
|
55
|
+
* 返回 **`null` = 没有覆盖层**(不是「不知道」):今天只有 `recovered` 走这条 —— 引擎明说这次重试
|
|
56
|
+
* 已经成功,行就该摘掉。此前本函数返回型钉死非空,于是 `recovered` 只能被塞进 error 臂。
|
|
10
57
|
*/
|
|
11
58
|
export function mapBrainStatusToRetry(p, nowMs) {
|
|
12
|
-
|
|
59
|
+
// 退避剩余量:ms 位在场就用 ms(1200ms 用 sec 位会被 ceil 成 2s,行上少一秒多一秒都是谎)。
|
|
60
|
+
const remainingMs = typeof p.retryInMs === 'number' ? p.retryInMs : (p.retryInSec ?? 0) * 1000;
|
|
61
|
+
const deadline = nowMs + Math.max(0, remainingMs);
|
|
62
|
+
/** 引擎真给的计数才落位;缺席 ⇒ 键不 stamp(诚实缺席,不造 0)。 */
|
|
63
|
+
const counts = {
|
|
64
|
+
...(typeof p.attempt === 'number' ? { attempt: p.attempt } : {}),
|
|
65
|
+
...(typeof p.maxRetries === 'number' ? { maxRetries: p.maxRetries } : {}),
|
|
66
|
+
};
|
|
13
67
|
switch (p.phase) {
|
|
68
|
+
// 🔴 引擎直报「恢复」:摘行。绝不落 error 臂 —— 那是把成功渲成失败。
|
|
69
|
+
case 'recovered':
|
|
70
|
+
return null;
|
|
14
71
|
case 'reconnecting':
|
|
15
|
-
return { kind: 'stalled', deadline };
|
|
72
|
+
return { kind: 'stalled', deadline, ...counts };
|
|
16
73
|
case 'rate_limited':
|
|
17
|
-
return { kind: 'error', deadline, error: { formatted: '', rateLimits: {} } };
|
|
74
|
+
return { kind: 'error', deadline, ...counts, error: { formatted: '', rateLimits: {} } };
|
|
18
75
|
case 'circuit_open':
|
|
19
76
|
return {
|
|
20
77
|
kind: 'error',
|
|
21
78
|
deadline,
|
|
79
|
+
...counts,
|
|
22
80
|
error: { formatted: p.detail ?? 'Service temporarily unavailable', isNetworkDown: true },
|
|
23
81
|
};
|
|
82
|
+
// 重试用尽:仍是错误行(与 recovered 反向,绝不许一起归成「结束了 ⇒ 清行」),但**打终态位** ——
|
|
83
|
+
// 实装 core 的 finally 只发 `{phase, detail}`(detail = 'retries exhausted' / 'cancelled while
|
|
84
|
+
// retrying'),既没有计数也没有 retryIn*,所以「不会再重试了」这件事**只能**由本位表达;
|
|
85
|
+
// 靠 attempt===maxRetries 去推是错的(供给方根本不发那两位)。
|
|
86
|
+
case 'gave_up':
|
|
87
|
+
return { kind: 'error', deadline, terminal: true, ...counts, error: { formatted: p.detail ?? '' } };
|
|
24
88
|
case 'retrying':
|
|
25
89
|
default:
|
|
26
|
-
return { kind: 'error', deadline, error: { formatted: '' } };
|
|
90
|
+
return { kind: 'error', deadline, ...counts, error: { formatted: '' } };
|
|
27
91
|
}
|
|
28
92
|
}
|
package/dist/seatContract.d.ts
CHANGED
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
import type { ReasoningEffort } from "./effortWire.js";
|
|
76
76
|
import type { LaneProof } from "./seam.js";
|
|
77
77
|
import type { QuestionAnswer } from "./liveQuestionStore.js";
|
|
78
|
+
import type { ToolApprovalDelegation } from "./hitl/toolApprovalWire.js";
|
|
78
79
|
/** 一次门校验的失败说明。 */
|
|
79
80
|
export interface SeatValidationFailure {
|
|
80
81
|
/** 人可读的失败原因,恒在场且非空(宿主日志里那一行的正文)。 */
|
|
@@ -222,6 +223,18 @@ export interface ToolPermissionRequest {
|
|
|
222
223
|
* 与转录里的引擎时间戳不可直接相减)。缺席 = wire 那一行没带时间(旧 server 的 durable 行)⇒
|
|
223
224
|
* 消费端渲「刚刚」或不渲时间,**绝不**回落成 0(那会渲成 1970)。 */
|
|
224
225
|
createdAtMs?: number;
|
|
226
|
+
/** 治理强制位([C130] 补键,2026-08-08;server ≥7.5.0,语义原样来自
|
|
227
|
+
* `ApprovalCardRequest.governanceForced`):此门来自部署治理层,「permission-mode overrides
|
|
228
|
+
* do not apply」。🔴 缺席 ≠ `false`:键**只在为真时在场**,缺席 = 无治理来源证据(真的非治理
|
|
229
|
+
* ask 与 server 判据够不着的形共用缺席)⇒ 渲染端绝不渲「可用表态绕开」,也绝不合成治理徽标。
|
|
230
|
+
* 唯一合法供给 = 宿主(session-host)从已过 hitl wire 门的 `ApprovalCardRequest` **透传**,
|
|
231
|
+
* 绝不自铸;为 `false` 的值不该出现在座位面上(条件 stamp 纪律与 wire 门同款)。 */
|
|
232
|
+
governanceForced?: true;
|
|
233
|
+
/** 委派出处链([C130] 补键;core 5.9.0 W1,原样来自 `ApprovalCardRequest.delegation`,形 =
|
|
234
|
+
* hitl 的命名类型,单一真源)。只读展示增强——卡说清这只 ask 从哪一层子代浮上来;`agentName`
|
|
235
|
+
* UNTRUSTED-for-display(纯文本渲染,零注入面)。缺席 = 非子代 ask 或 wire 没给 ⇒ 不渲委派行。
|
|
236
|
+
* 坏形处置与 hitl wire 门同款纪律:**读坏形降缺席**,绝不因展示键拒掉整条审批行。 */
|
|
237
|
+
delegation?: ToolApprovalDelegation;
|
|
225
238
|
}
|
|
226
239
|
/**
|
|
227
240
|
* 座位事件名的**闭集**(REF-CC-065):这是三端唯一的事件名真源。
|
|
@@ -451,6 +464,14 @@ export interface SendMessageRequest {
|
|
|
451
464
|
/** Per-turn settings sub-object → `TaskRequest.settings`. Closed shape (`SeatTurnSettings`);
|
|
452
465
|
* today's only member is the `ultracode` preset boolean. */
|
|
453
466
|
settings?: SeatTurnSettings;
|
|
467
|
+
/** 本轮 scenario 拨盘选择([C131] 补载体,2026-08-08)→ `TaskRequest.scenario`。**开集**(SDK
|
|
468
|
+
* `Scenario = KnownScenario | (string & {})` 同姿态——部署可注册自有场景,座位面不替它闭集)。
|
|
469
|
+
* 缺席 = 不 stamp,由部署解析默认场景(assignment 链)。错误面是响亮的 typed 400:名字不在
|
|
470
|
+
* 部署词表 ⇒ `scenario_unknown`(body 带 live 词表 `known`);在词表但不在本 principal
|
|
471
|
+
* allowlist ⇒ `scenario_not_allowed`(带 allowlist)——渲染端可据此渲有效选项,不猜不折叠。
|
|
472
|
+
* 此前的结构性缺席与 `reasoningEffort` 同款:闭集载体就是 lane 本身,不在集里 = 拨盘的选择
|
|
473
|
+
* 在座位 lane 上没有位置可放(web [C131]① 记账的九键无载体形)。 */
|
|
474
|
+
scenario?: string;
|
|
454
475
|
}
|
|
455
476
|
/** `uploadAttachment` 入参(REF-CC-074:此前是导出方法签名里的 3 成员内联匿名形,直接命中 B4)。 */
|
|
456
477
|
export interface AttachmentUploadRequest {
|
|
@@ -665,9 +686,9 @@ export declare const SEAT_STORE_PROPS: readonly ["sessionsStateStore", "focusedS
|
|
|
665
686
|
/** 事件式(main→renderer push)的 verb —— SPEC 里 `kind: "event"` 的那一撮,门里逐元素对账。 */
|
|
666
687
|
export declare const SEAT_EVENT_METHOD_NAMES: readonly ["onEvent", "onToolPermissionRequest"];
|
|
667
688
|
export declare const LOCAL_SESSION_EVENT_KEYS: readonly ["type", "sessionId", "message", "messages", "error", "errorCategory", "code", "request", "data", "sources", "permissionMode", "session", "userMessageUuid", "hasPreClearSession", "requestId", "outcome", "chrome"];
|
|
668
|
-
export declare const TOOL_PERMISSION_REQUEST_KEYS: readonly ["requestId", "sessionId", "toolName", "toolCallId", "input", "message", "toctouBinding", "createdAtMs"];
|
|
689
|
+
export declare const TOOL_PERMISSION_REQUEST_KEYS: readonly ["requestId", "sessionId", "toolName", "toolCallId", "input", "message", "toctouBinding", "createdAtMs", "governanceForced", "delegation"];
|
|
669
690
|
export declare const LOCAL_SESSION_RECORD_KEYS: readonly ["sessionId", "engineSessionId", "model", "title", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked"];
|
|
670
|
-
export declare const SEND_MESSAGE_REQUEST_KEYS: readonly ["sessionId", "text", "model", "userMessageUuid", "permissionMode", "attachmentIds", "reasoningEffort", "retainBackgroundProcesses", "settings"];
|
|
691
|
+
export declare const SEND_MESSAGE_REQUEST_KEYS: readonly ["sessionId", "text", "model", "userMessageUuid", "permissionMode", "attachmentIds", "reasoningEffort", "retainBackgroundProcesses", "settings", "scenario"];
|
|
671
692
|
export declare const TOOL_PERMISSION_DECISION_KEYS: readonly ["sessionId", "requestId", "decision", "answer", "updatedInput", "remember"];
|
|
672
693
|
export declare const START_SESSION_OPTION_KEYS: readonly ["model", "title", "cwd", "excludeTools"];
|
|
673
694
|
/**
|
|
@@ -707,6 +728,12 @@ export declare const SEAT_VALIDATOR_KEY_COVERAGE: {
|
|
|
707
728
|
readonly unchecked: readonly [{
|
|
708
729
|
readonly key: "input";
|
|
709
730
|
readonly why: "UNTRUSTED service-redacted 工具参数 / 问题载荷:形状由工具自己定(CC request-args 姿态),渲染端只读不回喂;缺席/畸形的渲染义务写在字段注释里。";
|
|
731
|
+
}, {
|
|
732
|
+
readonly key: "governanceForced";
|
|
733
|
+
readonly why: "展示增强位,权威门在 hitl wire 卡口(条件 stamp,false 不上 wire);座位门再裁一次=配对机制前提不共享的雷,且拒整条 request 会为一个徽标键把安全 ask 丢给引擎 TTL deny。消费端按 `=== true` 读,其余一律当缺席。";
|
|
734
|
+
}, {
|
|
735
|
+
readonly key: "delegation";
|
|
736
|
+
readonly why: "展示增强位,权威结构门在 hitl wire 卡口(结构读+仅子代 ask 在场,坏形降缺席);座位门同款理由不设第二判官。消费端坏形降缺席,绝不拒行。";
|
|
710
737
|
}];
|
|
711
738
|
};
|
|
712
739
|
readonly LocalSessionEvent: {
|
package/dist/seatContract.js
CHANGED
|
@@ -657,6 +657,10 @@ export const TOOL_PERMISSION_REQUEST_KEYS = [
|
|
|
657
657
|
"message",
|
|
658
658
|
"toctouBinding",
|
|
659
659
|
"createdAtMs",
|
|
660
|
+
// [C130] 补键(2026-08-08):治理徽标+委派行的座位载体——desktop 座位 lane 此前无键可透传,
|
|
661
|
+
// 「治理徽标可开工」只在浏览器 lane 成立(半场口径,[C130] 修正)。
|
|
662
|
+
"governanceForced",
|
|
663
|
+
"delegation",
|
|
660
664
|
];
|
|
661
665
|
export const LOCAL_SESSION_RECORD_KEYS = [
|
|
662
666
|
"sessionId",
|
|
@@ -681,6 +685,8 @@ export const SEND_MESSAGE_REQUEST_KEYS = [
|
|
|
681
685
|
"reasoningEffort",
|
|
682
686
|
"retainBackgroundProcesses",
|
|
683
687
|
"settings",
|
|
688
|
+
// [C131] 补载体(2026-08-08):scenario 拨盘的座位 lane 席位(同上结构性缺席形)。
|
|
689
|
+
"scenario",
|
|
684
690
|
];
|
|
685
691
|
export const TOOL_PERMISSION_DECISION_KEYS = [
|
|
686
692
|
"sessionId",
|
|
@@ -730,6 +736,14 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
|
|
|
730
736
|
key: "input",
|
|
731
737
|
why: "UNTRUSTED service-redacted 工具参数 / 问题载荷:形状由工具自己定(CC request-args 姿态),渲染端只读不回喂;缺席/畸形的渲染义务写在字段注释里。",
|
|
732
738
|
},
|
|
739
|
+
{
|
|
740
|
+
key: "governanceForced",
|
|
741
|
+
why: "展示增强位,权威门在 hitl wire 卡口(条件 stamp,false 不上 wire);座位门再裁一次=配对机制前提不共享的雷,且拒整条 request 会为一个徽标键把安全 ask 丢给引擎 TTL deny。消费端按 `=== true` 读,其余一律当缺席。",
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
key: "delegation",
|
|
745
|
+
why: "展示增强位,权威结构门在 hitl wire 卡口(结构读+仅子代 ask 在场,坏形降缺席);座位门同款理由不设第二判官。消费端坏形降缺席,绝不拒行。",
|
|
746
|
+
},
|
|
733
747
|
],
|
|
734
748
|
},
|
|
735
749
|
LocalSessionEvent: {
|
package/dist/skillsWireCaps.d.ts
CHANGED
|
@@ -8,17 +8,44 @@
|
|
|
8
8
|
* loader graph (loadSkillsDir / loadPluginCommands pull analytics/signals/fs). It imports ONLY the `SkillSpec`
|
|
9
9
|
* type (erased at build), so a test can esbuild-bundle THIS file alone — no runtime side effects.
|
|
10
10
|
*
|
|
11
|
-
* The caps mirror the
|
|
12
|
-
*
|
|
11
|
+
* The caps mirror the server's `validateUserSkills` hard limits: ≤10 items, name ≤64, description ≤1024,
|
|
12
|
+
* content ≤1MB — a violation is a 400, so we truncate/limit HERE to make each FIELD well-formed
|
|
13
13
|
* by construction.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 措辞收窄(2026-08-08 codex 复审命中):原文写「make the live request well-formed by construction」——
|
|
16
|
+
* 那是**过头的承诺**,本模块只管每个字段的**逐字段**帽,管不到整请求。server 那边 10 项 × 1MB 与
|
|
17
|
+
* 它自己 8MB 的 `MAX_BODY` 是两个互不知情的帽,所以极端技能集仍可能整请求 400/413。本层不加
|
|
18
|
+
* 「按剩余预算偷偷多截一点」的聚合补偿:那恰好把一个响亮的 400 换回一次静默截断(= 本次修掉的病)。
|
|
19
|
+
* 该治的是 server 两帽的一致性;此处只把承诺说准。
|
|
14
20
|
*/
|
|
15
21
|
import type { SkillSpec } from '@sema-agent/sdk';
|
|
16
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Server caps for `TaskRequest.skills` — violations are a 400 (server `validateUserSkills`).
|
|
24
|
+
*
|
|
25
|
+
* 🔴 对照坐标(2026-08-08 修正,#3004 跟修批;取证方式 = 直读 shipped 产物,不信任何注释):
|
|
26
|
+
* · `items` / `name` / `description` —— server `dist/http/server.js` `validateUserSkills`:
|
|
27
|
+
* `MAX_USER_SKILLS = 10`、name 上限字面量 `64`、description 上限字面量 `1024`(7.5.0 实测)。
|
|
28
|
+
* · `content` —— server `MAX_SKILL_CONTENT_CHARS = 1_048_576`(`dist/http/server.js`),它自己的
|
|
29
|
+
* 头注点名这是 core `SKILL_CONTENT_MAX_CHARS` 的同值孪生(core `dist/core/runner/synthetic-tools.js`
|
|
30
|
+
* 的 `SKILL_CONTENT_MAX_CHARS = 1024 * 1024`);core 1.293 起技能正文**零截断、超 1MB 整拒**。
|
|
31
|
+
*
|
|
32
|
+
* 🔴 此前这一键钉 `32768` —— 那不是「上游改小了」,而是**一开始就抄错**:32768 是 server 的
|
|
33
|
+
* `MAX_OUTPUT_SCHEMA_CHARS`(另一个量)。错值的后果不是 400、是**静默截到 1/32**:`toCappedSpec`
|
|
34
|
+
* 在发请求前就把技能正文切掉,模型看见的 SKILL.md 少一大截,而链上任何一层都不会响一声。
|
|
35
|
+
*
|
|
36
|
+
* 反漂移腿:`scripts/run-engine-vocab-floor-test.mjs` 的 G2-a 段拿本键对**实装 @sema-agent/core**
|
|
37
|
+
* 的 `SKILL_CONTENT_MAX_CHARS` 逐值对账(devDep 钉在本仓,不靠兄弟树物料)——上游改门,那边先红。
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ 留给上游的一处**上游内部张力**(如实记录,本层不代偿):server 允许 10 条 × 1MB = 10MB 技能,
|
|
40
|
+
* 而它自己的整体 body 帽是 8MB(`MAX_BODY`)—— 极端组合会整请求 400/413。这是 server 两个帽之间的
|
|
41
|
+
* 事,壳侧不做「按剩余预算偷偷多截一点」的补偿:那正好会把一个**响亮的 400** 换回一次**静默截断**,
|
|
42
|
+
* 即本次修的这个病。真要治,治在 server 的两帽一致性上。
|
|
43
|
+
*/
|
|
17
44
|
export declare const SKILL_CAPS: {
|
|
18
45
|
readonly items: 10;
|
|
19
46
|
readonly name: 64;
|
|
20
47
|
readonly description: 1024;
|
|
21
|
-
readonly content:
|
|
48
|
+
readonly content: 1048576;
|
|
22
49
|
};
|
|
23
50
|
/** The minimal shape of a loaded skill `Command` this projection reads (a structural subset of CC's Command,
|
|
24
51
|
* cast at the call site so we stay decoupled from the wide Command union). */
|
package/dist/skillsWireCaps.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Server caps for `TaskRequest.skills` — violations are a 400 (server `validateUserSkills`).
|
|
3
|
+
*
|
|
4
|
+
* 🔴 对照坐标(2026-08-08 修正,#3004 跟修批;取证方式 = 直读 shipped 产物,不信任何注释):
|
|
5
|
+
* · `items` / `name` / `description` —— server `dist/http/server.js` `validateUserSkills`:
|
|
6
|
+
* `MAX_USER_SKILLS = 10`、name 上限字面量 `64`、description 上限字面量 `1024`(7.5.0 实测)。
|
|
7
|
+
* · `content` —— server `MAX_SKILL_CONTENT_CHARS = 1_048_576`(`dist/http/server.js`),它自己的
|
|
8
|
+
* 头注点名这是 core `SKILL_CONTENT_MAX_CHARS` 的同值孪生(core `dist/core/runner/synthetic-tools.js`
|
|
9
|
+
* 的 `SKILL_CONTENT_MAX_CHARS = 1024 * 1024`);core 1.293 起技能正文**零截断、超 1MB 整拒**。
|
|
10
|
+
*
|
|
11
|
+
* 🔴 此前这一键钉 `32768` —— 那不是「上游改小了」,而是**一开始就抄错**:32768 是 server 的
|
|
12
|
+
* `MAX_OUTPUT_SCHEMA_CHARS`(另一个量)。错值的后果不是 400、是**静默截到 1/32**:`toCappedSpec`
|
|
13
|
+
* 在发请求前就把技能正文切掉,模型看见的 SKILL.md 少一大截,而链上任何一层都不会响一声。
|
|
14
|
+
*
|
|
15
|
+
* 反漂移腿:`scripts/run-engine-vocab-floor-test.mjs` 的 G2-a 段拿本键对**实装 @sema-agent/core**
|
|
16
|
+
* 的 `SKILL_CONTENT_MAX_CHARS` 逐值对账(devDep 钉在本仓,不靠兄弟树物料)——上游改门,那边先红。
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ 留给上游的一处**上游内部张力**(如实记录,本层不代偿):server 允许 10 条 × 1MB = 10MB 技能,
|
|
19
|
+
* 而它自己的整体 body 帽是 8MB(`MAX_BODY`)—— 极端组合会整请求 400/413。这是 server 两个帽之间的
|
|
20
|
+
* 事,壳侧不做「按剩余预算偷偷多截一点」的补偿:那正好会把一个**响亮的 400** 换回一次**静默截断**,
|
|
21
|
+
* 即本次修的这个病。真要治,治在 server 的两帽一致性上。
|
|
22
|
+
*/
|
|
23
|
+
export const SKILL_CAPS = { items: 10, name: 64, description: 1024, content: 1_048_576 };
|
|
3
24
|
/** Build one capped `SkillSpec`, or null when there is no usable name/body. description folds in `whenToUse`
|
|
4
25
|
* (CC's progressive-disclosure metadata is name+description+whenToUse) so the engine's Skill tool can select
|
|
5
26
|
* it. All three fields are truncated to the server caps. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@sema-agent/agent-types": ">=0.2.0",
|
|
31
|
-
"@sema-agent/sdk": ">=6.
|
|
31
|
+
"@sema-agent/sdk": ">=6.9.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@sema-agent/agent-types": "^0.2.0",
|
|
35
35
|
"@sema-agent/core": "^5.10.0",
|
|
36
|
-
"@sema-agent/sdk": "^6.
|
|
36
|
+
"@sema-agent/sdk": "^6.9.0",
|
|
37
37
|
"esbuild": "^0.27.4",
|
|
38
38
|
"typescript": "^6.0.2"
|
|
39
39
|
}
|