@sema-agent/client-core 0.29.0 → 0.30.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/CHANGELOG.md +455 -0
- package/README.md +19 -3
- package/dist/adapt/arms.js +24 -1
- package/dist/adapt/wireShapes.d.ts +7 -0
- package/dist/adapt/wireShapes.js +7 -0
- package/dist/adapter/activeRunSelfHeal.d.ts +285 -48
- package/dist/adapter/activeRunSelfHeal.js +553 -19
- package/dist/adapter/runStream.js +13 -3
- package/dist/engineWireSdk.d.ts +10 -2
- package/dist/engineWireSdk.js +7 -3
- package/dist/hitl/approvalDecisionNoteAudit.d.ts +58 -0
- package/dist/hitl/approvalDecisionNoteAudit.js +91 -0
- package/dist/hitl/armedGateRegistry.d.ts +62 -4
- package/dist/hitl/armedGateRegistry.js +226 -14
- package/dist/hitl/askParkRowRouting.d.ts +150 -0
- package/dist/hitl/askParkRowRouting.js +183 -0
- package/dist/hitl/gateIdentity.d.ts +8 -0
- package/dist/hitl/gateIdentity.js +8 -0
- package/dist/hitl/hitlBridge.d.ts +7 -0
- package/dist/hitl/hitlBridge.js +11 -2
- package/dist/hitl/parkOwnership.d.ts +2 -1
- package/dist/hitl/parkOwnership.js +11 -3
- package/dist/hitl/parkRowBirthWait.d.ts +63 -0
- package/dist/hitl/parkRowBirthWait.js +192 -0
- package/dist/hitl/planReviewWire.d.ts +31 -1
- package/dist/hitl/planReviewWire.js +69 -30
- package/dist/hitl/resumeRunningCard.d.ts +134 -0
- package/dist/hitl/resumeRunningCard.js +177 -0
- package/dist/hitl/toolApprovalWire.d.ts +49 -9
- package/dist/hitl/toolApprovalWire.js +9 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +14 -0
- package/dist/seatContract.d.ts +27 -0
- package/dist/seatContract.js +42 -0
- package/dist/subagent/engineSubagentTail.d.ts +0 -2
- package/dist/subagent/engineSubagentTail.js +7 -15
- package/dist/subagentContentStore.d.ts +58 -2
- package/dist/subagentContentStore.js +95 -6
- package/dist/toolResult.d.ts +26 -0
- package/dist/toolResult.js +38 -6
- package/dist/workflowClient.d.ts +6 -1
- package/docs/INTEGRATION-CLIENTS.md +844 -0
- package/docs/REFACTOR-LEDGER.md +392 -0
- package/package.json +7 -4
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hitl/resumeRunningCard.ts — **resume 撞上「上一轮还在跑」**时那张三选卡的判定 / 选项集 / 文案
|
|
3
|
+
* (#265 上收件,2026-08-14;源形 = cli `src/sema/resumeRunningArm.ts` 的纯判定半场,编排腿留在端上)。
|
|
4
|
+
*
|
|
5
|
+
* ── 病与改形 ────────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
* resume 时读到一条 `running` 的 run,旧形是**客户端自己**给它打一枪 cancel(动机 = 僵尸烧 token)。
|
|
7
|
+
* 矛盾:resume 时的 running run **不可判**是「用户要的在飞工作」还是「僵尸」—— 而实况里自动 cancel
|
|
8
|
+
* 恰好是错的方向(用户批准之后 run 该继续跑,resume 时 cancel = 把刚救活的 run 又杀了)。血统纪律:
|
|
9
|
+
* **破坏性动作恒不自动**。改形 = 把三条路交回用户:
|
|
10
|
+
* ① `Attach and watch` —— 接管为当前 turn 输出;
|
|
11
|
+
* ② `Cancel it` —— 那个动作降级为**用户按下**的选项;
|
|
12
|
+
* ③ `Leave it running in the background`(**默认焦点**)—— 零动作。
|
|
13
|
+
*
|
|
14
|
+
* ── §3.2a 活性证据的缺席纪律(不可削)──────────────────────────────────────────────────────
|
|
15
|
+
* 判活体的 `msSinceLastActivity`(相对毫秒)在场 ⇒ 卡上渲一行「last activity Ns ago」;
|
|
16
|
+
* **缺席 = 无法证明**(跨副本命中 / 副本重启后),不是「不活」—— 那一行**整行不渲**,绝不折成 0、
|
|
17
|
+
* 绝不写成「已死」。
|
|
18
|
+
*
|
|
19
|
+
* ── §3.2b 无 UI 臂:退役自动 cancel 之后不许留死墙 ──────────────────────────────────────────
|
|
20
|
+
* 「不自动 cancel + 没有卡可选」= 又一堵死墙。所以没有卡的形必须**显式定义臂**:恒不 cancel;
|
|
21
|
+
* 输出**机读三选指路**({@link resumeRunningNoUiGuidance},attach / cancel / 后台留着三条路各给
|
|
22
|
+
* 真实命令或 API 路径);headless 车道走**专码**退出({@link RESUME_RUNNING_NO_UI_EXIT_CODE});
|
|
23
|
+
* 绝不静默挂起、绝不谎报成功。
|
|
24
|
+
*
|
|
25
|
+
* ── 🔴 塌缩纪律 ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
* Esc / 空答 / 认不出的自由文本 / 作答看门狗到点 / 呈卡失败 —— 一律收口成 ③(零动作)。
|
|
27
|
+
* 破坏性动作只认**逐字命中**的显式选择({@link resumeChoiceFromLabels})。
|
|
28
|
+
*
|
|
29
|
+
* ── 🔴 假 affordance 禁令(按供给渲)───────────────────────────────────────────────────────
|
|
30
|
+
* 一个按了没用的选项比没有这个选项更坏:durable `runs.events` 动词缺席**或**没有接管消费口 ⇒
|
|
31
|
+
* ① 整个不渲;`runs.cancel` 缺席 ⇒ ② 整个不渲;两条都缺 ⇒ 一张卡都不铸,走 §3.2b 臂。
|
|
32
|
+
*
|
|
33
|
+
* ── 归层 ────────────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
* 本文件 = 纯判定 + 选项集 + 文案,**零 import**。呈卡口/动词切片/判活口/指路 sink/退出码应用口
|
|
35
|
+
* 全部由端在装配点注入;端只剩装配与呈现。
|
|
36
|
+
*/
|
|
37
|
+
// ── 逐字契约面(答案按 label 原样回声 —— 引擎的 `selected ⊆ options` 围栏,漂一个字节即失灵)──
|
|
38
|
+
/** ① 接管观看。 */
|
|
39
|
+
export const RESUME_CHOICE_ATTACH_LABEL = 'Attach and watch';
|
|
40
|
+
/** ② 用户显式取消(自动 cancel 那个动作的降级形)。 */
|
|
41
|
+
export const RESUME_CHOICE_CANCEL_LABEL = 'Cancel it';
|
|
42
|
+
/** ③ 后台留着 —— 🔴 **默认焦点**,所以它必须排在 options[0](见 {@link resumeRunningOptions})。 */
|
|
43
|
+
export const RESUME_CHOICE_BACKGROUND_LABEL = 'Leave it running in the background';
|
|
44
|
+
/** 卡的 header(答案按 header 归键;本卡只有一问)。与 409 三选卡的入口刻意不同词 —— 同一张卡的
|
|
45
|
+
* 两个入口,题面说的不是一件事。 */
|
|
46
|
+
export const RESUME_CHOICE_HEADER = 'Earlier run still in flight';
|
|
47
|
+
/**
|
|
48
|
+
* §3.2a 活性证据行。**缺席 ⇒ null(整行不渲)**;绝不渲 0、绝不渲「已死」。
|
|
49
|
+
* 负数 / 非有限值同样按缺席处理(wire 位 UNTRUSTED,类型说是 number 也要真判)。
|
|
50
|
+
*/
|
|
51
|
+
export function resumeRunningLivenessRow(msSinceLastActivity) {
|
|
52
|
+
const ms = msSinceLastActivity;
|
|
53
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0)
|
|
54
|
+
return null;
|
|
55
|
+
return `The engine last recorded activity on it ${String(Math.round(ms / 1000))}s ago.`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 卡的题面。第二行 = 活性证据(§3.2a 缺席时**整行消失**,不留占位、不留「unknown」字样 ——
|
|
59
|
+
* 一行「last activity: unknown」和一行 0 一样是在替引擎回答一个它没回答的问题)。
|
|
60
|
+
*/
|
|
61
|
+
export function resumeRunningQuestionText(i) {
|
|
62
|
+
const head = `This conversation was resumed while an earlier turn is still executing on the engine ` +
|
|
63
|
+
`(run ${i.taskId}, status ${i.status}). sema did not touch it. What should it do?`;
|
|
64
|
+
const liveness = resumeRunningLivenessRow(i.msSinceLastActivity);
|
|
65
|
+
return liveness === null ? head : `${head}\n${liveness}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 选项集(同一张卡两个入口,选项集**按入口分形**)。
|
|
69
|
+
*
|
|
70
|
+
* 🔴 **顺序即默认焦点**:卡的渲染面 `focusedIndex` 初值恒 0,而 wire 的 `QuestionFrame` 里
|
|
71
|
+
* **没有**任何「默认焦点」位可传。所以「默认焦点 = ③ 后台留着」在结构上只有一个落法:把 ③ 排在
|
|
72
|
+
* options[0]。谁哪天为了「读起来顺」把 ①②③ 按序号排回去,默认焦点就漂到了 `Attach and watch`
|
|
73
|
+
* —— 一个手滑回车就把一条后台 run 接管到前台。常驻门逐位反钉这个顺序。
|
|
74
|
+
*/
|
|
75
|
+
export function resumeRunningOptions(i) {
|
|
76
|
+
const options = [
|
|
77
|
+
{
|
|
78
|
+
// 🔴 默认焦点 —— 三条路里唯一零副作用的一条,所以它是手滑回车的落点。
|
|
79
|
+
// 🔴 措辞:这一项**不许**承诺「它会留在某个任务面板里」—— 这张卡的前提恰恰是「那条 run 活得
|
|
80
|
+
// 比上一个客户端进程久」,新进程里没有任何腿会为它建行(③ 的处置本身也是显式零动作)。
|
|
81
|
+
// 默认焦点上的一句假 affordance 是最坏的一格:手滑回车的人拿到一条既看不见、也停不掉、
|
|
82
|
+
// 还占着会话锁的 run。换成两句**可证的**真话:本屏不会列出它;它还占着会话,所以下一条
|
|
83
|
+
// 消息会再撞上它(那时 409 三选卡会带着 steer/cancel 两条真出路出现)。
|
|
84
|
+
label: RESUME_CHOICE_BACKGROUND_LABEL,
|
|
85
|
+
description: 'Leaves the run alone. It keeps executing on the engine, but this screen will not show it and it will not be listed here — your next message runs into it again, and sema asks you then.',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
if (i.canAttach) {
|
|
89
|
+
options.push({
|
|
90
|
+
label: RESUME_CHOICE_ATTACH_LABEL,
|
|
91
|
+
// 🔴 第二句是**知情同意**那一半(见 {@link resumeAttachReplayDisclosure}):resume 入口没有
|
|
92
|
+
// 事件锚可用,durable 尾只能从这条 run 的第一帧读起,所以崩溃前已经落盘并被 resume 还原到
|
|
93
|
+
// 屏上的那一段会再出现一次。用户按下之前就得看见这句 —— 一个会做出用户没预料到的事的
|
|
94
|
+
// 选项,和一个按了没用的选项同样是假 affordance。
|
|
95
|
+
description: 'Follows that run from here: sema replays it from the engine ledger and keeps streaming it into this conversation. ' +
|
|
96
|
+
'Anything from that turn already shown above will appear a second time — sema has no resume marker for it.',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (i.canCancel) {
|
|
100
|
+
options.push({
|
|
101
|
+
label: RESUME_CHOICE_CANCEL_LABEL,
|
|
102
|
+
description: 'Stops that run on the engine (whatever it has been doing is discarded) and frees this session for a new turn.',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return options;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 答案 → 决定。🔴 只认**逐字命中**的标签;空答(overlay 的 Esc/dismiss 形)、多选、认不出的
|
|
109
|
+
* 自由文本一律回落 ③ —— 破坏性动作只认显式选择(与 409 三选卡同律)。
|
|
110
|
+
*/
|
|
111
|
+
export function resumeChoiceFromLabels(selected) {
|
|
112
|
+
if (!Array.isArray(selected) || selected.length !== 1)
|
|
113
|
+
return 'background';
|
|
114
|
+
const label = selected[0];
|
|
115
|
+
if (label === RESUME_CHOICE_ATTACH_LABEL)
|
|
116
|
+
return 'attach';
|
|
117
|
+
if (label === RESUME_CHOICE_CANCEL_LABEL)
|
|
118
|
+
return 'cancel';
|
|
119
|
+
return 'background';
|
|
120
|
+
}
|
|
121
|
+
// ── §3.2b 无 UI 臂 ───────────────────────────────────────────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* headless 车道撞 resume-running 的**专码**退出码。
|
|
124
|
+
*
|
|
125
|
+
* 为什么非 0:这一趟 resume **没有**把会话交到一个可用状态 —— 那条 run 还占着会话,脚本接下来
|
|
126
|
+
* 提交什么都会撞 409。静默 0 = 谎报成功。
|
|
127
|
+
* 为什么是 75:sysexits.h 的 `EX_TEMPFAIL`(「暂时性失败,请稍后重试」)语义正对 —— run 跑完
|
|
128
|
+
* 或被取消之后同一条命令就能过;与 cli 既用的 0/1/2/129/143 零撞(脚本消费者据此可以把「会话被
|
|
129
|
+
* 在飞 run 占住」与「真失败(1)」「用法错(2)」分臂)。
|
|
130
|
+
*/
|
|
131
|
+
export const RESUME_RUNNING_NO_UI_EXIT_CODE = 75;
|
|
132
|
+
/** 机读指路的行首标识 —— 脚本按它 grep;UI 改版不许动它。 */
|
|
133
|
+
export const RESUME_RUNNING_GUIDANCE_TAG = 'sema: resume-running:';
|
|
134
|
+
/**
|
|
135
|
+
* §3.2b 的**机读三选指路**。三条路各给真实命令或 API 路径,一条都不许省 ——
|
|
136
|
+
* 少写一条 = 那条路在这个车道上被客户端单方面关掉了。
|
|
137
|
+
*
|
|
138
|
+
* 🔴 `attach=` 那一行只有在**真知道 session id** 时才给具体命令:不知道还硬拼一条
|
|
139
|
+
* `sema --resume <sessionId>` 字面量,就是印一条按了没用的命令(假 affordance 的文本形)。
|
|
140
|
+
*/
|
|
141
|
+
export function resumeRunningNoUiGuidance(i) {
|
|
142
|
+
const sid = typeof i.sessionId === 'string' && i.sessionId.length > 0 ? i.sessionId : null;
|
|
143
|
+
const attach = sid === null ? 'sema --resume (pick this session from the list)' : `sema --resume ${sid}`;
|
|
144
|
+
const T = RESUME_RUNNING_GUIDANCE_TAG;
|
|
145
|
+
return [
|
|
146
|
+
`${T} run ${i.taskId} from an earlier turn is still executing on the engine, and this lane has no card to ask you on. sema did NOT cancel it and did NOT attach to it.`,
|
|
147
|
+
`${T} attach=${attach}`,
|
|
148
|
+
`${T} cancel=POST /v1/runs/${i.taskId}/cancel`,
|
|
149
|
+
// 🔴 这条车道连 REPL 都不挂载,更没有任何本地面板可看。给的是引擎侧真读面。
|
|
150
|
+
`${T} background=do nothing — run ${i.taskId} keeps executing on the engine; this client does not list it (read it with GET /v1/runs/${i.taskId})`,
|
|
151
|
+
].join('\n');
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* ① 接管之前的**显式披露**。
|
|
155
|
+
*
|
|
156
|
+
* resume 入口没有 `lastEventId` 可用(端今天不持久化引擎事件锚),所以 durable 尾从这条 run 的
|
|
157
|
+
* 第一帧读起 —— 崩溃前已落盘、已被 resume 还原到屏上的那一段会再渲一遍。续听腿那道「无锚 × 已上屏
|
|
158
|
+
* ⇒ 不许从头续读」的门在这一格判不出来(它数的是**本条流**吐过几帧,resume 入口恒 0),所以不能
|
|
159
|
+
* 拿它当安全证明。处置 = 说在前面:静默复读会让用户以为模型把同一段活干了两遍。
|
|
160
|
+
*
|
|
161
|
+
* 🔴 **账目面同源**:同一次重放也会把这条 run 已经执行过的每一个 `turn_end` 再喂一次成本累计器。
|
|
162
|
+
* 客户端这一端证明不了哪一枚已经在恢复出来的总额里(那需要一本按 turn 落盘的账 —— 与缺席的
|
|
163
|
+
* 事件锚是同一件移交残件),所以接管腿声明**不计账**。既然不计,就必须在**同一行**说清楚:
|
|
164
|
+
* 只披露视觉复读、不披露账目缺口,用户拿成本面跟引擎账本对不上时只会怀疑客户端算错了。
|
|
165
|
+
*/
|
|
166
|
+
export function resumeAttachReplayDisclosure(taskId) {
|
|
167
|
+
return (`sema: attaching to run ${taskId} — sema is replaying it from the engine's ledger, so anything from that ` +
|
|
168
|
+
`turn already shown above will appear again below. There is no resume marker for a run that outlived its shell. ` +
|
|
169
|
+
`The replayed turns are not billed into this session's cost again either — the engine's own record ` +
|
|
170
|
+
`(GET /v1/runs/${taskId}) is the authority on what that run really spent.`);
|
|
171
|
+
}
|
|
172
|
+
/** ② 打完 cancel 但引擎没确认落地时的如实一行(绝不谎报「已停止」)。 */
|
|
173
|
+
export function resumeRunningCancelUnconfirmedRow(taskId) {
|
|
174
|
+
return (`sema: the cancel for run ${taskId} did not come back confirmed, so that run may still be executing. ` +
|
|
175
|
+
`This screen does not list it — read its real state on the engine (GET /v1/runs/${taskId}) before ` +
|
|
176
|
+
`assuming this session is free.`);
|
|
177
|
+
}
|
|
@@ -142,8 +142,14 @@ export interface ApprovalCardDenyDecision {
|
|
|
142
142
|
* 这次决断仍是 deny(两条腿上的 `decision` 位逐字节不动)。
|
|
143
143
|
* 🔴 **两腿不同形**(见 {@link surfaceFsApprovalAndDecide} / {@link surfaceToolApprovalFrameAndRespond}):
|
|
144
144
|
* · durable park 腿(`/decide`)wire **有** reason 位 ⇒ 原文上 wire(包内按 server 上限截断);
|
|
145
|
-
* · live 帧腿(`/v1/tool-approvals/:id/respond`)
|
|
146
|
-
*
|
|
145
|
+
* · live 帧腿(`/v1/tool-approvals/:id/respond`)的 `decision` 仍是三词闭集,归因走**另一个键
|
|
146
|
+
* `note`**(与 durable `AskDecisionBody.note` 同词同源同一列 `decision_note`)—— 本位的原文经
|
|
147
|
+
* 两道门后落到 `RespondToolApprovalOpts.note` 上 wire:能力位
|
|
148
|
+
* `approvalDecisionNoteCapable === true`,且长度不超 `MAX_RESPOND_NOTE_CHARS`;任一不满足则
|
|
149
|
+
* 只留痕不发(绝不自造侧信道)。发了还要看 ack 的 `noteRecorded` 才算落行。
|
|
150
|
+
* ⚠️ 本段 0.28.0 原文写的是「wire 无 reason 位、只留痕不发,候 server 补位」—— server 7.15.0
|
|
151
|
+
* 已到货、0.29.0 #229 已兑现,那句话从此不成立(2026-08-13 类型/wire 超集台账普查时发现该
|
|
152
|
+
* JSDoc 已陈旧;实现坐标见 {@link surfaceToolApprovalFrameAndRespond} 里的 note 两道门)。
|
|
147
153
|
* 与 `{kind:'failed'}` 的 `reason` **不同义**:那一位是「卡面本身不可用」的诊断,不是人的拒因,
|
|
148
154
|
* 两者绝不互相顶替。
|
|
149
155
|
*/
|
|
@@ -161,7 +167,17 @@ export interface ApprovalCardRequest {
|
|
|
161
167
|
args: object;
|
|
162
168
|
callKey: string;
|
|
163
169
|
signal?: AbortSignal;
|
|
164
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* 后台子代 ask 的归属徽章(卡头 `· @name`;宿主 ask 恒缺席)。
|
|
172
|
+
* ⚠️ **出身声明订正**(0.30.0 发包扫描,2026-08-14):本段 0.28.0 原文写的是「CC swarm workerBadge
|
|
173
|
+
* **原生位**」。类型/wire 超集台账普查逐份直证的结果与那句话相反 —— CC 语料九份
|
|
174
|
+
* (cli220/221/222/223 + pretty218/219/220/221/223)`workerBadge` **全 0 命中**(正控:同一把探针在
|
|
175
|
+
* 同目录对 `permissionMode` 有命中,证明它会说话),`@sema-agent/agent-types` 0.2.0 镜像的 TS AST
|
|
176
|
+
* 声明名集合(1651 个名字)里也没有这个名字。⇒ 这一位按现有证据是 **sema 自铸**,不是 CC 原生位;
|
|
177
|
+
* 终判(让位/改名/登记为超集)候台账 `docs/type-superset.json` 的 `_covenant`「未建工作」条清账。
|
|
178
|
+
* 🔴 三端读到的 `.d.ts` 注释随 dist 出包,所以订正必须落在这里 —— 只在不进 tarball 的台账里留话,
|
|
179
|
+
* 消费端读到的仍是那句错误出身断言。
|
|
180
|
+
*/
|
|
165
181
|
workerBadge?: {
|
|
166
182
|
name: string;
|
|
167
183
|
color: string;
|
|
@@ -170,10 +186,19 @@ export interface ApprovalCardRequest {
|
|
|
170
186
|
* 不可注入 args 的提示走这里([1543]③ 记账的 note 位,2026-07-23 落位)。 */
|
|
171
187
|
wireNote?: string;
|
|
172
188
|
/**
|
|
173
|
-
* 治理强制位(server ≥7.5.0,2026-08-08 补透传)
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
189
|
+
* 治理强制位(server ≥7.5.0,2026-08-08 补透传)。**缺席 ≠ false**:只在为真时在场,缺席 = 无治理
|
|
190
|
+
* 来源的证据。壳应据此把门呈成**表态掀不掉**(而不是引导用户去改 `permissionMode`);呈现形是壳
|
|
191
|
+
* 半场,本包只保证这一位到得了卡口 —— 此前它在帧类型上就不存在,于是宿主无论怎么写都读不到。
|
|
192
|
+
*
|
|
193
|
+
* 🔴 **本位是双源合流**(0.30.0 发包扫描订正,2026-08-14;本段原文只写「原样来自
|
|
194
|
+
* {@link ToolApprovalFrame.governanceForced}」,而 [C170] 那一批起 durable 腿也往这一位灌):
|
|
195
|
+
* · **活卡帧腿**:`ToolApprovalFrame.governanceForced`(进程内治理标记表);
|
|
196
|
+
* · **durable park 行腿**:`PendingCheckpoint.governanceForced`(`surfaceFsApprovalAndDecide` 的
|
|
197
|
+
* 行 → 卡重铸处 stamp,条件同样只认 `=== true`)。
|
|
198
|
+
* ⚠️ 上游对这两位的关系有逐字声明(sdk 6.17.2 `PendingCheckpoint` JSDoc):判据属主是
|
|
199
|
+
* `governanceOriginOf`(行上取证格 ∧ 部署姿态合取),与活卡帧那位**语义同族、判据不同源**,
|
|
200
|
+
* 「同一只 ask 两条腿的在场性可以不一致」。⇒ 宿主**不许**把本位读成「活卡帧的治理标记」:
|
|
201
|
+
* 帧腿缺席不等于 durable 腿缺席,反之亦然;要区分来源就看你拿到的是哪条腿的卡。
|
|
177
202
|
*/
|
|
178
203
|
governanceForced?: true;
|
|
179
204
|
/** 委派出处链(core 5.9.0 W1)——原样来自 {@link ToolApprovalFrame.delegation};只读展示增强,
|
|
@@ -186,6 +211,20 @@ export interface ApprovalCardRequest {
|
|
|
186
211
|
* 出身裁剪(MANDATED ask 不提供本档)候 core 5.25.0 出身键 wire 过境,见 #144/[3438]/[3442]。
|
|
187
212
|
*/
|
|
188
213
|
ruleSuggestions?: RuleSuggestion[];
|
|
214
|
+
/**
|
|
215
|
+
* 持久规则候选的**只读**对偶([C170] 答问②半场,0.29.0)——原样来自 durable 队列行
|
|
216
|
+
* `PendingCheckpoint.ruleSuggestions`(server 7.16.0 [3684]② 补齐的供给)的合形项。
|
|
217
|
+
* 🔴 与 {@link ruleSuggestions} **刻意分键不合流**:那一位的契约是「卡渲可选中的『不再询问』档
|
|
218
|
+
* → 决断带 {@link ApprovalCardAllowDecision.persistRule} 回兑」,兑付口=同副本活卡腿
|
|
219
|
+
* `respond.persistRule`;而 durable 腿的 `/decide` 体**无规则位**(SDK 6.17.2 PendingCheckpoint
|
|
220
|
+
* JSDoc 逐字:display/triage-only)——把行上候选落进那一位,就是一个按下去规则不落地的假
|
|
221
|
+
* affordance。本键的不变量是**决断字节**:决断绝不因它带 `persistRule`(或任何规则位)——
|
|
222
|
+
* 「只读」限定的是 wire 回兑通道,不是屏面。渲成可选中项是**允许**的,前提=兑付走客户端
|
|
223
|
+
* **本地**落规则(cli 1.0.76 起的形:选中 → 客户端写自己的 settings `permissions.allow` 并把
|
|
224
|
+
* 成败如实上屏,决断仍与两态时代逐字节相同;这正是 CC 第三态的原形——本地文件写)。
|
|
225
|
+
* 缺席 = 无候选/老行/坏形(三者同形,不猜);既有卡口不读本键 ⇒ 卡形字节不变。
|
|
226
|
+
*/
|
|
227
|
+
ruleSuggestionsReadOnly?: RuleSuggestion[];
|
|
189
228
|
/**
|
|
190
229
|
* 被越级的持久 allow 规则**原文**(#144,原样来自 {@link ToolApprovalFrame.persistedRuleShadowed}
|
|
191
230
|
* 的合形值)——壳据此渲「你的规则仍在,只是这次调用被要求逐次确认」;缺席 = 卡形与 0.27.0
|
|
@@ -308,8 +347,9 @@ export interface ToolApprovalFrame {
|
|
|
308
347
|
* {@link ApprovalCardRequest.persistedRuleShadowed} 的同名警示(下游删除规则面不许拿它当规则身份)。
|
|
309
348
|
*
|
|
310
349
|
* 耐久路(park 行)的对偶是 `gate.riskDescriptor.shadowedRule`(server 对 riskDescriptor 整体透传);
|
|
311
|
-
*
|
|
312
|
-
*
|
|
350
|
+
* durable 腿自 0.29.0 起 stamp 行上的 governanceForced/ruleSuggestionsReadOnly 两个展示键
|
|
351
|
+
* ([C170] 答问②半场,见 {@link surfaceFsApprovalAndDecide} 的卡入参),但 shadowedRule 的
|
|
352
|
+
* 耐久对偶仍未消费——那一路是独立的一件,不在本键的施工面内。
|
|
313
353
|
*/
|
|
314
354
|
persistedRuleShadowed?: string;
|
|
315
355
|
}
|
|
@@ -248,11 +248,20 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
248
248
|
const bridge = new HitlBridge(deps.client, taskId);
|
|
249
249
|
// A-028.3:卡身份键经 gateIdentity 唯一铸口(呈现台账/壳侧重开腿同源,字面不再各铸各的)。
|
|
250
250
|
const callKey = approvalCallKey(gatedCallId, taskId);
|
|
251
|
+
// [C170] 答问②半场(0.29.0):durable 富行的两个展示键随卡透传 —— 修前这里是四位闭集,
|
|
252
|
+
// server 7.16.0 起行上就有的 ruleSuggestions/governanceForced 在「行 → 卡」重铸处整段丢失
|
|
253
|
+
// (feed 行原样透传零丢失,丢的只有这处)。governanceForced 条件 stamp 只认 === true(缺席
|
|
254
|
+
// 纪律与活卡腿同款:缺席=无治理来源证据,绝不写 false);候选走 readRuleSuggestions 同一把
|
|
255
|
+
// 合形窄化,落**只读键**(红线见 ApprovalCardRequest.ruleSuggestionsReadOnly 顶注:/decide
|
|
256
|
+
// 无规则位,落可兑付位=假 affordance)。
|
|
257
|
+
const ruleSuggestionsReadOnly = readRuleSuggestions(pending.ruleSuggestions);
|
|
251
258
|
const card = await surfaceApprovalCard({
|
|
252
259
|
toolName,
|
|
253
260
|
args,
|
|
254
261
|
callKey,
|
|
255
262
|
...(signal ? { signal } : {}),
|
|
263
|
+
...(pending.governanceForced === true ? { governanceForced: true } : {}),
|
|
264
|
+
...(ruleSuggestionsReadOnly !== undefined ? { ruleSuggestionsReadOnly } : {}),
|
|
256
265
|
});
|
|
257
266
|
switch (card.kind) {
|
|
258
267
|
case 'failed':
|
package/dist/index.d.ts
CHANGED
|
@@ -224,6 +224,10 @@ export * from './hitl/planReviewWire.js';
|
|
|
224
224
|
export * from './hitl/gateIdentity.js';
|
|
225
225
|
export * from './hitl/armedGateRegistry.js';
|
|
226
226
|
export * from './hitl/parkOwnership.js';
|
|
227
|
+
export * from './hitl/parkRowBirthWait.js';
|
|
228
|
+
export * from './hitl/approvalDecisionNoteAudit.js';
|
|
229
|
+
export * from './hitl/askParkRowRouting.js';
|
|
230
|
+
export * from './hitl/resumeRunningCard.js';
|
|
227
231
|
export * from './hitl/approvalsFeed.js';
|
|
228
232
|
export * from './compensations.js';
|
|
229
233
|
export * from './request/printNotification.js';
|
|
@@ -235,3 +239,4 @@ export * from './model/catalogLoader.js';
|
|
|
235
239
|
export * from './model/providerAuth.js';
|
|
236
240
|
export * from './websearch/searchProviderPresets.js';
|
|
237
241
|
export * from './env/localeGeo.js';
|
|
242
|
+
export type { PendingCheckpoint, RuleSuggestion } from '@sema-agent/sdk';
|
package/dist/index.js
CHANGED
|
@@ -326,6 +326,20 @@ export * from './hitl/planReviewWire.js';
|
|
|
326
326
|
export * from './hitl/gateIdentity.js';
|
|
327
327
|
export * from './hitl/armedGateRegistry.js';
|
|
328
328
|
export * from './hitl/parkOwnership.js';
|
|
329
|
+
// ── #265(2026-08-14):C1-C5「决策卡死墙」病族的三端公共判定层上收(源形 = cli 五模块的判据半场;
|
|
330
|
+
// 呈现/编排留端)。四件都是纯判定,零宿主端口:
|
|
331
|
+
// · parkRowBirthWait:「待决行还没出生」这个第三态的有界可中断重查环 —— 把「行不在 pending 表」
|
|
332
|
+
// 从单义的「已决」拆成 row/unborn/settled 三态,`settled` 必须由正面证据得出。
|
|
333
|
+
// · approvalDecisionNoteAudit:回决备注(decisionNote/noteRecorded)三态读面 + 人话行,
|
|
334
|
+
// `unknown ⇒ 整行不渲`(缺席 ≠ false)。
|
|
335
|
+
// · askParkRowRouting:ask park 重开链的四问(队列三态分类 / 行身份 / 问答臂 vs 工具门臂 /
|
|
336
|
+
// 「行已消失」两义分臂)+ 行级单活闸工厂(一个待决项至多一张卡)。
|
|
337
|
+
// · resumeRunningCard:resume 撞上在飞 run 的三选卡(默认焦点 = 零动作那一项 / 活性证据缺席整行
|
|
338
|
+
// 不渲 / 无 UI 臂的机读三选指路与专码退出)。
|
|
339
|
+
export * from './hitl/parkRowBirthWait.js';
|
|
340
|
+
export * from './hitl/approvalDecisionNoteAudit.js';
|
|
341
|
+
export * from './hitl/askParkRowRouting.js';
|
|
342
|
+
export * from './hitl/resumeRunningCard.js';
|
|
329
343
|
// B7 ③(census G20,**行为改动**不是搬迁):pending-approvals 推送 feed(stream 优先 / 断流回落
|
|
330
344
|
// 轮询 / 定期再试)。🔴 它**不替换** D-1 的取件 —— 那三处必须继续走权威 `list()`(见文件头)。
|
|
331
345
|
export * from './hitl/approvalsFeed.js';
|
package/dist/seatContract.d.ts
CHANGED
|
@@ -162,6 +162,33 @@ export declare const TOOL_PERMISSION_REQUEST_ID_DOMAINS: readonly ["live:", "dur
|
|
|
162
162
|
export type ToolPermissionRequestIdDomain = (typeof TOOL_PERMISSION_REQUEST_ID_DOMAINS)[number];
|
|
163
163
|
/** 读口:requestId → 它的域。未登记前缀 / 非串 ⇒ `undefined`(诚实缺席,绝不猜一个域)。 */
|
|
164
164
|
export declare function toolPermissionRequestIdDomain(requestId: unknown): ToolPermissionRequestIdDomain | undefined;
|
|
165
|
+
/**
|
|
166
|
+
* **正向铸口**:域 + 域内 id → `ToolPermissionRequest.requestId`(P-26,2026-08-12)。
|
|
167
|
+
*
|
|
168
|
+
* 沿革:此前本文件只有域表与读口 {@link toolPermissionRequestIdDomain},**没有铸口** —— 于是每个
|
|
169
|
+
* 铸方(session-host 的五条 park 臂、各端的合成行)各自手抄 `` `live:${id}` `` 这样的模板串。手抄
|
|
170
|
+
* 模板串的失效形不是编译红,是**运行期路由到一个不存在的目标**:少个冒号 / 抄错域 / id 是
|
|
171
|
+
* `undefined` 时静默铸出 `"live:undefined"`,而读口对这三种坏键里的后两种照样「认得出域」,所以
|
|
172
|
+
* 谁都不会响。铸口在场之后,这一类错在**铸的那一行**就被拒。
|
|
173
|
+
*
|
|
174
|
+
* 🔴 **往返性**(本口的契约,门里有断言):对任何登记域 `d` 与合法 id,
|
|
175
|
+
* `toolPermissionRequestIdDomain(toolPermissionRequestId(d, id)) === d` 恒成立 ——
|
|
176
|
+
* 铸出来的键必被读口认回**同一个**域,`isToolPermissionRequest` / `LOCAL_SESSIONS_SPEC` 的
|
|
177
|
+
* 域校验也必然放行(三处读的是同一张 {@link TOOL_PERMISSION_REQUEST_ID_DOMAINS})。
|
|
178
|
+
*
|
|
179
|
+
* 🔴 **坏入参一律 throw,绝不静默吐坏键**:铸口的整个价值就在于「坏键铸不出来」,返回
|
|
180
|
+
* `undefined` 只会把 `${undefined}` 推到下一行的模板串里(与 `honest-absence` 同族的取舍:
|
|
181
|
+
* 缺席要说出来,而这里连「缺席」都不该发生 —— 调用方手上没有合法 id 就不该走到铸这一步)。
|
|
182
|
+
* 三条拒绝各自对应一个真实误用:
|
|
183
|
+
* ① 域不在词表(含忘了尾冒号的 `"live"`)—— 域外键 = 这条决断无处可去,与读口/校验器同判据;
|
|
184
|
+
* ② id 非串 / 空串 —— `"live:"` 是半截键,读口却照样认出 `live:` 域,故必须在铸时拦;
|
|
185
|
+
* ③ id **本身**已经带着某个登记域前缀 —— 典型误用 = 把一条铸好的 requestId 当 id 再喂进来,
|
|
186
|
+
* 得到 `"live:live:ap-1"`(往返性仍成立、校验器仍放行,只有决断落地时才发现目标不存在)。
|
|
187
|
+
* ⚠️ 这条不拦「id 里含冒号」:`durable:<engineSessionId>:<callId>` 的 id 段天生带冒号。
|
|
188
|
+
*
|
|
189
|
+
* 错误文本只带**域**与判据,不回显 id 原文(id 段可能含 session/call 标识,错误路径也是外溢面)。
|
|
190
|
+
*/
|
|
191
|
+
export declare function toolPermissionRequestId(domain: ToolPermissionRequestIdDomain, id: string): string;
|
|
165
192
|
/** design/80 D-1 的 TOCTOU 绑定**对**(REF-CC-070:此前是两个互不相干的 optional,半个 pair 照样过门)。
|
|
166
193
|
* 一位承载整对 ⇒「只有 callId 没有 inputHash」在类型层不可表达,门里也不用再补联合判据。 */
|
|
167
194
|
export interface ToctouBinding {
|
package/dist/seatContract.js
CHANGED
|
@@ -118,6 +118,48 @@ export function toolPermissionRequestIdDomain(requestId) {
|
|
|
118
118
|
return undefined;
|
|
119
119
|
return TOOL_PERMISSION_REQUEST_ID_DOMAINS.find((d) => requestId.startsWith(d));
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* **正向铸口**:域 + 域内 id → `ToolPermissionRequest.requestId`(P-26,2026-08-12)。
|
|
123
|
+
*
|
|
124
|
+
* 沿革:此前本文件只有域表与读口 {@link toolPermissionRequestIdDomain},**没有铸口** —— 于是每个
|
|
125
|
+
* 铸方(session-host 的五条 park 臂、各端的合成行)各自手抄 `` `live:${id}` `` 这样的模板串。手抄
|
|
126
|
+
* 模板串的失效形不是编译红,是**运行期路由到一个不存在的目标**:少个冒号 / 抄错域 / id 是
|
|
127
|
+
* `undefined` 时静默铸出 `"live:undefined"`,而读口对这三种坏键里的后两种照样「认得出域」,所以
|
|
128
|
+
* 谁都不会响。铸口在场之后,这一类错在**铸的那一行**就被拒。
|
|
129
|
+
*
|
|
130
|
+
* 🔴 **往返性**(本口的契约,门里有断言):对任何登记域 `d` 与合法 id,
|
|
131
|
+
* `toolPermissionRequestIdDomain(toolPermissionRequestId(d, id)) === d` 恒成立 ——
|
|
132
|
+
* 铸出来的键必被读口认回**同一个**域,`isToolPermissionRequest` / `LOCAL_SESSIONS_SPEC` 的
|
|
133
|
+
* 域校验也必然放行(三处读的是同一张 {@link TOOL_PERMISSION_REQUEST_ID_DOMAINS})。
|
|
134
|
+
*
|
|
135
|
+
* 🔴 **坏入参一律 throw,绝不静默吐坏键**:铸口的整个价值就在于「坏键铸不出来」,返回
|
|
136
|
+
* `undefined` 只会把 `${undefined}` 推到下一行的模板串里(与 `honest-absence` 同族的取舍:
|
|
137
|
+
* 缺席要说出来,而这里连「缺席」都不该发生 —— 调用方手上没有合法 id 就不该走到铸这一步)。
|
|
138
|
+
* 三条拒绝各自对应一个真实误用:
|
|
139
|
+
* ① 域不在词表(含忘了尾冒号的 `"live"`)—— 域外键 = 这条决断无处可去,与读口/校验器同判据;
|
|
140
|
+
* ② id 非串 / 空串 —— `"live:"` 是半截键,读口却照样认出 `live:` 域,故必须在铸时拦;
|
|
141
|
+
* ③ id **本身**已经带着某个登记域前缀 —— 典型误用 = 把一条铸好的 requestId 当 id 再喂进来,
|
|
142
|
+
* 得到 `"live:live:ap-1"`(往返性仍成立、校验器仍放行,只有决断落地时才发现目标不存在)。
|
|
143
|
+
* ⚠️ 这条不拦「id 里含冒号」:`durable:<engineSessionId>:<callId>` 的 id 段天生带冒号。
|
|
144
|
+
*
|
|
145
|
+
* 错误文本只带**域**与判据,不回显 id 原文(id 段可能含 session/call 标识,错误路径也是外溢面)。
|
|
146
|
+
*/
|
|
147
|
+
export function toolPermissionRequestId(domain, id) {
|
|
148
|
+
if (!TOOL_PERMISSION_REQUEST_ID_DOMAINS.includes(domain)) {
|
|
149
|
+
throw new Error(`toolPermissionRequestId: 未登记的 requestId 域 —— 只认 ${TOOL_PERMISSION_REQUEST_ID_DOMAINS.join(" / ")}` +
|
|
150
|
+
"(域前缀就是决断的路由依据,域外键无处可去)");
|
|
151
|
+
}
|
|
152
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
153
|
+
throw new Error(`toolPermissionRequestId: 域 ${domain} 的 id 必须是非空串(收到 ${typeof id})—— ` +
|
|
154
|
+
"半截键会被读口认成本域,却路由不到任何目标");
|
|
155
|
+
}
|
|
156
|
+
const nested = toolPermissionRequestIdDomain(id);
|
|
157
|
+
if (nested !== undefined) {
|
|
158
|
+
throw new Error(`toolPermissionRequestId: 域 ${domain} 的 id 本身已带域前缀 ${nested} —— ` +
|
|
159
|
+
"把铸好的 requestId 当 id 再喂进来会得到双前缀键(durable 的 id 段可以含冒号,但不能以域前缀开头)");
|
|
160
|
+
}
|
|
161
|
+
return `${domain}${id}`;
|
|
162
|
+
}
|
|
121
163
|
export const TOCTOU_BINDING_KEYS = ["callId", "inputHash"];
|
|
122
164
|
/**
|
|
123
165
|
* 座位事件名的**闭集**(REF-CC-065):这是三端唯一的事件名真源。
|
|
@@ -20,8 +20,6 @@ export interface SubagentActivity {
|
|
|
20
20
|
export type SubagentActivitySink = (a: SubagentActivity) => void;
|
|
21
21
|
/** 装/卸 Progress 段落点。传 null 卸。返回还原函数。 */
|
|
22
22
|
export declare function installSubagentActivitySink(sink: SubagentActivitySink | null): () => void;
|
|
23
|
-
/** tool_end.output 经 server redactDeep,原型不保 string——渲染面统一字符串化(对象走 JSON)。 */
|
|
24
|
-
export declare function coerceOutput(v: unknown): string | undefined;
|
|
25
23
|
/**
|
|
26
24
|
* tail 是否正对该行开着 —— **两源互斥判别口**(行帧 `currentTool` 臂在 tail 活跃时让位:
|
|
27
25
|
* tail 的 tool_start 带真 args,渲染更细;两源同录 = 每步双行)。
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ── 拆缝(T35/T36/T44 同一条原则:判定半场进包、执行半场留宿主)────────────────────────────────
|
|
5
5
|
* **进包**:caps 探测 · tail 台账(每 taskId 至多一条)· SSE 消费循环 · 帧 → subagentContentStore
|
|
6
|
-
* 的四条投影 ·
|
|
6
|
+
* 的四条投影 · settle 收口 · 诊断锚 · 两源互斥判别口 `isEngineSubagentTailActive`
|
|
7
|
+
* (`coerceOutput` 原也在这里,2026-08-12 搬去 `subagentContentStore.ts` 与 C1 腿共用一份)。
|
|
7
8
|
* **留宿主**:`recordSubagentActivity` / `recordFleetCurrentTool` 里对
|
|
8
9
|
* `AppState.tasks[taskId].progress.recentActivities` 的读改写 —— 那是**端的任务状态形状**
|
|
9
10
|
* (`LocalAgentTaskState` / `ToolActivity`),web/桌面各有各的形。
|
|
@@ -49,7 +50,7 @@ import { hostLog } from '../host.js';
|
|
|
49
50
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
50
51
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
51
52
|
import { activeEngineRunId, engineSessionParam } from '../engineSessionParam.js';
|
|
52
|
-
import { getBgParentRun, parentToolCallIdOf, publishSubagentContentEvent, settleSubagentContent, } from '../subagentContentStore.js';
|
|
53
|
+
import { coerceOutput, getBgParentRun, parentToolCallIdOf, publishSubagentContentEvent, settleSubagentContent, } from '../subagentContentStore.js';
|
|
53
54
|
// ── caps 探测(true 固化 / false TTL / 失败不缓存)────────────────────────────────────────────
|
|
54
55
|
const capByBase = new Map();
|
|
55
56
|
const CAP_FALSE_RETRY_TTL_MS = 5 * 60_000;
|
|
@@ -83,19 +84,10 @@ export function installSubagentActivitySink(sink) {
|
|
|
83
84
|
activitySink = prev;
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (typeof v === 'string')
|
|
91
|
-
return v;
|
|
92
|
-
try {
|
|
93
|
-
return JSON.stringify(v);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return String(v);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
87
|
+
/* `coerceOutput`(tool_end.output 的字符串化口)**已搬到 `subagentContentStore.ts`**
|
|
88
|
+
* (#158 移交① / [3674](d) 姊妹病,2026-08-12):喂那个 store 的是两条腿(本 tail 腿 + seam
|
|
89
|
+
* 适配器的 C1 分流臂),字符串化口必须只有一份,故跟着它服务的那个位走。公面导出名不变
|
|
90
|
+
* (index.ts 对两个模块都是 `export *`,所以这里**不再 re-export** —— 同名双星导出会撞)。 */
|
|
99
91
|
/**
|
|
100
92
|
* tail 是否正对该行开着 —— **两源互斥判别口**(行帧 `currentTool` 臂在 tail 活跃时让位:
|
|
101
93
|
* tail 的 tool_start 带真 args,渲染更细;两源同录 = 每步双行)。
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* WIRE FACTS: with `forwardSubagentEvents: true` on the task request, the live stream carries the
|
|
12
12
|
* subagent's text_delta / reasoning_delta / tool_start / tool_end stamped with EventIdentity
|
|
13
|
-
*
|
|
13
|
+
* (§E1 redact upstream; §E2 identity). 🔴 EventIdentity 的键是 `eventId` / `parentToolCallId`
|
|
14
|
+
* (+ LIVE 白名单四臂上的 `sourceTaskId` / `bgAgentId`)—— **没有 `taskId`**(sdk `events.d.ts` 的
|
|
15
|
+
* `interface EventIdentity` 直证;`taskId` 只长在 `meta` 首帧上)。所以内容帧到不了「自带引擎
|
|
16
|
+
* taskId」那一步,归账键靠 `parentToTask`,见 `canonicalKey`。The seam adapter (runStream.ts)
|
|
14
17
|
* DIVERTS those events here — they must never reach the main-transcript pipeline (a subagent's tokens
|
|
15
18
|
* rendering as the leader's text is the pollution failure mode).
|
|
16
19
|
*
|
|
@@ -68,8 +71,61 @@ export interface SubagentContentEvent {
|
|
|
68
71
|
output?: string | undefined;
|
|
69
72
|
isError?: boolean | undefined;
|
|
70
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* `tool_end.output` → 本 store 的 `output` 位(**唯一**字符串化口)。
|
|
76
|
+
*
|
|
77
|
+
* 为什么住在这里(#158 移交① / [3674](d) 姊妹病,2026-08-12 搬入):这个函数原本长在
|
|
78
|
+
* `subagent/engineSubagentTail.ts`(tail 腿)里。而喂本 store 的是**两条**腿 —— tail 腿与
|
|
79
|
+
* seam 适配器的 C1 分流臂(`adapter/runStream.ts`)—— C1 那条当时自己写了
|
|
80
|
+
* `typeof sub.output === 'string' ? sub.output : undefined`,于是同一个 wire 形经不同腿进同一个
|
|
81
|
+
* 账本得到两种结果(块数组走 C1 恒空)。字符串化口跟着**它服务的那个位**走,两条腿一律 import
|
|
82
|
+
* 这一份:第二份实现在类型面就没有立足点了。
|
|
83
|
+
* 🔴 住这里的另一个硬理由:tail 腿的传递闭包里有 SDK/host(engineWireSdk 等),而 C1 所在的
|
|
84
|
+
* `runStream.ts` 有一道**可移植性闭包门**(零 Node 内建 / 零 react / 文件数棘轮 ≤7)——
|
|
85
|
+
* 从 tail 腿 import 会当场把整条 wire 依赖拖进内核。本 store 已在那 7 件之内且零 import,
|
|
86
|
+
* 所以搬到这里的代价是 0 个新闭包文件。
|
|
87
|
+
*
|
|
88
|
+
* 语义:`undefined`/`null` ⇒ `undefined`(诚实缺席,不铸空串);`string` ⇒ 原样直通;
|
|
89
|
+
* **§E1 的 `(TextContent|ImageContent)[]` 块数组 ⇒ 规范展平**(text 块贡献 `.text`、image 块贡献
|
|
90
|
+
* 中性占位 `[image]`);其余(server `redactDeep` 之后原型不保的对象形等)⇒ `JSON.stringify`,
|
|
91
|
+
* 序列化失败退 `String(v)`。
|
|
92
|
+
*
|
|
93
|
+
* 🔴 块数组这一支为什么不是 `JSON.stringify`(0.30.0 发包扫描修,2026-08-14):`output` 是**展示位**
|
|
94
|
+
* (投进 {@link planSubagentViewSlots} 的结果栏),而同一个 wire 形在主 lane 走的是
|
|
95
|
+
* `adapt/wireShapes.ts` 的 `flattenWireOutput`(工具卡体 / print 帧 / 面板 settle 三处)。修前这里
|
|
96
|
+
* 出的是 `[{"type":"text","text":"…"}]` 这种机器形 —— 同一份素材主 lane 渲人话、子代查看态渲转义
|
|
97
|
+
* JSON;带 image 块时整段 base64(events.d.ts 的 `totalChars` 注释逐字:image/document = base64 真实
|
|
98
|
+
* 体量)会被原样灌进内容账本并上屏(item 数有 200 上限、**单 item 体量无上限**)。
|
|
99
|
+
* 🔴 为什么这里还有第二份展平实现、而不是 import 那一份:本 store 是 `runStream.ts` 内核可移植闭包
|
|
100
|
+
* (`run-client-core-portability-test.mjs` 的 `MAX_CLOSURE_FILES`,只许降)里的一件,且本文件头注
|
|
101
|
+
* 三处以「dep-free / 零 import」为立身之本;从 `adapt/wireShapes.ts` 拉一条边会同时破这两条。
|
|
102
|
+
* ⇒ 两份实现由 **pure 门 ⑨b 段的等值断言**逐形对拍钉住(字符串 / 纯 text 块 / 含 image 块 / 混合 /
|
|
103
|
+
* 空数组 / 未知块形六形,`coerceOutput(x) === flattenWireOutput(x)` + 逐字面值),任一侧改语义当场红。
|
|
104
|
+
* 动本函数的块数组分支 ⇒ 同批看那条断言。
|
|
105
|
+
*/
|
|
106
|
+
export declare function coerceOutput(v: unknown): string | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* **内容归账那一半**(`parentToolCallId → taskId`,多对一):把停在 parent 键下的内容迁到 canonical
|
|
109
|
+
* task 键,并让后续内容帧的 `canonicalKey` 解析到 task。**不写** `taskToParent`。
|
|
110
|
+
*
|
|
111
|
+
* 为什么要与 {@link registerSubagentAlias} 分开(#158② 复审收货,2026-08-12):这张表其实承载
|
|
112
|
+
* **两件不同的事**,方向与基数都不同 ——
|
|
113
|
+
* · `parentToTask`:内容归账,**多对一**(同一个子代可能被多次调用宣告:SendMessage 复活会为
|
|
114
|
+
* 同一 taskId 再发一条 `async_launched` 回执,骑的是复活那次调用的 toolCallId);旧键不删,
|
|
115
|
+
* 所以两次宣告的内容都归到同一个 taskId,加一条只会更全。
|
|
116
|
+
* · `taskToParent`:**单值**,而它的读口 {@link parentToolCallIdOf} 是**对外**的 C2 steer 投递
|
|
117
|
+
* 目标(service 按「委派工具调用的 id」路由子代 steer)。这一位的**权威来源是 wire 的内容帧**
|
|
118
|
+
* (`publishSubagentContentEvent` 记在内容态上的 `parentToolCallId`,读口对它做兜底),
|
|
119
|
+
* 一条启动回执**不足以**改写它:复活回执带的是复活调用,拿它盖掉真正的委派调用 = 用户插话
|
|
120
|
+
* 投到错的目标,而「复活后该按哪个 target 投」要 server 语义作证,本包无从判定。
|
|
121
|
+
* ⇒ 回执类喂点只用本函数(只加内容归账,零 steer 面影响);`task_progress` tick 那条喂点两键都带、
|
|
122
|
+
* 且是引擎对「这条 tick 属谁」的当期陈述,继续用 {@link registerSubagentAlias}。
|
|
123
|
+
*/
|
|
124
|
+
export declare function registerSubagentContentAlias(parentToolCallId: string, taskId: string): void;
|
|
71
125
|
/** Register the parentToolCallId ↔ engine-taskId pair (from task_progress, which carries both).
|
|
72
|
-
* Content parked under the parent key migrates to the canonical task key.
|
|
126
|
+
* Content parked under the parent key migrates to the canonical task key.
|
|
127
|
+
* ⚠️ 这一口**同时**写单值的 `taskToParent`(= 对外 C2 steer 目标,见
|
|
128
|
+
* {@link registerSubagentContentAlias} 头注的两件事分家说明)。只想补内容归账的喂点用那一口。 */
|
|
73
129
|
export declare function registerSubagentAlias(parentToolCallId: string, taskId: string): void;
|
|
74
130
|
export declare function publishSubagentContentEvent(ev: SubagentContentEvent): void;
|
|
75
131
|
/** 查看态 composer echo (C2 steer optimistic display) — segment-closes the buffers first so the echo
|