@sema-agent/client-core 0.36.0 → 0.37.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/CHANGELOG.md +178 -0
- package/README.md +1 -1
- package/dist/adapt/arms.js +3 -0
- package/dist/adapter/activeRunSelfHeal.d.ts +35 -2
- package/dist/adapter/activeRunSelfHeal.js +157 -4
- package/dist/adapter/downstream/eventToSdkMessage.js +5 -0
- package/dist/adapter/runStream.d.ts +14 -1
- package/dist/adapter/runStream.js +4 -0
- package/dist/engineCapsCache.d.ts +48 -2
- package/dist/engineCapsCache.js +157 -11
- package/dist/fleet/fleetProjection.d.ts +9 -1
- package/dist/fleet/fleetProjection.js +13 -3
- package/dist/fleetTaskDesc.d.ts +5 -1
- package/dist/fleetTaskDesc.js +39 -2
- package/dist/hitl/armedGateRegistry.js +11 -3
- package/dist/hitl/hitlBridge.d.ts +20 -0
- package/dist/hitl/hitlBridge.js +156 -12
- package/dist/hitl/parkResolver.d.ts +1 -0
- package/dist/hitl/parkResolver.js +22 -2
- package/dist/hitl/toolApprovalWire.d.ts +41 -6
- package/dist/hitl/toolApprovalWire.js +40 -6
- package/dist/request/taskRequest.js +11 -11
- package/dist/retryStatus.d.ts +38 -3
- package/dist/retryStatus.js +15 -5
- package/dist/toolResult.d.ts +8 -0
- package/dist/toolResult.js +15 -0
- package/docs/INTEGRATION-CLIENTS.md +16 -17
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HitlBridge, HitlSafetyError, findPendingForTask } from './hitlBridge.js';
|
|
1
|
+
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask } from './hitlBridge.js';
|
|
2
2
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
3
3
|
import { hostLog } from '../host.js';
|
|
4
4
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
@@ -179,7 +179,14 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal) {
|
|
|
179
179
|
// 这里 instanceof 窄化后再过 isGateFailureCode 白名单——不认得的码(将来 hitlBridge 那边
|
|
180
180
|
// 万一多加一种)一律降级成 undefined,退回文案兜底,不让开集腐蚀这个新判别位。
|
|
181
181
|
const code = e instanceof HitlSafetyError && isGateFailureCode(e.code) ? e.code : undefined;
|
|
182
|
-
return {
|
|
182
|
+
return {
|
|
183
|
+
kind: 'failed',
|
|
184
|
+
gatedCallId,
|
|
185
|
+
reason: `decide failed: ${String(e)}`,
|
|
186
|
+
...(code !== undefined ? { code } : {}),
|
|
187
|
+
// Inkglow-1085 P0a:ask 腿与 fs 腿同形 —— 瞬断耗尽给判别位,resolvePark 走重呈臂。
|
|
188
|
+
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
189
|
+
};
|
|
183
190
|
}
|
|
184
191
|
}
|
|
185
192
|
/** 台账里那批 gated `tool_start` 当拍 args 快照 → 决断腿要的 `toolCallId → args` 表(UNTRUSTED 原样搬运)。 */
|
|
@@ -247,6 +254,19 @@ export async function resolvePark(park, ctx) {
|
|
|
247
254
|
(led.decidedCount() > 0 ? ` [decided so far: ${led.decidedCount()}]` : ''));
|
|
248
255
|
return { kind: 'reattach' };
|
|
249
256
|
}
|
|
257
|
+
// Inkglow-1085 P0a —— **重呈臂**:decide 出站在瞬断类失败上重试一次仍未送达(两发都没拿到引擎的
|
|
258
|
+
// 语义答复)。此刻 run 仍 parked、pending 行仍可决 —— 合成 `hitl_unanswered` 把 turn 判死是三条
|
|
259
|
+
// 出路里唯一不可逆的那条,只配给「引擎答了且拒了」的语义失败。这里改走 reattach:durable 流对
|
|
260
|
+
// suspended run 必然把 park 帧再送一遍(#110 已解决重放同一条机械路径),于是**同一张卡重新交给
|
|
261
|
+
// 用户**(重呈的呈现就是卡本身,归端零新 UI);hop 预算照吃(每轮都要人再答一次,不会空转)。
|
|
262
|
+
// 引擎真死时失败也尽快显形:下一轮的 approvals.list / runs.events 对死引擎当场失败,walks 既有
|
|
263
|
+
// 诚实红(reason 是 approvals.list failed,不带 retryExhausted ⇒ 不再进本臂)。
|
|
264
|
+
if (outcome.kind === 'failed' && outcome.retryExhausted === true) {
|
|
265
|
+
const seq = led.lastSeq();
|
|
266
|
+
hostLog('debug', `liveHitlAskWire: decide transport retries exhausted (${outcome.reason}) — re-presenting the gate via ` +
|
|
267
|
+
`re-attach runs.events(${taskId})${seq ? ` from seq ${seq}` : ''} instead of failing the turn (the run is still parked and the pending row is still decidable)`);
|
|
268
|
+
return { kind: 'reattach' };
|
|
269
|
+
}
|
|
250
270
|
if (outcome.kind !== 'decided') {
|
|
251
271
|
hostLog('debug', `liveHitlAskWire: gate not decided (${outcome.kind}${'reason' in outcome ? `: ${outcome.reason}` : ''}) — fail-soft to suspended terminal`);
|
|
252
272
|
const events = [...led.flushHeld()]; // 回退:毒化帧照旧渲染(= 修复前的诚实红)
|
|
@@ -104,10 +104,16 @@ export type FsApprovalOutcome = {
|
|
|
104
104
|
} | {
|
|
105
105
|
kind: 'aborted';
|
|
106
106
|
gatedCallId?: string | undefined;
|
|
107
|
-
}
|
|
107
|
+
}
|
|
108
|
+
/** `retryExhausted`(Inkglow-1085 P0a,0.37.0):decide 出站在**瞬断类**失败上重试一次仍未送达
|
|
109
|
+
* (两发都没拿到引擎的语义答复)—— 在场即真。消费方(parkResolver)据此走**重呈臂**(re-attach
|
|
110
|
+
* 重放 park ⇒ 同一张卡重新交给用户),不合成 `hitl_unanswered` 判死 turn。缺席 = 语义类失败
|
|
111
|
+
* (引擎答了且拒了)或卡面失败,既有 fail-soft 语义逐字节不变。 */
|
|
112
|
+
| {
|
|
108
113
|
kind: 'failed';
|
|
109
114
|
gatedCallId?: string | undefined;
|
|
110
115
|
reason: string;
|
|
116
|
+
retryExhausted?: true;
|
|
111
117
|
};
|
|
112
118
|
/** 本桥消费的 wire 面(liveHitlAskWire 的 AskGateWireDeps 同形切片,mock 可注入)。 */
|
|
113
119
|
export interface FsApprovalWireDeps {
|
|
@@ -347,9 +353,18 @@ export interface ToolApprovalFrame {
|
|
|
347
353
|
* lane 的 ask 出此键(宿主自身 ask 的 sourceTaskId=sessionId 已在 server 侧收口不上帧)。
|
|
348
354
|
* ⚠️ 值=子代 sessionId 非 a… handle(fence 实测 2026-07-23)——不是 fleet 台账键。 */
|
|
349
355
|
sourceTaskId?: string;
|
|
350
|
-
/**
|
|
351
|
-
*
|
|
352
|
-
|
|
356
|
+
/**
|
|
357
|
+
* core 1.378 RB-39②([1550]):Runner 填充的只读显式判别键——委派子代 gate 的 ask 恒带,
|
|
358
|
+
* 受信 internals 事实不可伪造。在场即子代(优先于 sourceTaskId 权宜式)。
|
|
359
|
+
*
|
|
360
|
+
* 🔴 **字面 `true` 不是 `boolean`**(#307 双扫 S47,2026-08-19;SDK 7.1.0/7.2.0
|
|
361
|
+
* `dist/resources/tool-approvals.d.ts` 的 `fromSubagent?: true` 逐字)——与本 interface 上
|
|
362
|
+
* `governanceForced?: true` / `requiresRealApproval?: true` 同族的**在场即真**键:
|
|
363
|
+
* wire 上只有「在场」与「缺席」两态,`false` 根本不是合法取值。此前这里写 `boolean` 是本仓
|
|
364
|
+
* 自铸的宽形,把一个不存在的第三态写进了契约。
|
|
365
|
+
* 缺席 = **没有显式判别证据**(不是「明确不是子代」),读法见 {@link isFromSubagent}。
|
|
366
|
+
*/
|
|
367
|
+
fromSubagent?: true;
|
|
353
368
|
/** core 1.378 RB-39②:展示身份(UNTRUSTED-for-display,server redact 后上帧)——徽章名一手源。 */
|
|
354
369
|
sourceAgentName?: string;
|
|
355
370
|
/**
|
|
@@ -495,8 +510,28 @@ export interface ToolApprovalDelegation {
|
|
|
495
510
|
* 下面两个类型钉保证镜像与 interface 本身不可能漂移(少键/多键都是编译错)。
|
|
496
511
|
*/
|
|
497
512
|
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "delegation", "outcome"];
|
|
498
|
-
/**
|
|
499
|
-
*
|
|
513
|
+
/**
|
|
514
|
+
* 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
515
|
+
* (server 1.258 [1549]①3,旧代际兼容)。
|
|
516
|
+
*
|
|
517
|
+
* 🔴 **`=== true` 是在场判别,不是布尔求值**(#307 S47 复核结论,2026-08-19)。`fromSubagent`
|
|
518
|
+
* 的 wire 形是 `?: true`(见上方声明),所以运行期只可能是 `true` 或缺席;一个**显式 `false`**
|
|
519
|
+
* 只能来自注入面或不合契约的实现,它承载的信息是「这个载体不合契约」,**不是**「引擎明确判定不是
|
|
520
|
+
* 子代」。故这里刻意**不**把 `false` 当成否定证据:它与缺席同档 —— 落回 `sourceTaskId` 在场性
|
|
521
|
+
* 那条旧代际权宜臂。
|
|
522
|
+
*
|
|
523
|
+
* 为什么这个方向是对的(而不是「显式 false ⇒ 直接 return false」):
|
|
524
|
+
* · 本判据的**唯一消费面是展示归属**(`workerBadge` 徽章 + `delegation` 出处链,见下方卡口),
|
|
525
|
+
* 不参与任何放行/收窄决策 —— 两个方向的代价不对称:多一枚徽章只是噪声,少一枚徽章是把
|
|
526
|
+
* 「这是子代发起的」这条事实对用户藏起来;
|
|
527
|
+
* · `sourceTaskId` 本身就是 server 只对后台子代 lane 才出的键(见其声明),它在场是**独立的**
|
|
528
|
+
* 子代证据。让一个不合契约的 `false` 去否决一条独立成立的证据,等于让注入面拿到一个
|
|
529
|
+
* 「隐藏子代身份」的开关;
|
|
530
|
+
* · 与本文件 `governanceForced` 的「缺席 ≠ false」同族纪律:在场即真的键上,非 `true` 一律读作
|
|
531
|
+
* 「没有这条证据」,而不是「有一条相反的证据」。
|
|
532
|
+
* 反漂移:类型侧 `?: true` 已让**编译期**的显式 false 不可能构造;本臂守的是运行期(wire/注入面)
|
|
533
|
+
* 的越界载体,pure 门有对应负控(`fromSubagent:false` + sourceTaskId 在场 ⇒ 仍判子代)。
|
|
534
|
+
*/
|
|
500
535
|
export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
|
|
501
536
|
/** 三选卡决断 → respond 端点的 wire 枚举(server parseToolApprovalDecision)。 */
|
|
502
537
|
export type ToolApprovalRespondDecision = 'allow' | 'allow_session' | 'deny';
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
* 静默截断);真落行与否看 ack 的 `noteRecorded`(缺席 ≠ false)。
|
|
78
78
|
* 🔴 方向纪律:reason/note 只做归因,绝不参与裁决;缺席 ⇒ 现状字节不变。
|
|
79
79
|
*/
|
|
80
|
-
import { DEFAULT_DENY_REASON, HitlBridge, HitlSafetyError, denyReasonForWire, findPendingForTask, } from './hitlBridge.js';
|
|
80
|
+
import { DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, denyReasonForWire, findPendingForTask, } from './hitlBridge.js';
|
|
81
81
|
import { hostLog } from '../host.js';
|
|
82
82
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
83
83
|
import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
@@ -305,7 +305,10 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
305
305
|
// binding 不匹配意味着「人看见的那一行在他决断期间被换掉了」,自动重试等于替人对一件
|
|
306
306
|
// 他没看过的事按了 Yes。`no_pending` 同族(那一行已经没了,重试同样只会再失败一次)。
|
|
307
307
|
// 上抛给外层 catch ⇒ typed `failed` ⇒ 调用方走 fail-soft 诚实红,由人重新决断。
|
|
308
|
-
|
|
308
|
+
// Inkglow-1085 P0a:瞬断耗尽同样**不许**回退纯 approve —— 引擎此刻根本够不着,再补一发
|
|
309
|
+
// 纯 approve 只是再烧一轮超时,还把「传输断了」错标成「老 server 不识别 remember」。
|
|
310
|
+
// 上抛给外层 catch ⇒ retryExhausted 判别位 ⇒ parkResolver 走重呈臂。
|
|
311
|
+
if (e instanceof HitlSafetyError || e instanceof DecideTransportRetryExhaustedError)
|
|
309
312
|
throw e;
|
|
310
313
|
hostLog('debug', `liveToolApprovalWire: decide(approve+remember) failed (${String(e)}) — falling back to plain approve`);
|
|
311
314
|
}
|
|
@@ -314,7 +317,13 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
314
317
|
return { kind: 'decided', gatedCallId };
|
|
315
318
|
}
|
|
316
319
|
catch (e) {
|
|
317
|
-
return {
|
|
320
|
+
return {
|
|
321
|
+
kind: 'failed',
|
|
322
|
+
gatedCallId,
|
|
323
|
+
reason: `decide(approve) failed: ${String(e)}`,
|
|
324
|
+
// Inkglow-1085 P0a:瞬断耗尽的判别位(在场即真)—— 语义失败缺席,reason 字节不变。
|
|
325
|
+
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
326
|
+
};
|
|
318
327
|
}
|
|
319
328
|
case 'deny':
|
|
320
329
|
try {
|
|
@@ -326,7 +335,12 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
326
335
|
return { kind: 'decided', gatedCallId, denied: true };
|
|
327
336
|
}
|
|
328
337
|
catch (e) {
|
|
329
|
-
return {
|
|
338
|
+
return {
|
|
339
|
+
kind: 'failed',
|
|
340
|
+
gatedCallId,
|
|
341
|
+
reason: `decide(deny) failed: ${String(e)}`,
|
|
342
|
+
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
343
|
+
};
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
346
|
}
|
|
@@ -403,8 +417,28 @@ function isToolApprovalDelegation(v) {
|
|
|
403
417
|
return false;
|
|
404
418
|
return d.agentName === undefined || typeof d.agentName === 'string';
|
|
405
419
|
}
|
|
406
|
-
/**
|
|
407
|
-
*
|
|
420
|
+
/**
|
|
421
|
+
* 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
422
|
+
* (server 1.258 [1549]①3,旧代际兼容)。
|
|
423
|
+
*
|
|
424
|
+
* 🔴 **`=== true` 是在场判别,不是布尔求值**(#307 S47 复核结论,2026-08-19)。`fromSubagent`
|
|
425
|
+
* 的 wire 形是 `?: true`(见上方声明),所以运行期只可能是 `true` 或缺席;一个**显式 `false`**
|
|
426
|
+
* 只能来自注入面或不合契约的实现,它承载的信息是「这个载体不合契约」,**不是**「引擎明确判定不是
|
|
427
|
+
* 子代」。故这里刻意**不**把 `false` 当成否定证据:它与缺席同档 —— 落回 `sourceTaskId` 在场性
|
|
428
|
+
* 那条旧代际权宜臂。
|
|
429
|
+
*
|
|
430
|
+
* 为什么这个方向是对的(而不是「显式 false ⇒ 直接 return false」):
|
|
431
|
+
* · 本判据的**唯一消费面是展示归属**(`workerBadge` 徽章 + `delegation` 出处链,见下方卡口),
|
|
432
|
+
* 不参与任何放行/收窄决策 —— 两个方向的代价不对称:多一枚徽章只是噪声,少一枚徽章是把
|
|
433
|
+
* 「这是子代发起的」这条事实对用户藏起来;
|
|
434
|
+
* · `sourceTaskId` 本身就是 server 只对后台子代 lane 才出的键(见其声明),它在场是**独立的**
|
|
435
|
+
* 子代证据。让一个不合契约的 `false` 去否决一条独立成立的证据,等于让注入面拿到一个
|
|
436
|
+
* 「隐藏子代身份」的开关;
|
|
437
|
+
* · 与本文件 `governanceForced` 的「缺席 ≠ false」同族纪律:在场即真的键上,非 `true` 一律读作
|
|
438
|
+
* 「没有这条证据」,而不是「有一条相反的证据」。
|
|
439
|
+
* 反漂移:类型侧 `?: true` 已让**编译期**的显式 false 不可能构造;本臂守的是运行期(wire/注入面)
|
|
440
|
+
* 的越界载体,pure 门有对应负控(`fromSubagent:false` + sourceTaskId 在场 ⇒ 仍判子代)。
|
|
441
|
+
*/
|
|
408
442
|
export function isFromSubagent(frame) {
|
|
409
443
|
if (frame.fromSubagent === true)
|
|
410
444
|
return true;
|
|
@@ -59,7 +59,7 @@ export const REQUEST_FIELD_MATRIX = [
|
|
|
59
59
|
{ field: 'reasoningEffort', lanes: ['interactive'], live: false, why: '`/effort` 拨盘存在 AppState,print 无 AppState', gap: true },
|
|
60
60
|
{ field: 'model', lanes: ['interactive'], live: false, why: '`/model` 中途换模型读 toolUseContext.options.mainLoopModel;print 的模型走 MODEL_ID/--model 另一条路', gap: true },
|
|
61
61
|
{ field: 'images', lanes: ['interactive'], live: false, why: '贴图提交是交互动作,`-p` 的 stdin 没有图片块' },
|
|
62
|
-
// ⚠️ 四键一条(0.
|
|
62
|
+
// ⚠️ 四键一条(0.35.0 补第四键 `resumeAtMode`):它是 `rewindWireCaps` 的 `SeamRewindSpec` 真 wire 键,
|
|
63
63
|
// 且 `rewindSpecForMode` 的 `both` / `conversation` 两臂**恒返**它(排他截语义 —— 回到目标消息
|
|
64
64
|
// **之前**)。修前合写项只列三键 ⇒ 真 `/rewind` 还原 turn 上 `unregisteredRequestKeys` 会点名
|
|
65
65
|
// `resumeAtMode`,而那正是「本层不许有未登记键」这条判据的误报,会教端去白名单化真键。
|
|
@@ -129,7 +129,7 @@ const shapeTag = (v) => {
|
|
|
129
129
|
/**
|
|
130
130
|
* plain object 判别 —— 按 **prototype** 判,不按 `Object.prototype.toString` 的标签判。
|
|
131
131
|
*
|
|
132
|
-
* 🔴 标签判法是**假的**(0.
|
|
132
|
+
* 🔴 标签判法是**假的**(0.35.0 codex 对抗复审 [high] 采纳):`[object Object]` 对**普通类实例**同样
|
|
133
133
|
* 成立,而 `Symbol.toStringTag` 还能让任意载体自报这个标签。放它过去之后,下面的摊开用的是
|
|
134
134
|
* `Object.entries`(**只取自有可枚举键**)—— 于是一个把 `permissions.deny/ask` 挂在**原型 getter**
|
|
135
135
|
* 上的载体会摊出一个**空快照**,请求照发 = 权限静默变宽,正是本节要堵的那个 fail-open 形。
|
|
@@ -139,7 +139,7 @@ const shapeTag = (v) => {
|
|
|
139
139
|
* `Map` / `Date` / boxed 包装对象 / 数组一律拒 —— 它们要么内容不在自有键上,要么摊开就是垃圾键;
|
|
140
140
|
* 「上游产出坏了」比「悄悄发一个更宽的权限面」更该被人看见。
|
|
141
141
|
*
|
|
142
|
-
* 🔴 判据 **realm 无关**(0.
|
|
142
|
+
* 🔴 判据 **realm 无关**(0.35.0 codex 对抗复审第八轮 [medium] 采纳):不拿「**本** realm 的
|
|
143
143
|
* `Object.prototype`」做身份比较 —— iframe / `node:vm` / 另一个渲染进程里的对象字面量各有**自己的**
|
|
144
144
|
* `Object.prototype`,身份比较会把这些**完全合法、JSON 忠实**的载体误判成异形,在 web / desktop 宿主
|
|
145
145
|
* 上变成提交前的硬失败。
|
|
@@ -242,7 +242,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
242
242
|
}
|
|
243
243
|
return { value: items }; // 重建:原数组(可能是子类/带访问器/带覆盖方法)不出门
|
|
244
244
|
}
|
|
245
|
-
// 🔴 重建成**无原型**记录(0.
|
|
245
|
+
// 🔴 重建成**无原型**记录(0.35.0 codex 对抗复审第七轮 [high] 的可采半场):
|
|
246
246
|
// ① 忠实 —— 源本来就允许 `Object.create(null)` 字典,重建成 `{}` 等于把载体形换掉了;
|
|
247
247
|
// ② 少一条改写面 —— 无原型记录不会继承任何**事后**装到 `Object.prototype` 上的 `toJSON`。
|
|
248
248
|
// ⚠️ 但这只关掉了 record 那一半:重建出来的**数组**必须是真数组(`Array.isArray` / 序列化成
|
|
@@ -264,7 +264,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
264
264
|
// `Object.prototype` 的 setter 上:键整个丢掉、还顺手换了目标对象的原型。
|
|
265
265
|
Object.defineProperty(rec, k, { value: r.value, enumerable: true, writable: true, configurable: true });
|
|
266
266
|
}
|
|
267
|
-
// 🔴 判据的读法**一律排在捕获之后**(0.
|
|
267
|
+
// 🔴 判据的读法**一律排在捕获之后**(0.35.0 codex 对抗复审第十二轮 [high] 采纳):`toJSON` 走属性
|
|
268
268
|
// 查找、`isPlainRecord` 走 `getPrototypeOf`/`constructor` —— 这三种读法**都可以带副作用**
|
|
269
269
|
// (`getPrototypeOf` 陷阱在被问的那一刻把 `deny` 清空),而**原生 `JSON.stringify` 从不问原型**。
|
|
270
270
|
// 校在捕获之前 = 本层自己的读法成了丢内容的那一环(比被动撒谎更糟:那是我们引入的丢失面)。
|
|
@@ -281,7 +281,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
281
281
|
/**
|
|
282
282
|
* 快照 → 摊开成 `settings` 子键前的两道处理:**具名通道让位** + **畸形响亮拒**。
|
|
283
283
|
*
|
|
284
|
-
* ## ① 具名通道管着的键一律让位(0.
|
|
284
|
+
* ## ① 具名通道管着的键一律让位(0.35.0)
|
|
285
285
|
*
|
|
286
286
|
* 快照是**开放集 spread**(子键原样就是 wire 键),所以它天生是一条**第二通道**。0.34.0 只剥了
|
|
287
287
|
* 「车道异名」键(表里那行的 lanes 不含本车道),于是**两条车道都登记**的具名键剥不到 ——
|
|
@@ -296,7 +296,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
296
296
|
* 🔴 剥得**窄**:只剥表里点名的具名子键。快照**表外**的子键(permissions / env / model / …)
|
|
297
297
|
* 原样摊开 —— 那是开放集的全部意义;`outputStyle` 没有车道行(它是 live 兜底层的位)故不受影响。
|
|
298
298
|
*
|
|
299
|
-
* ## ② 畸形快照响亮拒(0.
|
|
299
|
+
* ## ② 畸形快照响亮拒(0.35.0,fail-closed)
|
|
300
300
|
*
|
|
301
301
|
* `undefined` / `null` = **合法缺席**(端没解析出快照;老写法拿 null 当空快照传),照旧降空对象。
|
|
302
302
|
* 其余非 plain-object 形(数组 / 原始值 / boxed 包装对象 / Map / **类实例**…)= **上游产出坏了**:
|
|
@@ -305,11 +305,11 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
305
305
|
* `TypeError` 带形状描述(合法载体的判据见 {@link isPlainRecord} —— 「摊得全」是它的全部理由)。
|
|
306
306
|
* (本包是发出去的 npm 公开面:JS 调用方与版本偏斜的宿主都到得了这里,型面拦不住。)
|
|
307
307
|
*
|
|
308
|
-
* ## ③ 权限面**子树**同样要摊得全(0.
|
|
308
|
+
* ## ③ 权限面**子树**同样要摊得全(0.35.0 codex 对抗复审第二轮 [high] 采纳)
|
|
309
309
|
*
|
|
310
310
|
* 只校最外层是不够的:`{ permissions: new Map([['deny',['Write']]]) }` 的外层是合法字面量,可
|
|
311
311
|
* `JSON.stringify` 把那只 Map 序列化成 `"permissions":{}` —— deny 规则**静默消失**,与 ② 要堵的
|
|
312
|
-
* 是同一个权限变宽形,只是深了一层。所以 `permissions` 子树递归过 {@link
|
|
312
|
+
* 是同一个权限变宽形,只是深了一层。所以 `permissions` 子树递归过 {@link materializeJsonFaithful}:
|
|
313
313
|
* plain record / 数组 / JSON 原始值才算摊得全,`Map`/`Set`/类实例/boxed/环一律拒。
|
|
314
314
|
*
|
|
315
315
|
* 🔴 **射程刻意只到 `permissions`**,不做整份请求体的深净化:那是「包级 wire 载荷 JSON-safe
|
|
@@ -335,7 +335,7 @@ const resolvedSnapshotForWire = (resolved) => {
|
|
|
335
335
|
// 当场物化成脱钩副本),原件的形状判据留到最后;校不过就整体拒,拒绝面一字未变。
|
|
336
336
|
const named = namedSettingsSubKeys();
|
|
337
337
|
const out = {};
|
|
338
|
-
// 🔴 **键先捕获、值逐个即读即处理**(0.
|
|
338
|
+
// 🔴 **键先捕获、值逐个即读即处理**(0.35.0 codex 对抗复审第六轮 [high]):`Object.entries(resolved)`
|
|
339
339
|
// 会把**所有兄弟键**的值先读齐 —— 于是一个 `env`/`model` 位上的 getter 能在 `permissions` 被
|
|
340
340
|
// 重建**之前**把它的 deny 数组清空(拿到的是同一个引用)。这与内层那条(见
|
|
341
341
|
// {@link materializeJsonFaithful} 第五条)是同一个病、只是高一层:两层都必须按 stringify 的
|
|
@@ -410,7 +410,7 @@ export function buildTaskRequest(input, lane) {
|
|
|
410
410
|
// resolved 两车道都进表(#292 P1)——它是**开放集 spread**(子键即 wire 键),所以走
|
|
411
411
|
// `laneHas + live` 而不是 `on()`:`on()` 判的是「这个键值非空」,而这里要判的是「这份快照要不要
|
|
412
412
|
// 摊开」。
|
|
413
|
-
// 🔴 具名通道**赢过快照**这件事不靠下面的合并序(0.
|
|
413
|
+
// 🔴 具名通道**赢过快照**这件事不靠下面的合并序(0.35.0):合并序只在具名通道**有值**时管用,
|
|
414
414
|
// 而具名门否决时的表现恰恰是**没值**(端不给这个键)。让位改由 `resolvedSnapshotForWire`
|
|
415
415
|
// 做**结构剥离** —— 具名通道管着的子键快照一概不产,顺序此后只是可读性,不再是安全依据。
|
|
416
416
|
const s = input.settings ?? {};
|
package/dist/retryStatus.d.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* recovered → null → 覆盖层摘掉(重试**成功**,不是错误 —— 见下)
|
|
10
10
|
* gave_up → error+terminal → '✻ <detail>'(重试用尽的终态;与 recovered 反向。**打 terminal 位**,
|
|
11
11
|
* 渲染面据此不得再接「· Retrying in Ns」——已经没有下一次了)
|
|
12
|
-
* 绝不捏造 attempt 计数——只用引擎真给的 phase / detail / retryInSec / retryInMs / attempt /
|
|
12
|
+
* 绝不捏造 attempt 计数——只用引擎真给的 phase / detail / retryInSec / retryInMs / attempt /
|
|
13
|
+
* maxRetries / errClass(core 5.43.0 起七键)。
|
|
13
14
|
*
|
|
14
15
|
* 🔴 员数与字段补全(2026-08-08,#3004 跟修批)。此前本文件只列 4 相 + 3 字段,而引擎侧
|
|
15
16
|
* (core `BrainStatusPhase` / `BrainStatus`,dist/core/types.d.ts)是 **6 相 + 6 字段**,server 两腿的
|
|
@@ -30,11 +31,15 @@ export type RetryStatus =
|
|
|
30
31
|
deadline: number;
|
|
31
32
|
attempt?: number;
|
|
32
33
|
maxRetries?: number;
|
|
34
|
+
/** 见 {@link BrainStatusPayload.errClass}(引擎给了才在场;本层只透传,措辞是壳半场)。 */
|
|
35
|
+
errClass?: BrainRetryErrClass | (string & {});
|
|
33
36
|
} | {
|
|
34
37
|
kind: 'error';
|
|
35
38
|
deadline: number;
|
|
36
39
|
attempt?: number;
|
|
37
40
|
maxRetries?: number;
|
|
41
|
+
/** 见 {@link BrainStatusPayload.errClass}(引擎给了才在场;本层只透传,措辞是壳半场)。 */
|
|
42
|
+
errClass?: BrainRetryErrClass | (string & {});
|
|
38
43
|
/**
|
|
39
44
|
* 🔴 **终态位**(2026-08-08 对抗复审命中):`true` ⇔ 引擎**不会再重试了**(`gave_up` 相)。
|
|
40
45
|
* 缺席 = 仍在重试循环里(retrying / rate_limited / circuit_open)。
|
|
@@ -65,7 +70,23 @@ export type RetryStatus =
|
|
|
65
70
|
*/
|
|
66
71
|
export declare const BRAIN_STATUS_PHASES: readonly ["rate_limited", "retrying", "reconnecting", "circuit_open", "recovered", "gave_up"];
|
|
67
72
|
export type BrainStatusPhase = (typeof BRAIN_STATUS_PHASES)[number];
|
|
68
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* core `BrainRetryErrClass`(5.43.0)的**类型面镜像** —— 一次重试等待的**原因分桶**,
|
|
75
|
+
* provider 中立(绝不是 HTTP 状态码 / syscall code / provider 分类法;那些不许过这条通道):
|
|
76
|
+
* · `connect_refused` 目标本身给了确定否定(该地址没人 accept / 名字无地址)—— 短梯队服务的那一类;
|
|
77
|
+
* · `transport` 其余传输层失败(connect 超时 / reset / 流中断 / 流停滞)—— 全梯队;
|
|
78
|
+
* · `rate_limit` provider 要求放慢;
|
|
79
|
+
* · `server` provider 自报自己这边出错;
|
|
80
|
+
* · `http` 状态谓词判终态、但 provider 自己的显式重试裁定说重试;
|
|
81
|
+
* · `output_cap` 根本不是连接失败:provider 报上下文超限后按更低 output cap 重发(无退避)。
|
|
82
|
+
*
|
|
83
|
+
* 🔴 **刻意没有运行期值镜像**(与 {@link BRAIN_STATUS_PHASES} 不同):本包对 errClass **零分支**
|
|
84
|
+
* (纯透传),少一个成员没有任何行为后果 —— 开集读会把不认得的桶原样带过去。而 phase 是有 switch
|
|
85
|
+
* 分支的,少一相会落 default 臂被渲成错话,所以那张表才需要运行期镜像 + engine-vocab 等值门。
|
|
86
|
+
* 判据锚在「真正决定结果的量」上:决定结果的是有没有分支,不是有没有一张表。
|
|
87
|
+
*/
|
|
88
|
+
export type BrainRetryErrClass = 'connect_refused' | 'transport' | 'rate_limit' | 'server' | 'http' | 'output_cap';
|
|
89
|
+
/** wire 上 `status` 臂的载荷(= core `BrainStatus`;server 两腿白名单原样转发这 7 键)。 */
|
|
69
90
|
export interface BrainStatusPayload {
|
|
70
91
|
/** 闭集 + `(string & {})`:未知相仍可携带(开集读),不必先改类型再解析。 */
|
|
71
92
|
phase: BrainStatusPhase | (string & {});
|
|
@@ -79,13 +100,27 @@ export interface BrainStatusPayload {
|
|
|
79
100
|
attempt?: number;
|
|
80
101
|
/** 引擎这一轮的重试上限。与 `attempt` 一起才能渲「2/5」。 */
|
|
81
102
|
maxRetries?: number;
|
|
103
|
+
/**
|
|
104
|
+
* core 5.43.0(#307 双扫 S44,2026-08-19;ADDITIVE)——**这次等待的原因分桶**
|
|
105
|
+
* (core `BrainRetryErrClass`,provider 中立闭集)。与 `phase`(引擎正在**做什么**)互补:
|
|
106
|
+
* 本键说的是**为什么**。只在**重试等待帧**上在场且引擎分得出类;`recovered`/`gave_up` 两个终态帧
|
|
107
|
+
* 与 `circuit_open`(本地快失败,不是观测到的失败)上刻意缺席。
|
|
108
|
+
*
|
|
109
|
+
* 🔴 **闭集 + `(string & {})`,读时按开集处理**,与 `phase` 同款纪律:一个部署可以在自己的通道上
|
|
110
|
+
* 冒出别的桶,认得的照走、认不得的原样带过去 —— 绝不因为「不认得」就把这次等待的原因抹成缺席
|
|
111
|
+
* (那是把引擎真给的量在本层剥掉,#3004 那批修的正是这一类)。
|
|
112
|
+
* 缺席 = 引擎没分类,**不得**被渲成某个默认桶。
|
|
113
|
+
*
|
|
114
|
+
* 分工:本包只负责让这个事实到得了壳(投影/透传保真),措辞与是否上屏是壳半场。
|
|
115
|
+
*/
|
|
116
|
+
errClass?: BrainRetryErrClass | (string & {});
|
|
82
117
|
}
|
|
83
118
|
/**
|
|
84
119
|
* 上面那个 interface 的**运行期键镜像**(照 `TOOL_APPROVAL_FRAME_KEYS_MIRROR` 先例):
|
|
85
120
|
* engine-vocab 门 G2-c 拿它与 core `BrainStatus` 的键集逐元素比 ⇒ 引擎 additive 增键当天红。
|
|
86
121
|
* 下面两个类型钉保证镜像与 interface 之间不可能漂移(少键/多键都是编译错)。
|
|
87
122
|
*/
|
|
88
|
-
export declare const BRAIN_STATUS_PAYLOAD_KEYS: readonly ["phase", "detail", "retryInSec", "retryInMs", "attempt", "maxRetries"];
|
|
123
|
+
export declare const BRAIN_STATUS_PAYLOAD_KEYS: readonly ["phase", "detail", "retryInSec", "retryInMs", "attempt", "maxRetries", "errClass"];
|
|
89
124
|
/**
|
|
90
125
|
* BrainStatus 载荷 → spinner 行状态。
|
|
91
126
|
*
|
package/dist/retryStatus.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* recovered → null → 覆盖层摘掉(重试**成功**,不是错误 —— 见下)
|
|
10
10
|
* gave_up → error+terminal → '✻ <detail>'(重试用尽的终态;与 recovered 反向。**打 terminal 位**,
|
|
11
11
|
* 渲染面据此不得再接「· Retrying in Ns」——已经没有下一次了)
|
|
12
|
-
* 绝不捏造 attempt 计数——只用引擎真给的 phase / detail / retryInSec / retryInMs / attempt /
|
|
12
|
+
* 绝不捏造 attempt 计数——只用引擎真给的 phase / detail / retryInSec / retryInMs / attempt /
|
|
13
|
+
* maxRetries / errClass(core 5.43.0 起七键)。
|
|
13
14
|
*
|
|
14
15
|
* 🔴 员数与字段补全(2026-08-08,#3004 跟修批)。此前本文件只列 4 相 + 3 字段,而引擎侧
|
|
15
16
|
* (core `BrainStatusPhase` / `BrainStatus`,dist/core/types.d.ts)是 **6 相 + 6 字段**,server 两腿的
|
|
@@ -46,6 +47,8 @@ export const BRAIN_STATUS_PAYLOAD_KEYS = [
|
|
|
46
47
|
'retryInMs',
|
|
47
48
|
'attempt',
|
|
48
49
|
'maxRetries',
|
|
50
|
+
// core 5.43.0 跟车一键(#307 双扫 S44,2026-08-19):engine-vocab G2-c 对实装 core 逐键对账。
|
|
51
|
+
'errClass',
|
|
49
52
|
];
|
|
50
53
|
const _brainStatusKeyPin = [true, true];
|
|
51
54
|
void _brainStatusKeyPin;
|
|
@@ -64,19 +67,26 @@ export function mapBrainStatusToRetry(p, nowMs) {
|
|
|
64
67
|
...(typeof p.attempt === 'number' ? { attempt: p.attempt } : {}),
|
|
65
68
|
...(typeof p.maxRetries === 'number' ? { maxRetries: p.maxRetries } : {}),
|
|
66
69
|
};
|
|
70
|
+
/**
|
|
71
|
+
* 原因桶(#307 S44):引擎真给的**非空串**才落位。缺席 / 空串 / 非串 ⇒ 键不 stamp
|
|
72
|
+
* ——「不知道为什么等」渲成缺席,绝不折成某个默认桶。开集:不认得的桶原样带过去
|
|
73
|
+
* (本层零分支,认不得也没有可落错的臂)。
|
|
74
|
+
*/
|
|
75
|
+
const cause = typeof p.errClass === 'string' && p.errClass.length > 0 ? { errClass: p.errClass } : {};
|
|
67
76
|
switch (p.phase) {
|
|
68
77
|
// 🔴 引擎直报「恢复」:摘行。绝不落 error 臂 —— 那是把成功渲成失败。
|
|
69
78
|
case 'recovered':
|
|
70
79
|
return null;
|
|
71
80
|
case 'reconnecting':
|
|
72
|
-
return { kind: 'stalled', deadline, ...counts };
|
|
81
|
+
return { kind: 'stalled', deadline, ...counts, ...cause };
|
|
73
82
|
case 'rate_limited':
|
|
74
|
-
return { kind: 'error', deadline, ...counts, error: { formatted: '', rateLimits: {} } };
|
|
83
|
+
return { kind: 'error', deadline, ...counts, ...cause, error: { formatted: '', rateLimits: {} } };
|
|
75
84
|
case 'circuit_open':
|
|
76
85
|
return {
|
|
77
86
|
kind: 'error',
|
|
78
87
|
deadline,
|
|
79
88
|
...counts,
|
|
89
|
+
...cause,
|
|
80
90
|
error: { formatted: p.detail ?? 'Service temporarily unavailable', isNetworkDown: true },
|
|
81
91
|
};
|
|
82
92
|
// 重试用尽:仍是错误行(与 recovered 反向,绝不许一起归成「结束了 ⇒ 清行」),但**打终态位** ——
|
|
@@ -84,9 +94,9 @@ export function mapBrainStatusToRetry(p, nowMs) {
|
|
|
84
94
|
// retrying'),既没有计数也没有 retryIn*,所以「不会再重试了」这件事**只能**由本位表达;
|
|
85
95
|
// 靠 attempt===maxRetries 去推是错的(供给方根本不发那两位)。
|
|
86
96
|
case 'gave_up':
|
|
87
|
-
return { kind: 'error', deadline, terminal: true, ...counts, error: { formatted: p.detail ?? '' } };
|
|
97
|
+
return { kind: 'error', deadline, terminal: true, ...counts, ...cause, error: { formatted: p.detail ?? '' } };
|
|
88
98
|
case 'retrying':
|
|
89
99
|
default:
|
|
90
|
-
return { kind: 'error', deadline, ...counts, error: { formatted: '' } };
|
|
100
|
+
return { kind: 'error', deadline, ...counts, ...cause, error: { formatted: '' } };
|
|
91
101
|
}
|
|
92
102
|
}
|
package/dist/toolResult.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ import { type ModelFallbackReason } from './engineErrorCodes.js';
|
|
|
8
8
|
* 反过来,一个 `{type:'something-else'}` 或裸对象**不算** structured 在场 —— 宁可回落到正则,
|
|
9
9
|
* 也不许把「有个对象」当成「引擎发了结构化」(判据锚在决定结果的量上:决定的是「该不该信正则」)。
|
|
10
10
|
* ⚠️ 与设计稿的口径差:任务书写「29 项」,[1840] 清单逐条数是 **30 项**(见交接报告「设计稿错漏」)。
|
|
11
|
+
*
|
|
12
|
+
* 🔴 **本表是 core `CC_DETAIL_TYPES` 的镜像,不是本包自铸的词表**(core
|
|
13
|
+
* `dist/core/runner/tool-output-projection.js`)。等值由 `scripts/run-engine-vocab-floor-test.mjs` ⑤ 段
|
|
14
|
+
* 对**实装 core** 双向钉(漏一词 / 多一词都红),对账物 = 本仓 devDep `@sema-agent/core`。
|
|
15
|
+
* ⇒ **core 提货窗必须跟车对表**:每次抬 devDep core 版本,先跑 engine-vocab 门看这张表红不红,
|
|
16
|
+
* 红了按 core 那一版的铸点逐词补/删,别改门去迁就表。历史上两次恒绿(5.10 前停 devDep 5.1、
|
|
17
|
+
* 5.43 前停 devDep 5.20)的病根都不在表本身,在**对账物没跟着抬** —— 表漏一词的实际后果是
|
|
18
|
+
* 那一类卡永远退回正则解模型面文本,而任何一层都不会响。
|
|
11
19
|
*/
|
|
12
20
|
export declare const STRUCTURED_DETAIL_TYPES: ReadonlySet<string>;
|
|
13
21
|
/** structured 在场判别:顶层 `type` ∈ 白名单 ⇒ 返回该 type,否则 undefined(= 不在场)。 */
|
package/dist/toolResult.js
CHANGED
|
@@ -39,6 +39,14 @@ import { asModelFallbackReason } from './engineErrorCodes.js';
|
|
|
39
39
|
* 反过来,一个 `{type:'something-else'}` 或裸对象**不算** structured 在场 —— 宁可回落到正则,
|
|
40
40
|
* 也不许把「有个对象」当成「引擎发了结构化」(判据锚在决定结果的量上:决定的是「该不该信正则」)。
|
|
41
41
|
* ⚠️ 与设计稿的口径差:任务书写「29 项」,[1840] 清单逐条数是 **30 项**(见交接报告「设计稿错漏」)。
|
|
42
|
+
*
|
|
43
|
+
* 🔴 **本表是 core `CC_DETAIL_TYPES` 的镜像,不是本包自铸的词表**(core
|
|
44
|
+
* `dist/core/runner/tool-output-projection.js`)。等值由 `scripts/run-engine-vocab-floor-test.mjs` ⑤ 段
|
|
45
|
+
* 对**实装 core** 双向钉(漏一词 / 多一词都红),对账物 = 本仓 devDep `@sema-agent/core`。
|
|
46
|
+
* ⇒ **core 提货窗必须跟车对表**:每次抬 devDep core 版本,先跑 engine-vocab 门看这张表红不红,
|
|
47
|
+
* 红了按 core 那一版的铸点逐词补/删,别改门去迁就表。历史上两次恒绿(5.10 前停 devDep 5.1、
|
|
48
|
+
* 5.43 前停 devDep 5.20)的病根都不在表本身,在**对账物没跟着抬** —— 表漏一词的实际后果是
|
|
49
|
+
* 那一类卡永远退回正则解模型面文本,而任何一层都不会响。
|
|
42
50
|
*/
|
|
43
51
|
export const STRUCTURED_DETAIL_TYPES = new Set([
|
|
44
52
|
'edit',
|
|
@@ -102,6 +110,13 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
|
|
|
102
110
|
// `path_not_in_root`/`readonly_out_of_root` 同族(参数拒绝的结构化错误卡)。
|
|
103
111
|
'ask-question',
|
|
104
112
|
'bash_invalid_timeout',
|
|
113
|
+
// ── core 5.43.0 跟车一词(#307 双扫 S43,2026-08-19;engine-vocab 等值门对 5.43+ 实装物直证)──
|
|
114
|
+
// `read_path_denied` = fs 安全层拒读的结构化错误卡(core `dist/tools/fs/safety.js` 的
|
|
115
|
+
// `return { type: "read_path_denied", … }` 铸点),与 `path_not_in_root`(shell 面)/
|
|
116
|
+
// `readonly_out_of_root`(fs 写面)/ `bash_invalid_timeout`(参数面)同属「拒绝理由结构化」一族。
|
|
117
|
+
// 病根仍是对账物代际差:门锚本仓 devDep core,devDep 停 5.20.0 时该词还没铸 ⇒ 门恒绿。
|
|
118
|
+
// 修 = devDep 升 ^5.43.0(门当场翻红显出漂移)+ 补词,与 A-004.5 那批同一条路。
|
|
119
|
+
'read_path_denied',
|
|
105
120
|
]);
|
|
106
121
|
// 🔴 同批**删三词**(core 5.10.0 BREAKING「幽灵卡」清仓):`multiedit` / `memory-saved` /
|
|
107
122
|
// `memory-recall` —— 全树零铸点(MultiEdit/批量重放铸的是 `type:"edit"` 带 `edits[]`;core
|
|
@@ -15,23 +15,22 @@
|
|
|
15
15
|
|
|
16
16
|
## §0 版本锚与重扫纪律
|
|
17
17
|
|
|
18
|
-
### 0a. 版本锚(2026-08-
|
|
18
|
+
### 0a. 版本锚(2026-08-19)
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.
|
|
22
|
+
| 本包 | `@sema-agent/client-core` **0.37.0** | `package.json` `version` |
|
|
23
23
|
| peer:wire 契约 | `@sema-agent/sdk` **>=7.1.0**(value-level,非 type-only) | `package.json` `peerDependencies` |
|
|
24
24
|
| peer:会话词汇表 | `@sema-agent/agent-types` **>=0.2.0**(type-only,零运行时) | 同上 |
|
|
25
25
|
| runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
|
|
26
|
-
| 公开导出面 | **
|
|
26
|
+
| 公开导出面 | **753** 个运行期符号(+ 39 个测试钩;= npm `0.37.0` 的值;`0.36.0` 是 **749**,见 `CHANGELOG.md`) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
|
|
27
27
|
| 常驻门 | 以 `scripts/gates-manifest.json` 的 `suites` 长度为准(**本档不抄这个数**) | `scripts/gates-manifest.json`;`npm test` 的名单等值门与它逐名对账 |
|
|
28
28
|
| 沿革档 | 0.29.0 起建 `CHANGELOG.md`;更早批次记账在 `src/index.ts` 文件头 + `docs/REFACTOR-LEDGER.md` | — |
|
|
29
29
|
|
|
30
|
-
⚠️
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
对表**,不要按本表 —— 本表的组合在 npm 上今天还不存在。
|
|
30
|
+
⚠️ 0.37.0 已于 2026-08-19 发布,本表与 npm 最新版重新对齐(工作树 = npm)。装旧版(≤0.36.0)的
|
|
31
|
+
端注意:0.37.0 新增的四个 additive 导出(`invalidateEngineCaps` 两参形、`escapeDisplayControlChars`、
|
|
32
|
+
`DecideTransportRetryExhaustedError`、`clearRunningChoiceOffer`)在旧版上按名 import 会**在 ESM
|
|
33
|
+
实例化当场炸**(具名导出不存在)—— 提货前先抬依赖。旧版对表以 `CHANGELOG.md` 对应版本段为准。
|
|
35
34
|
|
|
36
35
|
🔴 **本表里仍然手抄的数字都有门看着**(#252,2026-08-14):`scripts/run-integration-doc-freshness-test.mjs`
|
|
37
36
|
① 段把 638 / 32 / §2b 十六域名数之和 / 191 / 4 / 33 逐个对 `public-export-baseline.json` 算出来的值,
|
|
@@ -100,7 +99,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
100
99
|
|
|
101
100
|
## §2 公共导出面地图(按域)
|
|
102
101
|
|
|
103
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
102
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**753** 项)。
|
|
104
103
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
105
104
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
106
105
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -110,7 +109,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
110
109
|
|
|
111
110
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
112
111
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
113
|
-
实测:
|
|
112
|
+
实测:753 项 **100% 是运行期导出,零 type-only**。
|
|
114
113
|
|
|
115
114
|
**推论(端必须知道)**:
|
|
116
115
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -119,20 +118,20 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
119
118
|
端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
|
|
120
119
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
(矩阵、键集、env 名、锚串)而非可调用物;**
|
|
124
|
-
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError`);
|
|
121
|
+
753 项的内部构成(帮助端估读表大小):**221** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
122
|
+
(矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
|
|
123
|
+
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
|
|
125
124
|
**39** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
126
125
|
|
|
127
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
126
|
+
### 2b. 域图(16 域,逐域计数之和 = 753)
|
|
128
127
|
|
|
129
128
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
130
129
|
|---|---|---|---|---|---|
|
|
131
130
|
| 1 | **适配内核(下行主链)** | 33 | `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` | 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts` |
|
|
132
131
|
| 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
|
|
133
|
-
| 3 | **HITL 决断卡链**(§4/§5 主战场) |
|
|
132
|
+
| 3 | **HITL 决断卡链**(§4/§5 主战场) | 120 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) · `DecideTransportRetryExhaustedError`(Inkglow-1085 P0a:decide 出站瞬断重试耗尽的 typed 判别 —— HitlBridge 内建单次退避重试,耗尽走重呈臂不判死 turn;端一般只消费行为,不需要 instanceof) | suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧) |
|
|
134
133
|
| 4 | **子代 wire + 面板侧信道台账** | 82 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent`(0.32.0 未发布 #280 件A:additive 第三参 `childTaskId` —— 端有行上下文时**应当**传,传了就走「台账优先 / 缺席即诚实缺席 + `noteBgOwnerAbsence` 留痕」的 Q3 口径,与 tail·taskOutput·subagentOutput 三腿同姿势、与孪生 resume 腿共用同一个 `resolveOwnerRunId` 判据;**不传**则逐字维持旧行为=回落在飞 run)· `resumeSettledSubagent` + `resolveSubagentResumeContext` + `resolveOwnerRunId` + `classifySubagentResumeFailure` + `subagentResumeAvailable`(#242 批 2 A-028.7:resume 判定半场上收,与 steer 孪生同居;取址三态 = 台账有行用行值 / 指名了行但台账缺席则**诚实缺席绝不回落在飞 run** / 没指名行才回落。出路文案归端)· `recordSubagentOwnerFromProgress` + `getBgParentRunOwner`(A-028.6:「子代 → 宿主 run」**单表**,宿主 run 必须由持 stream-local 值的调用方显式传入,包内绝不从 `activeEngineRunId()` 推断)· `noteBgOwnerAbsence`(#242 批 3 [4000] Q3=B:tail/taskOutput·taskStop/subagentOutput 三腿台账缺席即诚实缺席**绝不回落在飞 run**,缺席 warn 留痕每 (腿,taskId) 一条)· `clearBgTerminalFacts`(#242 批 3 扫码修:复活=新周期,旧周期终态事实作废——fleetLedger 复活两腿按尾段清账,factsAccepted 方向核不再拿上周期终态当先例)· `auditRetainWithoutWake`([4000] Q5:引擎宣示 `subagentResume` + 本端在付 `retainSubagentSessions` + 端未实现 `wakeSubagent` ⇒ 响亮一条;`CLIENT_VERBS.wakeSubagent` 维持 fail-soft)· `subscribeSubagentContent` · `subscribeEngineAgentPanel` · `publishQuestionFrame` / `respondToQuestion` | 驱动与观测委派子代;经 module 级台账喂活体 agent/task 面板。全部**能力位 gate**(§5b) | `src/subagent/*.ts`、`src/subagentContentStore.ts`、`src/engineAgentPanelStore.ts`、`src/engineInlineTaskStats.ts`、`src/engineToolLabelStore.ts`、`src/liveQuestionStore.ts` |
|
|
135
|
-
| 5 | **fleet 投影** |
|
|
134
|
+
| 5 | **fleet 投影** | 44 | `createFleetLedger` · `projectTasks` · `projectWorkflows` · `projectFleetAgentRows` · `readEngineActiveBgTasks` · `FLEET_TASK_VIEW_KEYS` · `escapeDisplayControlChars`(不可见字符可见化,行标签/描述消毒的共享底座) | 老 `fleetClient` 那一刀的成品:**帧体归库、连接归端** —— 端持 SSE 连接,库做行投影 + 保留台账 | `src/fleet/fleetProjection.ts`、`src/fleet/fleetLedger.ts`、`src/fleetAgentPanelProjection.ts`、`src/fleetTaskDesc.ts` |
|
|
136
135
|
| 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
|
|
137
136
|
| 7 | **通知与 outstanding 台账** | 37 | `installNotificationQueuePort` · `normalizeTaskNotification` · `taskNotificationDedupKeyFromWire` · `registerOutstandingBgTask` / `registerOutstandingWorkflowRun` · `notificationQueuePortMisses` · `subscribeOutstandingWorkflows` · `outstandingDeliverableWorkflowCount` | `task_notification` 归一 + 去重 + 投递进宿主命令队列的**一把闸**;`outstandingDeliverableWorkflowCount()` 是 headless `-p` 的**退出门** | `src/notifications.ts`(11 个 module 台账) |
|
|
138
137
|
| 8 | **工具结果卡** | 25 | `structuredToToolUseResult` · `readAsyncLaunchedAgentReceipt` · `wireOutputToBody` · `parseModelFacingBash` · `getPatchFromContents` · `toolEndResultToUserFrame` · `flattenToolOutput` | 铸端要渲的 `tool_result` 卡体,含客户端 diff hunk(唯一 runtime dep 的用处) | `src/toolResult.ts`、`src/printToolResultFrame.ts`、`src/diff/patch.ts` |
|
|
@@ -142,7 +141,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
142
141
|
| 12 | **workflow 与后台工作视图** | 19 | `projectWorkflowRun` · `createLiveWorkflowSource` · `ensureWorkflowActivityLedger` · `readWorkflowActivityLedger` · `stopWorkflowActivityLedger` · `resetWorkflowActivityLedgers` · `createBackgroundView` · `projectBackgroundView` · `recordWorkflowAgentTaskId` · `agentDisplayStatus` | 活过一个 turn 的长任务读面:workflow run + 跨 session 后台任务归一表(`assistant.tasks` 与 fleet SSE **两源独立降级**) | `src/workflow.ts`、`src/workflowClient.ts`、`src/workflowMonitor.ts`、`src/agentSession/backgroundView.ts`(+ 纯类型 `src/agentSession/contract.ts`) |
|
|
143
142
|
| 13 | **座位 IPC 契约** | 33 | `LOCAL_SESSIONS_SPEC` · `SEAT_METHOD_NAMES` · `SEAT_EVENT_TYPES` · `isLocalSessionEvent` · `isToolPermissionRequest` · `toolPermissionRequestId` · `SEAT_VALIDATOR_KEY_COVERAGE` | desktop↔web 座位 IPC 契约的**单一真源**(此前两边各一份、名字零重合 ⇒ 编译器永远不会告诉你它们漂了)。🔴 加 verb 忘了加 `LOCAL_SESSIONS_SPEC` **不报错**:preload 不注册 channel、渲染端读到 `undefined` | `src/seatContract.ts`(**零 import**,纯类型 + 常量 + 纯谓词) |
|
|
144
143
|
| 14 | **宿主端口与会话槽** | 26 | `installHost` · `installHostFor` · `hostPortMisses(For)` · `DEFAULT_SESSION_KEY` · `hostEnv` · `unrefTimer` · `parseLocaleTag` / `pickUiLanguage`(#244 F4 族D A-028.20:locale tag 手术单源 + UI 语言判定;与 `resolveRegionHint` 双出口成文 —— 语言偏好域 en/zh ≠ 地址可达域 cn/intl/unknown,`zh-Hant` 前者 zh 后者 intl 是设计)· `engineNamespaceKeyFor` / `mergeSessionMapRecord` / `mergeEngineEntry`(A-028.12:会话 id 映射单一键形 + merge 判定;存储经 `SessionMapStorePort` 归端 —— cli 文件锁/原子写,web localStorage)| 进程/端级装配层(settings/fs/queue/timers/session/log/probe),与 per-turn 的 `AdapterContext` **分层**。头注的判定规则:**这个能力每 turn 都会变吗?** 会 ⇒ `ctx`;不会 ⇒ `installHost` | `src/host.ts`、`src/hostEnv.ts`、`src/sessionSlot.ts`、`src/unrefTimer.ts`、`src/env/{localeGeo,localeTag,uiLanguage}.ts`、`src/sessionMap.ts` |
|
|
145
|
-
| 15 | **控制面与传输** |
|
|
144
|
+
| 15 | **控制面与传输** | 72 | `ControlRouter`(+ `ControlSafetyError`)· `makeEngineWireClient` + `resolveWireAuth` · `installEngineWireTarget` · `diagnoseSseIdleTear` / `isSseIdleError` · `attemptActiveRunSelfHeal` + `activeRunBusySignal` + `activeRunSelfHealRow` / `activeRunBusyHeadlessRow` · `kickEngineCapsProbe` / `engineCapTrue` / `invalidateEngineCaps(baseUrl, probe?)`(#307 S25:引擎温切后的 caps 生产失效口 —— kick 自带幂等闸,同 baseUrl 重启后不显式失效就永远读到旧引擎那一版能力位;调用方 = 壳的 respawn/restartEngine。🔴 **推荐两参形**:第二参给替代探测则「推进代际 + 注册新探测」在同一同步块内完成,失效与下一次 kick 之间那个「等待者读到未判」的窗按构造不存在;单参形保留给「只丢缓存、这一刻没有替代探测」的调用方,那种情形下读到未判是诚实结局) · `mapBrainStatusToRetry` · `waitForClaimRelease` + `CLAIM_RELEASED_STATES` / `CLAIM_HELD_STATES` · `atMostOnceFailureClass` / `readSteerDelivery` · `clearRunningChoiceOffer`(Inkglow-1085 P0b①:「Do nothing」登记的清口 —— 端的「重新打开操作菜单」入口;登记在场时 attemptActiveRunSelfHeal 不整卡重弹,not-parked 结局带 `alreadyOffered: true` 判别位,端据此降级渲一行)· `INTERACTIVE_WAY_OUT`(默认出路串单源)· `normalizeWirePrincipal`(A-028.10:principal 在场性 trim 原语 —— 全空白=缺席不发头,engineWireTarget 两臂/makeEngineWireClient/壳 livePrincipal 同尺)· `classifyTurnWireError` / `isWireTransportError` / `isPreStreamDrainingReject` / `isResumeAtRejection` / `drainingRetryDelayMs` / `scenarioDenyFromError` + `WIRE_NETWORK_ERROR_PATTERN`(A-028.11/.13:turn 错误分型判定半场,人话文案与渲染归端) | 上行通道的**监管**半场(submit / steer / kill / 队列命令定序)+ 传输构造、caps 探测、SSE 断流分诊、**409 active-run 自愈** | `src/controlRouter.ts`、`steering.ts`、`sseIdleTriage.ts`、`retryStatus.ts`、`diagnostics.ts`、`engineWireSdk.ts`、`engineWireTarget.ts`、`src/principalWire.ts`、`src/wireErrorTriage.ts`、`engineSessionParam.ts`、`engineCapsCache.ts`、`liveInitToolFace.ts`、`adapter/activeRunSelfHeal.ts` |
|
|
146
145
|
| 16 | **引擎词汇表与包自检** | 41 | `CONFIG_REFUSAL_CODES` / `isConfigRefusalCode` · `STOP_CONFLICT_CODES` · `isInterruptedToolEndCode` · `isRewindFamilyCode` · `CLIENT_VERBS` · `compensationSplitViolations` | 三端分臂共用的**去字面化** `errorCode` 词表(病根正是三端各抄一份字面);编译期 verb 闭合门;搬迁补偿登记表 | `src/engineErrorCodes.ts`(34 项;A-028.11/.13 补 `DRAINING_ERROR_CODE`/`SCENARIO_NOT_ALLOWED_ERROR_CODE`/`RESUME_AT_ERROR_CODE_PREFIX`)、`src/classifierVerdictWire.ts`、`src/compensations.ts`、`src/clientSlice.ts` |
|
|
147
146
|
|
|
148
147
|
🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
|