@sema-agent/client-core 0.19.0 → 0.20.1
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 +76 -20
- package/dist/adapt/ids.d.ts +1 -1
- package/dist/adapt/ids.js +4 -2
- package/dist/adapt/textStream.js +2 -2
- package/dist/adapt/toolCards.js +1 -1
- package/dist/adapt/turnFlags.d.ts +14 -0
- package/dist/adapt/turnFlags.js +16 -4
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +2 -0
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +12 -1
- package/dist/adapter/downstream/eventToSdkMessage.js +129 -4
- package/dist/attachmentsWireCaps.d.ts +73 -10
- package/dist/attachmentsWireCaps.js +188 -14
- package/dist/controlRouter.d.ts +45 -8
- package/dist/controlRouter.js +53 -21
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +1 -1
- package/dist/hitl/frameRouter.js +3 -1
- package/dist/hitl/hitlBridge.d.ts +19 -2
- package/dist/hitl/hitlBridge.js +37 -0
- package/dist/hitl/hitlHostSurface.d.ts +27 -0
- package/dist/hitl/hitlHostSurface.js +50 -10
- package/dist/hitl/parkResolver.d.ts +8 -1
- package/dist/hitl/parkResolver.js +26 -1
- package/dist/hitl/toolApprovalWire.d.ts +84 -6
- package/dist/hitl/toolApprovalWire.js +112 -6
- package/dist/retryStatus.d.ts +63 -4
- package/dist/retryStatus.js +69 -5
- package/dist/seam.d.ts +44 -1
- package/dist/seam.js +6 -0
- package/dist/seatContract.d.ts +60 -6
- package/dist/seatContract.js +52 -1
- package/dist/skillsWireCaps.d.ts +31 -4
- package/dist/skillsWireCaps.js +23 -2
- package/package.json +3 -3
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/seam.d.ts
CHANGED
|
@@ -375,7 +375,50 @@ export type ChromeEvent = {
|
|
|
375
375
|
kind: 'plan_review_park';
|
|
376
376
|
laneProof: LaneProof;
|
|
377
377
|
result: unknown;
|
|
378
|
-
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* FIX⑦(2026-08-07,黑板 [3017]/[3020];core 5.14.0 design/171 新 `TaskEvent` 臂,server 7.4.0
|
|
381
|
+
* 起真上 SSE)——「**谁把什么喂进了这条 run**」的生命周期账本帧:objective / 实时 steer /
|
|
382
|
+
* nextTurn / parked-steer 续跑 / wake 五个载体共用一个投影点。
|
|
383
|
+
*
|
|
384
|
+
* 🔴 **本帧不带正文**(core 的臂里就没有 text 字段)—— 所以它归 chrome 而不是 transcript,
|
|
385
|
+
* 而且宿主**不许**据此铸任何一行「用户说了…」的转录:那会是替引擎编一句它没说过的话。
|
|
386
|
+
* 宿主消费义务(全部可选,fail-soft):把它记进会话审计/时间线(「12:03 operator 通过 steer
|
|
387
|
+
* 注入了一条指令」),或在 HUD 上闪一下。什么都不做也是合法的 —— 本臂存在的第一价值是
|
|
388
|
+
* **不再落 `unknown_arm` 被丢弃**(账本帧丢了,壳就说不出「这条 run 被谁插过话」)。
|
|
389
|
+
* `actor.hostAsserted` 是消费端唯一能判「这个署名可信吗」的位:渲署名而不渲这个位 = 把一个
|
|
390
|
+
* 未经验证的名字渲成可信的(core 自己的 `[from …]` 渲染就是靠它决定加不加 `(unverified)`)。
|
|
391
|
+
*/
|
|
392
|
+
| HumanInputChromeEvent;
|
|
393
|
+
/**
|
|
394
|
+
* `human_input` 臂的署名(core `ActorAssertion` 的投影;server 已 redact 后上帧)。
|
|
395
|
+
* 🔴 `hostAsserted` 是消费端**唯一**能判「这个署名可信吗」的位 —— 渲 `id` 而不渲它,
|
|
396
|
+
* 等于把一个未经验证的名字渲成可信的。
|
|
397
|
+
*/
|
|
398
|
+
export interface HumanInputActor {
|
|
399
|
+
id?: string;
|
|
400
|
+
hostAsserted?: boolean;
|
|
401
|
+
issuer?: string;
|
|
402
|
+
}
|
|
403
|
+
/** {@link ChromeEvent} 的 `human_input` 臂(具名:本仓 typeshape 门对内联匿名形有棘轮)。 */
|
|
404
|
+
export interface HumanInputChromeEvent {
|
|
405
|
+
kind: 'human_input';
|
|
406
|
+
laneProof: LaneProof;
|
|
407
|
+
/** 载体:objective / steer / next_turn / parked_steer / wake …(core 开集,原样透传)。 */
|
|
408
|
+
carrier: string;
|
|
409
|
+
inputId?: string;
|
|
410
|
+
sessionSeq?: number;
|
|
411
|
+
/** 入口来源(oa / cc-mcp / portal …)。 */
|
|
412
|
+
source?: string;
|
|
413
|
+
delivery?: string;
|
|
414
|
+
/** 宿主从入口凭据派生的身份串(server 已 redact)。UNTRUSTED-for-display。 */
|
|
415
|
+
issuer?: string;
|
|
416
|
+
actor?: HumanInputActor;
|
|
417
|
+
entryId?: string;
|
|
418
|
+
/** 引擎铸的**稳定事件身份** —— durable 腿按 `Last-Event-ID` 续读会重放同一条账本帧,宿主
|
|
419
|
+
* 落审计/时间线时**按这个键幂等**(`inputId`/`entryId` 都是可选位,靠不住)。 */
|
|
420
|
+
eventId?: string;
|
|
421
|
+
}
|
|
379
422
|
/** `ChromeEvent` 的判别键(臂名)—— 覆盖率断言与宿主装配自检都锚在它上。 */
|
|
380
423
|
export type ChromeArmKind = ChromeEvent['kind'];
|
|
381
424
|
/**
|
package/dist/seam.js
CHANGED
|
@@ -35,6 +35,12 @@ const CHROME_ARM_TABLE = {
|
|
|
35
35
|
prompt_suggestions: { required: false, duty: '推 composer 上方 chips(绝不回喂模型)' },
|
|
36
36
|
last_turn_usage: { required: true, duty: '落最近一次 turn 真 usage(statusline 回落源)' },
|
|
37
37
|
plan_review_park: { required: true, duty: '弹 plan 审批卡(fail-soft,不挡后续终态帧)' },
|
|
38
|
+
// FIX⑦:账本帧,**不带正文** ⇒ 义务是可选的(记进审计/时间线即可);🔴 硬约束是**不许**据此
|
|
39
|
+
// 铸转录行,渲署名时必须带上 `actor.hostAsserted` 的可信度标注。
|
|
40
|
+
human_input: {
|
|
41
|
+
required: false,
|
|
42
|
+
duty: '可选:记进会话审计/时间线(谁把什么喂进了这条 run)。绝不铸 transcript 行(本帧不带正文);渲署名必须跟渲 actor.hostAsserted 的可信度标注',
|
|
43
|
+
},
|
|
38
44
|
};
|
|
39
45
|
/**
|
|
40
46
|
* chrome 臂**覆盖率断言出口**(§8-1 裁决的一半:client-core 定接口不定实现)。
|
package/dist/seatContract.d.ts
CHANGED
|
@@ -150,6 +150,17 @@ export interface LocalSessionRecord {
|
|
|
150
150
|
* registry 对账再回写:盘上的 `parked` 是过去的声明,不直接当现势渲。 */
|
|
151
151
|
parked?: boolean;
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* `ToolPermissionRequest.requestId` 的**域词表**(FIX⑥,2026-08-07)—— 三端唯一真源。
|
|
155
|
+
*
|
|
156
|
+
* 🔴 前缀不是装饰:它**就是**决断的路由依据(session-host 按它挑 wire 动词)。一个域外的 requestId
|
|
157
|
+
* 意味着「这条决断无处可去」,所以校验器对它 fail-loud 而不是放行(此前门只校「是不是串」,
|
|
158
|
+
* 于是 desktop 早就在用的 `plan:` 既不在枚举里、也不会被任何一处说出来)。
|
|
159
|
+
*/
|
|
160
|
+
export declare const TOOL_PERMISSION_REQUEST_ID_DOMAINS: readonly ["live:", "durable:", "question:", "elicit:", "plan:"];
|
|
161
|
+
export type ToolPermissionRequestIdDomain = (typeof TOOL_PERMISSION_REQUEST_ID_DOMAINS)[number];
|
|
162
|
+
/** 读口:requestId → 它的域。未登记前缀 / 非串 ⇒ `undefined`(诚实缺席,绝不猜一个域)。 */
|
|
163
|
+
export declare function toolPermissionRequestIdDomain(requestId: unknown): ToolPermissionRequestIdDomain | undefined;
|
|
153
164
|
/** design/80 D-1 的 TOCTOU 绑定**对**(REF-CC-070:此前是两个互不相干的 optional,半个 pair 照样过门)。
|
|
154
165
|
* 一位承载整对 ⇒「只有 callId 没有 inputHash」在类型层不可表达,门里也不用再补联合判据。 */
|
|
155
166
|
export interface ToctouBinding {
|
|
@@ -160,8 +171,9 @@ export declare const TOCTOU_BINDING_KEYS: readonly ["callId", "inputHash"];
|
|
|
160
171
|
/** HITL request row (CC's `request` position is schema-gated by `S1`; ours is this shape, projected
|
|
161
172
|
* from the sema wire's PendingCheckpoint — see session-host). */
|
|
162
173
|
export interface ToolPermissionRequest {
|
|
163
|
-
/** Stable request key — echo back on respondToToolPermission.
|
|
164
|
-
* (the prefix is what routes the decision back to the right wire verb — see session-host)
|
|
174
|
+
/** Stable request key — echo back on respondToToolPermission. FIVE id domains, prefix-tagged
|
|
175
|
+
* (the prefix is what routes the decision back to the right wire verb — see session-host);
|
|
176
|
+
* 词表真源 = {@link TOOL_PERMISSION_REQUEST_ID_DOMAINS},读口 = {@link toolPermissionRequestIdDomain}:
|
|
165
177
|
* `live:<approvalId>` = LIVE in-band tool-approval face (frame rides the token
|
|
166
178
|
* stream; unanswered → engine fail-CLOSED deny at its TTL);
|
|
167
179
|
* `durable:<engineSessionId>:<callId>` = the durable checkpoint class (approvals inbox);
|
|
@@ -170,8 +182,14 @@ export interface ToolPermissionRequest {
|
|
|
170
182
|
* headless default `{answers:[]}` at the 5-min TTL, which is
|
|
171
183
|
* why leaving this family unrendered is silent, [1947] 红④);
|
|
172
184
|
* `elicit:<elicitationId>` = LIVE inbound-MCP elicitation (a server asks the user for
|
|
173
|
-
* form input mid-tool-call)
|
|
174
|
-
*
|
|
185
|
+
* form input mid-tool-call);
|
|
186
|
+
* `plan:<checkpointId>` = plan-review park(FIX⑥ 2026-08-07 补登记)—— 决断路由到
|
|
187
|
+
* `assistant.planReview`,**不是** `/decide`(server 对走错
|
|
188
|
+
* 门的调用回 409 `gate_not_plan_review`)。desktop 的
|
|
189
|
+
* session-host 早已在铸这个前缀,而本枚举只写了四域:
|
|
190
|
+
* 域表说四个、现实跑五个,靠「域外的行长什么样」做判断的
|
|
191
|
+
* 消费端于是各猜各的。
|
|
192
|
+
* The LIVE domains are same-replica + non-durable: a late/duplicate decision 404s, which the
|
|
175
193
|
* host treats as "already released" (dismiss), never as a retryable error. */
|
|
176
194
|
requestId: string;
|
|
177
195
|
sessionId: string;
|
|
@@ -595,6 +613,12 @@ export interface LocalSessionsApi {
|
|
|
595
613
|
/** Read-only model catalog (engine `GET /v1/models`). Empty `models` = the deployment offers no
|
|
596
614
|
* catalog; the renderer must degrade honestly rather than fall back to a fixture list. */
|
|
597
615
|
listModels(): Promise<SeatModelCatalog>;
|
|
616
|
+
/** [C115]/[C117] 模型配置写 verb:把选定的 catalog 条目落成宿主配置(settings.json env 块)。
|
|
617
|
+
* 写盘语义归宿主实现(desktop main 复用 cli buildModelEnv 键映射+updateSettingsForSource 锁形+
|
|
618
|
+
* RETIRED_MODEL_ENV_TOMBSTONES 墓碑);契约只定形与诚实位。🔴 req.apiKey 是敏感位:validator
|
|
619
|
+
* 校形不校值,任何 throw/log/ack 绝不回显 req(错误消息只说哪个键缺/形错,不带值);
|
|
620
|
+
* requiresRestart 必须如实渲(改完不重启=假 affordance)。 */
|
|
621
|
+
applyModelConfig(req: ApplyModelConfigRequest): Promise<ApplyModelConfigResult>;
|
|
598
622
|
onEvent(cb: (ev: LocalSessionEvent) => void): () => void;
|
|
599
623
|
onToolPermissionRequest(cb: (req: ToolPermissionRequest) => void): () => void;
|
|
600
624
|
/** `$store$` reactive states (read + subscribe; writes go through the verbs above). */
|
|
@@ -606,11 +630,36 @@ export declare function isLocalSessionRecord(s: unknown): s is LocalSessionRecor
|
|
|
606
630
|
export declare function isToolPermissionRequest(r: unknown): r is ToolPermissionRequest;
|
|
607
631
|
export declare function isLocalSessionEvent(v: unknown): v is LocalSessionEvent;
|
|
608
632
|
export declare function isAttachmentUploadReceipt(v: unknown): v is AttachmentUploadReceipt;
|
|
633
|
+
/** 与 cli applyOnboard.CatalogEntry 逐键同形(真源对齐:cli 侧以 satisfies 断言钉住两形不漂——
|
|
634
|
+
* 包不反向依赖壳,故此处独立铸型)。api 闭集;apiKey 敏感位(校形不校值,零回显)。 */
|
|
635
|
+
export interface SeatCatalogEntry {
|
|
636
|
+
providerId: string;
|
|
637
|
+
providerName: string;
|
|
638
|
+
baseUrl: string;
|
|
639
|
+
api: "anthropic-messages" | "openai-completions";
|
|
640
|
+
apiKey?: string;
|
|
641
|
+
modelId: string;
|
|
642
|
+
contextWindow: number;
|
|
643
|
+
maxTokens: number;
|
|
644
|
+
cheapHint?: boolean;
|
|
645
|
+
}
|
|
646
|
+
export interface ApplyModelConfigRequest {
|
|
647
|
+
defaultModel: SeatCatalogEntry;
|
|
648
|
+
/** 缺席 = 与 default 同(宿主不写 MODEL_CHEAP_ID)。 */
|
|
649
|
+
cheapModel?: SeatCatalogEntry;
|
|
650
|
+
}
|
|
651
|
+
export interface ApplyModelConfigResult {
|
|
652
|
+
ok: boolean;
|
|
653
|
+
/** ok:false 时必在(GUI 要能渲原因);同样零敏感值回显。 */
|
|
654
|
+
reason?: string;
|
|
655
|
+
/** 必须如实渲(改完不重启=假 affordance,[C115] 预登记)。 */
|
|
656
|
+
requiresRestart: boolean;
|
|
657
|
+
}
|
|
609
658
|
export declare function isSeatModelCatalog(v: unknown): v is SeatModelCatalog;
|
|
610
659
|
export declare function isSendMessageAck(v: unknown): v is SendMessageAck;
|
|
611
660
|
export declare const LOCAL_SESSIONS_SPEC: EipcServiceSpec;
|
|
612
661
|
/** verb 名清单 = `LOCAL_SESSIONS_SPEC.methods` 应有的键集(逐元素相等,门里断言)。 */
|
|
613
|
-
export declare const SEAT_METHOD_NAMES: readonly ["start", "sendMessage", "interrupt", "stop", "respondToToolPermission", "cancelQueuedMessage", "reorderQueuedMessage", "promoteQueuedMessage", "setFocusedSession", "setModel", "getTranscript", "searchSessions", "uploadAttachment", "listModels", "onEvent", "onToolPermissionRequest"];
|
|
662
|
+
export declare const SEAT_METHOD_NAMES: readonly ["start", "sendMessage", "interrupt", "stop", "respondToToolPermission", "cancelQueuedMessage", "reorderQueuedMessage", "promoteQueuedMessage", "setFocusedSession", "setModel", "getTranscript", "searchSessions", "uploadAttachment", "listModels", "applyModelConfig", "onEvent", "onToolPermissionRequest"];
|
|
614
663
|
/** `$store$` 属性名清单;SPEC 的 store 键 + `"Store"` 必须逐条等于这里的名字(门里断言)。 */
|
|
615
664
|
export declare const SEAT_STORE_PROPS: readonly ["sessionsStateStore", "focusedSessionStateStore", "settingsStateStore"];
|
|
616
665
|
/** 事件式(main→renderer push)的 verb —— SPEC 里 `kind: "event"` 的那一撮,门里逐元素对账。 */
|
|
@@ -681,6 +730,11 @@ export declare const SEAT_VALIDATOR_KEY_COVERAGE: {
|
|
|
681
730
|
readonly validated: readonly ["models", "defaultId"];
|
|
682
731
|
readonly unchecked: readonly [];
|
|
683
732
|
};
|
|
733
|
+
readonly ApplyModelConfigResult: {
|
|
734
|
+
readonly check: (v: unknown) => SeatValidation;
|
|
735
|
+
readonly validated: readonly ["ok", "requiresRestart", "reason"];
|
|
736
|
+
readonly unchecked: readonly [];
|
|
737
|
+
};
|
|
684
738
|
readonly SendMessageAck: {
|
|
685
739
|
readonly guard: "isSendMessageAck";
|
|
686
740
|
readonly check: (v: unknown) => SeatValidation;
|
|
@@ -760,6 +814,6 @@ export declare const SEAT_VALIDATOR_COVERAGE_EXEMPT: readonly [{
|
|
|
760
814
|
}, {
|
|
761
815
|
readonly label: "LaneProof";
|
|
762
816
|
readonly check: (v: unknown) => SeatValidation;
|
|
763
|
-
readonly why:
|
|
817
|
+
readonly why: string;
|
|
764
818
|
}];
|
|
765
819
|
export {};
|
package/dist/seatContract.js
CHANGED
|
@@ -98,6 +98,26 @@ const isObject = (v) => typeof v === "object" && v !== null;
|
|
|
98
98
|
const isAbsent = (v) => typeof v === "undefined";
|
|
99
99
|
const optionalString = (v) => isAbsent(v) || typeof v === "string";
|
|
100
100
|
const inTuple = (tuple, v) => tuple.includes(v);
|
|
101
|
+
/**
|
|
102
|
+
* `ToolPermissionRequest.requestId` 的**域词表**(FIX⑥,2026-08-07)—— 三端唯一真源。
|
|
103
|
+
*
|
|
104
|
+
* 🔴 前缀不是装饰:它**就是**决断的路由依据(session-host 按它挑 wire 动词)。一个域外的 requestId
|
|
105
|
+
* 意味着「这条决断无处可去」,所以校验器对它 fail-loud 而不是放行(此前门只校「是不是串」,
|
|
106
|
+
* 于是 desktop 早就在用的 `plan:` 既不在枚举里、也不会被任何一处说出来)。
|
|
107
|
+
*/
|
|
108
|
+
export const TOOL_PERMISSION_REQUEST_ID_DOMAINS = [
|
|
109
|
+
"live:",
|
|
110
|
+
"durable:",
|
|
111
|
+
"question:",
|
|
112
|
+
"elicit:",
|
|
113
|
+
"plan:",
|
|
114
|
+
];
|
|
115
|
+
/** 读口:requestId → 它的域。未登记前缀 / 非串 ⇒ `undefined`(诚实缺席,绝不猜一个域)。 */
|
|
116
|
+
export function toolPermissionRequestIdDomain(requestId) {
|
|
117
|
+
if (typeof requestId !== "string")
|
|
118
|
+
return undefined;
|
|
119
|
+
return TOOL_PERMISSION_REQUEST_ID_DOMAINS.find((d) => requestId.startsWith(d));
|
|
120
|
+
}
|
|
101
121
|
export const TOCTOU_BINDING_KEYS = ["callId", "inputHash"];
|
|
102
122
|
/**
|
|
103
123
|
* 座位事件名的**闭集**(REF-CC-065):这是三端唯一的事件名真源。
|
|
@@ -250,6 +270,10 @@ const checkToolPermissionRequest = (r) => {
|
|
|
250
270
|
return bad("tool permission request is not an object");
|
|
251
271
|
if (typeof r.requestId !== "string")
|
|
252
272
|
return bad("requestId must be a string", "requestId");
|
|
273
|
+
// FIX⑥:域(前缀)就是决断的路由依据 —— 认不出域 = 这条决断无处可去,fail-closed 而不是放行。
|
|
274
|
+
if (toolPermissionRequestIdDomain(r.requestId) === undefined) {
|
|
275
|
+
return bad(`requestId must carry a registered domain prefix (${TOOL_PERMISSION_REQUEST_ID_DOMAINS.join("|")})`, "requestId");
|
|
276
|
+
}
|
|
253
277
|
if (typeof r.sessionId !== "string")
|
|
254
278
|
return bad("sessionId must be a string", "sessionId");
|
|
255
279
|
if (!isAbsent(r.toolName) && r.toolName !== null && typeof r.toolName !== "string") {
|
|
@@ -417,6 +441,22 @@ const checkSeatModelInfo = (m, index) => {
|
|
|
417
441
|
}
|
|
418
442
|
return true;
|
|
419
443
|
};
|
|
444
|
+
/** applyModelConfig ack 校验:布尔形 + reason 条件在场(ok:false ⇒ 必带非空 reason)。
|
|
445
|
+
* 🔴 错误消息零回显 req/ack 值——本 verb 载荷带 apiKey,校验层是回显面之一。 */
|
|
446
|
+
const checkApplyModelConfigResult = (v) => {
|
|
447
|
+
if (!isObject(v))
|
|
448
|
+
return bad("applyModelConfig result is not an object");
|
|
449
|
+
if (typeof v.ok !== "boolean")
|
|
450
|
+
return bad("ok must be a boolean", "ok");
|
|
451
|
+
if (typeof v.requiresRestart !== "boolean")
|
|
452
|
+
return bad("requiresRestart must be a boolean", "requiresRestart");
|
|
453
|
+
if (v.ok === false && (typeof v.reason !== "string" || v.reason.length === 0)) {
|
|
454
|
+
return bad("reason must be a non-empty string when ok is false", "reason");
|
|
455
|
+
}
|
|
456
|
+
if (v.reason !== undefined && typeof v.reason !== "string")
|
|
457
|
+
return bad("reason must be a string when present", "reason");
|
|
458
|
+
return true;
|
|
459
|
+
};
|
|
420
460
|
const checkSeatModelCatalog = (v) => {
|
|
421
461
|
if (!isObject(v))
|
|
422
462
|
return bad("model catalog is not an object");
|
|
@@ -548,6 +588,7 @@ export const LOCAL_SESSIONS_SPEC = {
|
|
|
548
588
|
searchSessions: { kind: "invoke", check: checkSessionSearchEnvelope },
|
|
549
589
|
uploadAttachment: { kind: "invoke", check: checkAttachmentUploadReceipt },
|
|
550
590
|
listModels: { kind: "invoke", check: checkSeatModelCatalog },
|
|
591
|
+
applyModelConfig: { kind: "invoke", check: checkApplyModelConfigResult },
|
|
551
592
|
onEvent: { kind: "event", check: checkLocalSessionEvent },
|
|
552
593
|
onToolPermissionRequest: { kind: "event", check: checkToolPermissionRequest },
|
|
553
594
|
},
|
|
@@ -573,6 +614,7 @@ export const SEAT_METHOD_NAMES = [
|
|
|
573
614
|
"searchSessions",
|
|
574
615
|
"uploadAttachment",
|
|
575
616
|
"listModels",
|
|
617
|
+
"applyModelConfig",
|
|
576
618
|
"onEvent",
|
|
577
619
|
"onToolPermissionRequest",
|
|
578
620
|
];
|
|
@@ -730,6 +772,11 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
|
|
|
730
772
|
validated: ["models", "defaultId"],
|
|
731
773
|
unchecked: [],
|
|
732
774
|
},
|
|
775
|
+
ApplyModelConfigResult: {
|
|
776
|
+
check: checkApplyModelConfigResult,
|
|
777
|
+
validated: ["ok", "requiresRestart", "reason"],
|
|
778
|
+
unchecked: [],
|
|
779
|
+
},
|
|
733
780
|
SendMessageAck: {
|
|
734
781
|
guard: "isSendMessageAck",
|
|
735
782
|
check: checkSendMessageAck,
|
|
@@ -815,7 +862,11 @@ export const SEAT_VALIDATOR_COVERAGE_EXEMPT = [
|
|
|
815
862
|
{
|
|
816
863
|
label: "LaneProof",
|
|
817
864
|
check: (v) => checkLaneProof(v, "laneProof"),
|
|
818
|
-
why: "判别式联合:必填键面随 `lane` 臂而变(subagent 要 parentToolCallId、workflow 要 via+key),而覆盖表的方法是「一个最小合法体 + 逐键投毒」—— 那套方法在它身上没有判别力(main 臂对另外两臂的伴随位本来就不该拒,投毒过了不代表漏校)
|
|
865
|
+
why: "判别式联合:必填键面随 `lane` 臂而变(subagent 要 parentToolCallId、workflow 要 via+key),而覆盖表的方法是「一个最小合法体 + 逐键投毒」—— 那套方法在它身上没有判别力(main 臂对另外两臂的伴随位本来就不该拒,投毒过了不代表漏校)。" +
|
|
866
|
+
"🔴 FIX④(2026-08-07 订正):本条此前自称判别力由「F 段**逐臂**投毒(空对象 / 非对象 / **未登记车道词**)」承担 —— 全树实扫,那三样里只有**空对象**与**非对象**真的存在(run-seat-contract-keys-test.mjs F 段)," +
|
|
867
|
+
"「未登记车道词」实际只钉了 `lane: 7`(非串,证明不了闭集成员判定在场),「逐臂」(subagent 缺 parentToolCallId / workflow 缺 via+key)则是**零覆盖**。也就是说这条豁免曾经拿三件不存在的判据给自己背书。" +
|
|
868
|
+
"现势(判据锚在真存在的东西上):① `LANE_PROOF_LANE_TUPLE` ⇄ seam.ts 车道联合的双向 `Covers`(本文件 `_seatContractPins`,加车道不跟 ⇒ 编译红);" +
|
|
869
|
+
"② run-client-core-pure-test.mjs **FIX7 段 ④** —— 未登记车道词 / subagent 缺伴随位 / workflow 缺 via 或 key 三条投毒 + 两条逐臂正控 + 一条「main 臂不被过紧误拒」负控,全部直接喂本条的 `check`。",
|
|
819
870
|
},
|
|
820
871
|
];
|
|
821
872
|
/** 双向覆盖钉:`Exclude` 两个方向都必须塌成 `never`,少一个键红、多一个键也红。
|
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.20.1",
|
|
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
|
}
|