@sema-agent/client-core 0.29.0 → 0.30.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 +400 -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 +264 -48
- package/dist/adapter/activeRunSelfHeal.js +501 -17
- 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/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/parkRowBirthWait.d.ts +63 -0
- package/dist/hitl/parkRowBirthWait.js +192 -0
- package/dist/hitl/resumeRunningCard.d.ts +134 -0
- package/dist/hitl/resumeRunningCard.js +177 -0
- package/dist/hitl/toolApprovalWire.d.ts +46 -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
|
@@ -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,17 @@ 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。卡口对本键只许**只读渲染**(chips/说明行),绝不渲成可选中项、决断绝不因它带
|
|
222
|
+
* `persistRule`。缺席 = 无候选/老行/坏形(三者同形,不猜);既有卡口不读本键 ⇒ 卡形字节不变。
|
|
223
|
+
*/
|
|
224
|
+
ruleSuggestionsReadOnly?: RuleSuggestion[];
|
|
189
225
|
/**
|
|
190
226
|
* 被越级的持久 allow 规则**原文**(#144,原样来自 {@link ToolApprovalFrame.persistedRuleShadowed}
|
|
191
227
|
* 的合形值)——壳据此渲「你的规则仍在,只是这次调用被要求逐次确认」;缺席 = 卡形与 0.27.0
|
|
@@ -308,8 +344,9 @@ export interface ToolApprovalFrame {
|
|
|
308
344
|
* {@link ApprovalCardRequest.persistedRuleShadowed} 的同名警示(下游删除规则面不许拿它当规则身份)。
|
|
309
345
|
*
|
|
310
346
|
* 耐久路(park 行)的对偶是 `gate.riskDescriptor.shadowedRule`(server 对 riskDescriptor 整体透传);
|
|
311
|
-
*
|
|
312
|
-
*
|
|
347
|
+
* durable 腿自 0.29.0 起 stamp 行上的 governanceForced/ruleSuggestionsReadOnly 两个展示键
|
|
348
|
+
* ([C170] 答问②半场,见 {@link surfaceFsApprovalAndDecide} 的卡入参),但 shadowedRule 的
|
|
349
|
+
* 耐久对偶仍未消费——那一路是独立的一件,不在本键的施工面内。
|
|
313
350
|
*/
|
|
314
351
|
persistedRuleShadowed?: string;
|
|
315
352
|
}
|
|
@@ -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
|
|
@@ -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
|
*
|
|
@@ -25,6 +28,64 @@
|
|
|
25
28
|
* NOTIFY: coalesced (~250ms) per-task notify so a per-token delta storm never turns into per-token
|
|
26
29
|
* AppState writes; consumers pull a snapshot on notify.
|
|
27
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* `tool_end.output` → 本 store 的 `output` 位(**唯一**字符串化口)。
|
|
33
|
+
*
|
|
34
|
+
* 为什么住在这里(#158 移交① / [3674](d) 姊妹病,2026-08-12 搬入):这个函数原本长在
|
|
35
|
+
* `subagent/engineSubagentTail.ts`(tail 腿)里。而喂本 store 的是**两条**腿 —— tail 腿与
|
|
36
|
+
* seam 适配器的 C1 分流臂(`adapter/runStream.ts`)—— C1 那条当时自己写了
|
|
37
|
+
* `typeof sub.output === 'string' ? sub.output : undefined`,于是同一个 wire 形经不同腿进同一个
|
|
38
|
+
* 账本得到两种结果(块数组走 C1 恒空)。字符串化口跟着**它服务的那个位**走,两条腿一律 import
|
|
39
|
+
* 这一份:第二份实现在类型面就没有立足点了。
|
|
40
|
+
* 🔴 住这里的另一个硬理由:tail 腿的传递闭包里有 SDK/host(engineWireSdk 等),而 C1 所在的
|
|
41
|
+
* `runStream.ts` 有一道**可移植性闭包门**(零 Node 内建 / 零 react / 文件数棘轮 ≤7)——
|
|
42
|
+
* 从 tail 腿 import 会当场把整条 wire 依赖拖进内核。本 store 已在那 7 件之内且零 import,
|
|
43
|
+
* 所以搬到这里的代价是 0 个新闭包文件。
|
|
44
|
+
*
|
|
45
|
+
* 语义:`undefined`/`null` ⇒ `undefined`(诚实缺席,不铸空串);`string` ⇒ 原样直通;
|
|
46
|
+
* **§E1 的 `(TextContent|ImageContent)[]` 块数组 ⇒ 规范展平**(text 块贡献 `.text`、image 块贡献
|
|
47
|
+
* 中性占位 `[image]`);其余(server `redactDeep` 之后原型不保的对象形等)⇒ `JSON.stringify`,
|
|
48
|
+
* 序列化失败退 `String(v)`。
|
|
49
|
+
*
|
|
50
|
+
* 🔴 块数组这一支为什么不是 `JSON.stringify`(0.30.0 发包扫描修,2026-08-14):`output` 是**展示位**
|
|
51
|
+
* (投进 {@link planSubagentViewSlots} 的结果栏),而同一个 wire 形在主 lane 走的是
|
|
52
|
+
* `adapt/wireShapes.ts` 的 `flattenWireOutput`(工具卡体 / print 帧 / 面板 settle 三处)。修前这里
|
|
53
|
+
* 出的是 `[{"type":"text","text":"…"}]` 这种机器形 —— 同一份素材主 lane 渲人话、子代查看态渲转义
|
|
54
|
+
* JSON;带 image 块时整段 base64(events.d.ts 的 `totalChars` 注释逐字:image/document = base64 真实
|
|
55
|
+
* 体量)会被原样灌进内容账本并上屏(item 数有 200 上限、**单 item 体量无上限**)。
|
|
56
|
+
* 🔴 为什么这里还有第二份展平实现、而不是 import 那一份:本 store 是 `runStream.ts` 内核可移植闭包
|
|
57
|
+
* (`run-client-core-portability-test.mjs` 的 `MAX_CLOSURE_FILES`,只许降)里的一件,且本文件头注
|
|
58
|
+
* 三处以「dep-free / 零 import」为立身之本;从 `adapt/wireShapes.ts` 拉一条边会同时破这两条。
|
|
59
|
+
* ⇒ 两份实现由 **pure 门 ⑨b 段的等值断言**逐形对拍钉住(字符串 / 纯 text 块 / 含 image 块 / 混合 /
|
|
60
|
+
* 空数组 / 未知块形六形,`coerceOutput(x) === flattenWireOutput(x)` + 逐字面值),任一侧改语义当场红。
|
|
61
|
+
* 动本函数的块数组分支 ⇒ 同批看那条断言。
|
|
62
|
+
*/
|
|
63
|
+
export function coerceOutput(v) {
|
|
64
|
+
if (v === undefined || v === null)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (typeof v === 'string')
|
|
67
|
+
return v;
|
|
68
|
+
// §E1 块数组 ⇒ 规范展平(与 adapt/wireShapes.ts 的 flattenWireOutput 逐字同语义;等值由 pure ⑨b 钉)
|
|
69
|
+
if (Array.isArray(v)) {
|
|
70
|
+
const parts = [];
|
|
71
|
+
for (const block of v) {
|
|
72
|
+
if (block && typeof block === 'object') {
|
|
73
|
+
const b = block;
|
|
74
|
+
if (b.type === 'text' && typeof b.text === 'string')
|
|
75
|
+
parts.push(b.text);
|
|
76
|
+
else if (b.type === 'image')
|
|
77
|
+
parts.push('[image]');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return parts.join('');
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return JSON.stringify(v);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return String(v);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
28
89
|
const MAX_ITEMS_PER_TASK = 200;
|
|
29
90
|
const MAX_TASKS = 32;
|
|
30
91
|
const NOTIFY_COALESCE_MS = 250;
|
|
@@ -105,13 +166,31 @@ function scheduleNotify(taskId) {
|
|
|
105
166
|
}
|
|
106
167
|
}, NOTIFY_COALESCE_MS);
|
|
107
168
|
}
|
|
108
|
-
/**
|
|
109
|
-
*
|
|
110
|
-
|
|
169
|
+
/**
|
|
170
|
+
* **内容归账那一半**(`parentToolCallId → taskId`,多对一):把停在 parent 键下的内容迁到 canonical
|
|
171
|
+
* task 键,并让后续内容帧的 `canonicalKey` 解析到 task。**不写** `taskToParent`。
|
|
172
|
+
*
|
|
173
|
+
* 为什么要与 {@link registerSubagentAlias} 分开(#158② 复审收货,2026-08-12):这张表其实承载
|
|
174
|
+
* **两件不同的事**,方向与基数都不同 ——
|
|
175
|
+
* · `parentToTask`:内容归账,**多对一**(同一个子代可能被多次调用宣告:SendMessage 复活会为
|
|
176
|
+
* 同一 taskId 再发一条 `async_launched` 回执,骑的是复活那次调用的 toolCallId);旧键不删,
|
|
177
|
+
* 所以两次宣告的内容都归到同一个 taskId,加一条只会更全。
|
|
178
|
+
* · `taskToParent`:**单值**,而它的读口 {@link parentToolCallIdOf} 是**对外**的 C2 steer 投递
|
|
179
|
+
* 目标(service 按「委派工具调用的 id」路由子代 steer)。这一位的**权威来源是 wire 的内容帧**
|
|
180
|
+
* (`publishSubagentContentEvent` 记在内容态上的 `parentToolCallId`,读口对它做兜底),
|
|
181
|
+
* 一条启动回执**不足以**改写它:复活回执带的是复活调用,拿它盖掉真正的委派调用 = 用户插话
|
|
182
|
+
* 投到错的目标,而「复活后该按哪个 target 投」要 server 语义作证,本包无从判定。
|
|
183
|
+
* ⇒ 回执类喂点只用本函数(只加内容归账,零 steer 面影响);`task_progress` tick 那条喂点两键都带、
|
|
184
|
+
* 且是引擎对「这条 tick 属谁」的当期陈述,继续用 {@link registerSubagentAlias}。
|
|
185
|
+
*/
|
|
186
|
+
export function registerSubagentContentAlias(parentToolCallId, taskId) {
|
|
187
|
+
aliasContentKey(parentToolCallId, taskId);
|
|
188
|
+
}
|
|
189
|
+
/** 两个登记口共用的内容归账实现(单源)。返回 false = 退化对被拒(自指/空值),调用方别再往下写。 */
|
|
190
|
+
function aliasContentKey(parentToolCallId, taskId) {
|
|
111
191
|
if (!parentToolCallId || !taskId || parentToolCallId === taskId)
|
|
112
|
-
return;
|
|
192
|
+
return false;
|
|
113
193
|
parentToTask.set(parentToolCallId, taskId);
|
|
114
|
-
taskToParent.set(taskId, parentToolCallId);
|
|
115
194
|
// a notify queued under the parent key before the alias landed must fire under the canonical key
|
|
116
195
|
if (pendingNotify.delete(parentToolCallId))
|
|
117
196
|
pendingNotify.add(taskId);
|
|
@@ -121,6 +200,16 @@ export function registerSubagentAlias(parentToolCallId, taskId) {
|
|
|
121
200
|
tasks.set(taskId, parked);
|
|
122
201
|
scheduleNotify(taskId);
|
|
123
202
|
}
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
/** Register the parentToolCallId ↔ engine-taskId pair (from task_progress, which carries both).
|
|
206
|
+
* Content parked under the parent key migrates to the canonical task key.
|
|
207
|
+
* ⚠️ 这一口**同时**写单值的 `taskToParent`(= 对外 C2 steer 目标,见
|
|
208
|
+
* {@link registerSubagentContentAlias} 头注的两件事分家说明)。只想补内容归账的喂点用那一口。 */
|
|
209
|
+
export function registerSubagentAlias(parentToolCallId, taskId) {
|
|
210
|
+
if (!aliasContentKey(parentToolCallId, taskId))
|
|
211
|
+
return;
|
|
212
|
+
taskToParent.set(taskId, parentToolCallId);
|
|
124
213
|
}
|
|
125
214
|
function canonicalKey(ev) {
|
|
126
215
|
// the wire's content events carry no taskId — runStream passes parentToolCallId in both slots
|
package/dist/toolResult.d.ts
CHANGED
|
@@ -211,6 +211,32 @@ export declare function todoWriteToolUseResult(rawInput: unknown, oldTodos: unkn
|
|
|
211
211
|
export declare function reportFindingsToolUseResult(structured: unknown, rawInput: unknown): {
|
|
212
212
|
toolUseResult: unknown;
|
|
213
213
|
} | null;
|
|
214
|
+
/**
|
|
215
|
+
* 后台(async)子代的**启动回执**上那两位「身份」—— `type:"agent"` 两形里 `async_launched` 那一形。
|
|
216
|
+
*
|
|
217
|
+
* 只带**身份**,不带可渲染性:`description`/`prompt` 是富卡的必填位(缺了要回落 text 路径),
|
|
218
|
+
* 与「这条回执宣告了哪个子代」无关,故不进本读口 —— 见 {@link structuredToToolUseResult} 的
|
|
219
|
+
* `case 'agent'`。
|
|
220
|
+
*/
|
|
221
|
+
export interface AsyncLaunchedAgentReceipt {
|
|
222
|
+
/** 引擎子代任务 id(注意:**启动回执是 snake_case `task_id`**,完成卡是 camelCase `taskId`)。 */
|
|
223
|
+
readonly taskId: string;
|
|
224
|
+
/** bg 子代生命周期号(SendMessage 复活即 +1)。wire 今天未必带 ⇒ 缺席按首周期解释,绝不铸 0。 */
|
|
225
|
+
readonly seq?: number;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 把一张 `tool_end.structured` 窄化成 {@link AsyncLaunchedAgentReceipt} —— **仅当**它是
|
|
229
|
+
* `type:"agent"` 的 **`async_launched` 启动回执**臂时;完成着陆报告 / 别的族 / 缺身份 ⇒ `null`。
|
|
230
|
+
*
|
|
231
|
+
* 🔴 分臂判据 = `status`(core 原话:"Branch on `status`, never on which keys are present"),
|
|
232
|
+
* 与姊妹读口 {@link readCompletedAgentCard} 互为反面。本函数是这条纪律在**回执**侧的单一实现点:
|
|
233
|
+
* 本包现在有两个消费点 —— 富卡投影(`structuredToToolUseResult`)与 adapt 的 bg 子代 alias 登记
|
|
234
|
+
* (`adapt/arms.ts` 的 `tool_end_result` 臂,#158②)—— 两处不得各抄一遍键名与 `'async_launched'`。
|
|
235
|
+
* 🔴 `task_id` 要**非空**串:空串是「有这个键但没有身份」,拿它当 taskId 会在内容账本里铸一条
|
|
236
|
+
* 键为 `''` 的映射(读口那侧的 `?? ''` 兜底会当场把它认成命中)。诚实拒,不铸空 id ——
|
|
237
|
+
* 与 {@link readCompletedAgentCard} 对 `taskId` 的同款判据一致。
|
|
238
|
+
*/
|
|
239
|
+
export declare function readAsyncLaunchedAgentReceipt(structured: unknown): AsyncLaunchedAgentReceipt | null;
|
|
214
240
|
/**
|
|
215
241
|
* 子代**完成着陆报告**卡(core `agents/subagent.ts#completedAgentCard` 的 wire 形)。
|
|
216
242
|
*
|
package/dist/toolResult.js
CHANGED
|
@@ -732,17 +732,21 @@ modelText) {
|
|
|
732
732
|
// 按宪法三问单独走。返回 null ⇒ 回落模型面 text 路径,与 5.10.0 之前逐字同形,零回归。
|
|
733
733
|
// ⚠️ 完成卡本身**没有被吞**:`eventToSdkMessage` 的 `tool_end_result` 臂把 `structured`
|
|
734
734
|
// 原样透传给宿主,typed 读口见本文件的 {@link readCompletedAgentCard}。
|
|
735
|
-
|
|
735
|
+
// 分臂 + 身份两位走 typed 读口 {@link readAsyncLaunchedAgentReceipt}(#158② 起本包有第二个
|
|
736
|
+
// 消费点 —— adapt 的 alias 登记 —— 判据必须只有一份;`status`/`task_id` 的读法不再散点)。
|
|
737
|
+
const receipt = readAsyncLaunchedAgentReceipt(s);
|
|
738
|
+
if (receipt === null)
|
|
736
739
|
return null;
|
|
737
|
-
const agentId =
|
|
738
|
-
//
|
|
739
|
-
// ——富卡在空 prompt 时收起展开面 **且** 压过 text 兜底,'' 会两条 lane 都丢信息
|
|
740
|
-
|
|
740
|
+
const agentId = receipt.taskId;
|
|
741
|
+
// description/prompt 是**卡的可渲染性**要求(不是身份):缺它们必须回落 **text 路径**而不是
|
|
742
|
+
// 降级成 '' ——富卡在空 prompt 时收起展开面 **且** 压过 text 兜底,'' 会两条 lane 都丢信息
|
|
743
|
+
// (对抗复审 M-1)。故这两位刻意留在本臂,不进读口。
|
|
744
|
+
if (typeof s.description !== 'string' || typeof s.prompt !== 'string') {
|
|
741
745
|
return null;
|
|
742
746
|
}
|
|
743
747
|
// seq = bg 子代生命周期号(SendMessage 复活即 +1)。wire 今天未必带它 —— 缺席 ⇒ 包内按
|
|
744
748
|
// 首周期解释;带了就必须透传,否则复活周期的观察登记会与首周期同键而被幂等吞掉(notif-02)。
|
|
745
|
-
registerOutstandingBgTask(agentId, s.description, s.prompt,
|
|
749
|
+
registerOutstandingBgTask(agentId, s.description, s.prompt, receipt.seq);
|
|
746
750
|
return {
|
|
747
751
|
toolUseResult: {
|
|
748
752
|
isAsync: true,
|
|
@@ -917,6 +921,34 @@ export function reportFindingsToolUseResult(structured, rawInput) {
|
|
|
917
921
|
},
|
|
918
922
|
};
|
|
919
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* 把一张 `tool_end.structured` 窄化成 {@link AsyncLaunchedAgentReceipt} —— **仅当**它是
|
|
926
|
+
* `type:"agent"` 的 **`async_launched` 启动回执**臂时;完成着陆报告 / 别的族 / 缺身份 ⇒ `null`。
|
|
927
|
+
*
|
|
928
|
+
* 🔴 分臂判据 = `status`(core 原话:"Branch on `status`, never on which keys are present"),
|
|
929
|
+
* 与姊妹读口 {@link readCompletedAgentCard} 互为反面。本函数是这条纪律在**回执**侧的单一实现点:
|
|
930
|
+
* 本包现在有两个消费点 —— 富卡投影(`structuredToToolUseResult`)与 adapt 的 bg 子代 alias 登记
|
|
931
|
+
* (`adapt/arms.ts` 的 `tool_end_result` 臂,#158②)—— 两处不得各抄一遍键名与 `'async_launched'`。
|
|
932
|
+
* 🔴 `task_id` 要**非空**串:空串是「有这个键但没有身份」,拿它当 taskId 会在内容账本里铸一条
|
|
933
|
+
* 键为 `''` 的映射(读口那侧的 `?? ''` 兜底会当场把它认成命中)。诚实拒,不铸空 id ——
|
|
934
|
+
* 与 {@link readCompletedAgentCard} 对 `taskId` 的同款判据一致。
|
|
935
|
+
*/
|
|
936
|
+
export function readAsyncLaunchedAgentReceipt(structured) {
|
|
937
|
+
if (structured === null || typeof structured !== 'object')
|
|
938
|
+
return null;
|
|
939
|
+
const s = structured;
|
|
940
|
+
if (s.type !== 'agent')
|
|
941
|
+
return null;
|
|
942
|
+
if (s.status !== 'async_launched')
|
|
943
|
+
return null;
|
|
944
|
+
const taskId = s.task_id;
|
|
945
|
+
if (typeof taskId !== 'string' || taskId.length === 0)
|
|
946
|
+
return null;
|
|
947
|
+
return {
|
|
948
|
+
taskId,
|
|
949
|
+
...(typeof s.seq === 'number' && Number.isFinite(s.seq) ? { seq: s.seq } : {}),
|
|
950
|
+
};
|
|
951
|
+
}
|
|
920
952
|
/**
|
|
921
953
|
* 把一张 `tool_end.structured` 窄化成 {@link CompletedAgentCard} —— **仅当**它是 `type:"agent"` 的
|
|
922
954
|
* **完成**臂时。不是 agent 卡 / 是 `async_launched` 回执 / 缺 `status` 或 `taskId` ⇒ `null`。
|
package/dist/workflowClient.d.ts
CHANGED
|
@@ -40,7 +40,12 @@ export interface LiveWorkflowController {
|
|
|
40
40
|
}
|
|
41
41
|
export interface LiveWorkflowConfig {
|
|
42
42
|
baseUrl: string;
|
|
43
|
-
|
|
43
|
+
/** Bearer 串,或 `{ mode: 'same-origin-relay' }`([C175],0.29.0)—— 浏览器同源宿主形:凭证由
|
|
44
|
+
* 同源反代承载,`baseUrl` 该形下可为相对路径;直传 {@link makeEngineWireClient} 的同名放宽位
|
|
45
|
+
* (唯一构造点纪律不破:relay 形也走那一个铸口)。串形语义与 0.28.x 字节不变。 */
|
|
46
|
+
authToken: string | {
|
|
47
|
+
mode: 'same-origin-relay';
|
|
48
|
+
};
|
|
44
49
|
/** SDK Principal(workflows are owner-scoped by principal)。缺席=不发 x-agent-principal 头
|
|
45
50
|
* (owner-null,F-011 停发;两侧同缺席仍对齐)。 */
|
|
46
51
|
principal: string | undefined;
|