@sema-agent/client-core 0.12.2 → 0.14.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.
Files changed (131) hide show
  1. package/README.md +15 -2
  2. package/dist/abortableSleep.d.ts +29 -0
  3. package/dist/abortableSleep.js +43 -0
  4. package/dist/adapt/arms.d.ts +67 -0
  5. package/dist/adapt/arms.js +623 -0
  6. package/dist/adapt/ids.d.ts +22 -0
  7. package/dist/adapt/ids.js +34 -0
  8. package/dist/adapt/instanceLedger.d.ts +36 -0
  9. package/dist/adapt/instanceLedger.js +50 -0
  10. package/dist/adapt/panelTasks.d.ts +59 -0
  11. package/dist/adapt/panelTasks.js +193 -0
  12. package/dist/adapt/textStream.d.ts +63 -0
  13. package/dist/adapt/textStream.js +141 -0
  14. package/dist/adapt/toolCards.d.ts +65 -0
  15. package/dist/adapt/toolCards.js +100 -0
  16. package/dist/adapt/turnFlags.d.ts +37 -0
  17. package/dist/adapt/turnFlags.js +55 -0
  18. package/dist/adapt/wireShapes.d.ts +96 -0
  19. package/dist/adapt/wireShapes.js +167 -0
  20. package/dist/adapt.d.ts +33 -60
  21. package/dist/adapt.js +84 -1207
  22. package/dist/adapter/downstream/eventToSdkMessage.d.ts +57 -13
  23. package/dist/adapter/downstream/eventToSdkMessage.js +174 -106
  24. package/dist/adapter/downstream/terminalToSdkResult.js +151 -160
  25. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +36 -0
  26. package/dist/adapter/downstream/turnUsageToModelUsage.js +34 -6
  27. package/dist/adapter/runStream.d.ts +36 -4
  28. package/dist/adapter/runStream.js +195 -13
  29. package/dist/adapter/types.d.ts +28 -2
  30. package/dist/agentSession/backgroundView.js +4 -15
  31. package/dist/agentsWireCaps.d.ts +10 -6
  32. package/dist/agentsWireCaps.js +21 -7
  33. package/dist/argvFlagValue.d.ts +41 -0
  34. package/dist/argvFlagValue.js +69 -0
  35. package/dist/attachmentsWireCaps.d.ts +3 -2
  36. package/dist/attachmentsWireCaps.js +5 -3
  37. package/dist/classifierVerdictWire.d.ts +8 -0
  38. package/dist/classifierVerdictWire.js +8 -0
  39. package/dist/cloudConfigWireCaps.d.ts +28 -1
  40. package/dist/cloudConfigWireCaps.js +51 -11
  41. package/dist/controlRouter.d.ts +14 -8
  42. package/dist/controlRouter.js +15 -21
  43. package/dist/detachWire.d.ts +15 -5
  44. package/dist/detachWire.js +17 -7
  45. package/dist/effortWire.d.ts +0 -21
  46. package/dist/effortWire.js +6 -20
  47. package/dist/engineInlineTaskStats.d.ts +12 -6
  48. package/dist/engineWireSdk.d.ts +12 -0
  49. package/dist/env/localeGeo.js +2 -1
  50. package/dist/envFlag.d.ts +39 -0
  51. package/dist/envFlag.js +51 -0
  52. package/dist/finalVerifyWire.d.ts +7 -5
  53. package/dist/finalVerifyWire.js +6 -5
  54. package/dist/fleet/fleetLedger.d.ts +32 -9
  55. package/dist/fleet/fleetLedger.js +119 -34
  56. package/dist/fleet/fleetProjection.d.ts +44 -6
  57. package/dist/fleet/fleetProjection.js +52 -10
  58. package/dist/fleetAgentPanelProjection.d.ts +18 -1
  59. package/dist/fleetAgentPanelProjection.js +61 -14
  60. package/dist/forkWireCaps.d.ts +2 -1
  61. package/dist/forkWireCaps.js +5 -11
  62. package/dist/goalStopHook.d.ts +142 -0
  63. package/dist/goalStopHook.js +258 -0
  64. package/dist/headlessPermissionModeWire.d.ts +10 -0
  65. package/dist/headlessPermissionModeWire.js +24 -21
  66. package/dist/headlessReconnectWire.d.ts +7 -1
  67. package/dist/headlessReconnectWire.js +20 -2
  68. package/dist/hitl/approvalsFeed.d.ts +22 -1
  69. package/dist/hitl/approvalsFeed.js +103 -9
  70. package/dist/hitl/askGateWire.d.ts +27 -96
  71. package/dist/hitl/askGateWire.js +69 -618
  72. package/dist/hitl/frameRouter.d.ts +86 -0
  73. package/dist/hitl/frameRouter.js +383 -0
  74. package/dist/hitl/gateLedger.d.ts +119 -0
  75. package/dist/hitl/gateLedger.js +113 -0
  76. package/dist/hitl/hitlBridge.d.ts +75 -15
  77. package/dist/hitl/hitlBridge.js +94 -22
  78. package/dist/hitl/hitlHostSurface.d.ts +49 -0
  79. package/dist/hitl/hitlHostSurface.js +155 -0
  80. package/dist/hitl/parkResolver.d.ts +74 -0
  81. package/dist/hitl/parkResolver.js +250 -0
  82. package/dist/hitl/planReviewWire.d.ts +60 -2
  83. package/dist/hitl/planReviewWire.js +197 -91
  84. package/dist/hitl/toolApprovalWire.d.ts +67 -4
  85. package/dist/hitl/toolApprovalWire.js +129 -31
  86. package/dist/hooksWireCaps.d.ts +1 -82
  87. package/dist/hooksWireCaps.js +44 -237
  88. package/dist/host.d.ts +16 -5
  89. package/dist/index.d.ts +2 -0
  90. package/dist/index.js +15 -1
  91. package/dist/interactiveToolsWire.d.ts +15 -4
  92. package/dist/interactiveToolsWire.js +24 -26
  93. package/dist/limitsWire.js +7 -40
  94. package/dist/liveInitToolFace.d.ts +51 -6
  95. package/dist/liveQuestionStore.d.ts +18 -6
  96. package/dist/liveQuestionStore.js +15 -0
  97. package/dist/model/providerPresets.js +11 -1
  98. package/dist/notifications.d.ts +62 -2
  99. package/dist/notifications.js +306 -49
  100. package/dist/printToolResultFrame.d.ts +12 -4
  101. package/dist/printToolResultFrame.js +11 -21
  102. package/dist/retainBackgroundWireCaps.d.ts +3 -2
  103. package/dist/retainBackgroundWireCaps.js +5 -9
  104. package/dist/sandboxWire.d.ts +9 -31
  105. package/dist/sandboxWire.js +51 -50
  106. package/dist/scenarioWire.d.ts +1 -1
  107. package/dist/scenarioWire.js +25 -36
  108. package/dist/seam.d.ts +23 -6
  109. package/dist/seam.js +40 -30
  110. package/dist/seatContract.d.ts +369 -83
  111. package/dist/seatContract.js +585 -198
  112. package/dist/selfOrchestrationWireCaps.d.ts +6 -5
  113. package/dist/selfOrchestrationWireCaps.js +8 -12
  114. package/dist/sessionSlot.d.ts +8 -0
  115. package/dist/sessionSlot.js +1 -0
  116. package/dist/steering.js +2 -2
  117. package/dist/subagent/engineTaskHandleWire.d.ts +3 -0
  118. package/dist/subagent/engineTaskHandleWire.js +17 -2
  119. package/dist/subagentContentStore.d.ts +5 -5
  120. package/dist/toolResult.d.ts +89 -8
  121. package/dist/toolResult.js +99 -30
  122. package/dist/typePins.d.ts +17 -0
  123. package/dist/typePins.js +1 -0
  124. package/dist/ultracodeWireCaps.js +5 -6
  125. package/dist/unrefTimer.d.ts +30 -0
  126. package/dist/unrefTimer.js +5 -0
  127. package/dist/workflow.d.ts +3 -2
  128. package/dist/workflow.js +3 -2
  129. package/dist/workflowClient.d.ts +7 -0
  130. package/dist/workflowClient.js +47 -12
  131. package/package.json +3 -3
@@ -0,0 +1,34 @@
1
+ import { deriveTranscriptId } from '../seam.js';
2
+ /** 主车道证明——本批实现的臂全在 leader lane(子代/workflow lane 的 tick 面留 #52b)。 */
3
+ export const MAIN = { lane: 'main' };
4
+ export const chrome = (event) => ({ plane: 'chrome', event });
5
+ /**
6
+ * 🔴 入参从裸 `unknown` 收窄成 `Frame`(拆分同批的真收窄,不是搬运):调用点全是「按 CC 词汇
7
+ * 现拼的对象字面量」或「原样透传的 wire 帧」,两者都是 `Record<string, unknown>` ——
8
+ * 裸 `unknown` 把窄化义务外包给了每一个调用点,而这里本来就知道自己收的是什么形。
9
+ * 到 `SDKMessage` 的那一步仍是 cast:CC 词汇的字面形与 SDK 类型有已登记的分歧(DIVERGENCE-2/9),
10
+ * 这条 cast 是**有意**的边界,不是偷懒。
11
+ */
12
+ export const transcript = (message) => ({
13
+ plane: 'transcript',
14
+ message: message,
15
+ });
16
+ /**
17
+ * [1653]:稳定键优先序 = 帧 id > seq > toolCallId,全缺才 ctx.uuid()。现役 wire 的稳定键落在
18
+ * `uuid` 字段(§E2 core 铸的 eventId / stamp 铸的帧 id),故映到 `id` 位喂进去(见 SEAM-GAP-1)。
19
+ * `suffix` = 同一帧派生多条消息时的去撞位(如 tool_use 卡 + 同帧的 reject 结果)。
20
+ */
21
+ export function makeIdOf(ctx) {
22
+ return (frame, suffix) => {
23
+ const base = typeof frame.id === 'string' && frame.id.length > 0
24
+ ? frame.id
25
+ : typeof frame.uuid === 'string' && frame.uuid.length > 0
26
+ ? frame.uuid
27
+ : undefined;
28
+ return deriveTranscriptId({
29
+ ...(base !== undefined ? { id: suffix !== undefined ? `${base}#${suffix}` : base } : {}),
30
+ ...(typeof frame.seq === 'number' ? { seq: frame.seq } : {}),
31
+ ...(typeof frame.toolCallId === 'string' ? { toolCallId: frame.toolCallId } : {}),
32
+ }, ctx);
33
+ };
34
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * adapt/instanceLedger.ts — **适配器实例级台账**(A 族拆分 REF-CC-FIX-05;矩阵 §1.5)。
3
+ *
4
+ * 三个 Set 的持有者。它们与 turn 级状态的区别是**跨 turn 存活**:
5
+ * · `renderedNotifications` —— 通知去重键(#63 的 export/import 往返面就是它);
6
+ * · `seenWorkflowToolCallIds` —— W1 三级判据③ 的素材(assistant 臂写、task_progress 臂读,
7
+ * 覆盖 uuid 形子代 id 与**跨 turn 迟到 tick**,所以必须跨 turn 活着);
8
+ * · `firedSubagentStartHookTaskIds` —— SubagentStart 一生只许一次(frame-lane-matrix #4),
9
+ * 同时是 task_notification 臂发 stop 的门。
10
+ *
11
+ * 🔴 FIX-05 就是本文件存在的理由:拆分前这三个 Set 挂在类上,`adapt()` 里既有 `self.` 又有
12
+ * `this.` **两条访问路径**(generator 表达式的 this 不是适配器,所以当时固定了一个 `const self`)。
13
+ * 收成一个显式入参对象之后:① `self` 别名与它那条 eslint-disable 一起消失;② 三个 Set 的
14
+ * 每一个读写点都必须经 `inst.` —— 「同一台账两条路径」这件事在结构上不再可能。
15
+ *
16
+ * 🔴 DIVERGENCE-6 的现势(别把它读成「本包所有台账都是实例级」):只有本文件这三件是实例级
17
+ * (= 一个 session 一份)。`notifiedRuns` / `cardEnqueuedRuns` 是 **module 台账**(notifications.ts),
18
+ * 因为它们的配对半场(队列 port / idle watcher / outstanding 药丸)在那儿 —— 放实例 =
19
+ * 前提不共享 = 双卡/零显示/模型二收(0.6.0 真缺口,[1857])。
20
+ */
21
+ export interface AdapterInstanceLedger {
22
+ /** 通知去重键:已渲染过? */
23
+ hasRenderedNotification(key: string): boolean;
24
+ /** 通知去重键:记一条(**必须在对应 yield 之前**落,见矩阵 §2.1④ 的 1377 行判定)。 */
25
+ addRenderedNotification(key: string): void;
26
+ /** 台账导出/回植用的快照与批量吃入(返回真正新吃进去的条数)。 */
27
+ listRenderedNotifications(): string[];
28
+ takeRenderedNotifications(keys: Iterable<unknown>): number;
29
+ /** W1 判据③:这个 tool_use id 是本会话见过的 Workflow 工具卡? */
30
+ isWorkflowCard(toolUseId: string): boolean;
31
+ markWorkflowCard(toolUseId: string): void;
32
+ /** SubagentStart 一生一次的门(fire 前判、fire 时记,两步都在 yield 之前)。 */
33
+ hasFiredSubagentStart(taskId: string): boolean;
34
+ markFiredSubagentStart(taskId: string): void;
35
+ }
36
+ export declare function createAdapterInstanceLedger(): AdapterInstanceLedger;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * adapt/instanceLedger.ts — **适配器实例级台账**(A 族拆分 REF-CC-FIX-05;矩阵 §1.5)。
3
+ *
4
+ * 三个 Set 的持有者。它们与 turn 级状态的区别是**跨 turn 存活**:
5
+ * · `renderedNotifications` —— 通知去重键(#63 的 export/import 往返面就是它);
6
+ * · `seenWorkflowToolCallIds` —— W1 三级判据③ 的素材(assistant 臂写、task_progress 臂读,
7
+ * 覆盖 uuid 形子代 id 与**跨 turn 迟到 tick**,所以必须跨 turn 活着);
8
+ * · `firedSubagentStartHookTaskIds` —— SubagentStart 一生只许一次(frame-lane-matrix #4),
9
+ * 同时是 task_notification 臂发 stop 的门。
10
+ *
11
+ * 🔴 FIX-05 就是本文件存在的理由:拆分前这三个 Set 挂在类上,`adapt()` 里既有 `self.` 又有
12
+ * `this.` **两条访问路径**(generator 表达式的 this 不是适配器,所以当时固定了一个 `const self`)。
13
+ * 收成一个显式入参对象之后:① `self` 别名与它那条 eslint-disable 一起消失;② 三个 Set 的
14
+ * 每一个读写点都必须经 `inst.` —— 「同一台账两条路径」这件事在结构上不再可能。
15
+ *
16
+ * 🔴 DIVERGENCE-6 的现势(别把它读成「本包所有台账都是实例级」):只有本文件这三件是实例级
17
+ * (= 一个 session 一份)。`notifiedRuns` / `cardEnqueuedRuns` 是 **module 台账**(notifications.ts),
18
+ * 因为它们的配对半场(队列 port / idle watcher / outstanding 药丸)在那儿 —— 放实例 =
19
+ * 前提不共享 = 双卡/零显示/模型二收(0.6.0 真缺口,[1857])。
20
+ */
21
+ export function createAdapterInstanceLedger() {
22
+ const renderedNotifications = new Set();
23
+ const seenWorkflowToolCallIds = new Set();
24
+ const firedSubagentStartHookTaskIds = new Set();
25
+ return {
26
+ hasRenderedNotification: (key) => renderedNotifications.has(key),
27
+ addRenderedNotification: (key) => {
28
+ renderedNotifications.add(key);
29
+ },
30
+ listRenderedNotifications: () => [...renderedNotifications],
31
+ takeRenderedNotifications: (keys) => {
32
+ let n = 0;
33
+ for (const k of keys) {
34
+ if (typeof k === 'string' && k.length > 0 && !renderedNotifications.has(k)) {
35
+ renderedNotifications.add(k);
36
+ n++;
37
+ }
38
+ }
39
+ return n;
40
+ },
41
+ isWorkflowCard: (toolUseId) => seenWorkflowToolCallIds.has(toolUseId),
42
+ markWorkflowCard: (toolUseId) => {
43
+ seenWorkflowToolCallIds.add(toolUseId);
44
+ },
45
+ hasFiredSubagentStart: (taskId) => firedSubagentStartHookTaskIds.has(taskId),
46
+ markFiredSubagentStart: (taskId) => {
47
+ firedSubagentStartHookTaskIds.add(taskId);
48
+ },
49
+ };
50
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * adapt/panelTasks.ts — M3 **面板行台账**(A 族拆分 REF-CC-SPLIT-04;矩阵 §1.3 六行状态)。
3
+ *
4
+ * 收编 `taskCardBinding` / `boundToolCalls` / `livePanelTasks` / `endedPanelTasks` /
5
+ * `liveBoundPanelTasks` / `subagentTypeById` 六行状态 + C5 laneOfTask / C6 bindTaskCard /
6
+ * C7 unboundSubagentCandidates / C8 fireSubagentStart / C9 settlePanelTasks 五个闭包。
7
+ *
8
+ * 引擎侧子代的 tick 只带 rollup 数字({toolUses,totalTokens}) = CC `AgentProgress` 同形,
9
+ * 所以忠实的 CC-187 面是 footer 的 CoordinatorTaskPanel(CC 自己对进程外 agent 的先例:
10
+ * 面板行 + task-notification,从不合成 inline 消息轨)。本模块把「tick → 哪张委派卡」的归属、
11
+ * 行的活性(live/inert/unbound)、以及三处 sweep 全部管起来。
12
+ *
13
+ * 🔴 拆分定案两条(矩阵 §3.2 #4/#6):
14
+ * ① `endedPanelTasks` 拆分前被 `settlePanelTasks` **和** task_notification 臂两处直写。
15
+ * 现在通知臂那半场收成 `settleFromNotification()`(clearResident + panel end +
16
+ * endedPanelTasks 判重 + if-started stop 整段),通知臂只调一次 ⇒ 跨臂直写消失。
17
+ * ② `firedSubagentStartHookTaskIds` 的读者(通知臂)随之进了本模块内部 —— 通知臂**不再直读**
18
+ * 实例台账,那条跨模块读同批消失。
19
+ *
20
+ * 🔴 module 台账 `engineAgentPanelStore` 的两个写口(mark/clearEnginePanelTaskResident)拆分前有
21
+ * A4/A9 **双所有者**。按记忆 `paired-mechanisms-must-share-premise` 的「整臂让位」:两处写口
22
+ * 现在**在 adapt 域内只在本文件**(`noteResidency()` 与 `settleFromNotification()`),不许 A 层
23
+ * 两个模块各写各的。
24
+ * ⚠️ ADAPT-F4 判据纠偏(2026-08-02):原文写的是「只在本文件」,而 `src/notifications.ts:915`
25
+ * 的 bg 合成半场也调 `clearEnginePanelTaskResident`(它自陈是「真终态唯二来源」之一,是设计内的
26
+ * 另一条腿、不是被劈开的臂)。照字面 grep 验这条判据的人当场得到证伪,只能二选一:误判成回归去
27
+ * 删 notifications 那半场,或判定这条钉不可信而整条丢掉 —— 两条都是坏结局。判据的作用域必须写准。
28
+ *
29
+ * 🔴 时序留钉(矩阵 §2.1②):`start()` 里 `markFiredSubagentStart` 必须在 `yield` **之前** ——
30
+ * yield* 展开后判重+记号与交付在**同一次** `.next()` 里,「已标记」⟺「已交付」原子成立;
31
+ * 改成 add-after-yield ⇒ 消费者收到 start 后早退就没记上 ⇒ 下一 turn 重 fire,直接违反「一生一次」。
32
+ */
33
+ import type { AdapterContext, AdapterOutput, LaneProof } from '../seam.js';
34
+ import type { AdapterInstanceLedger } from './instanceLedger.js';
35
+ import type { OpenCardView } from './toolCards.js';
36
+ export interface PanelTaskLedger {
37
+ /** T32 行绑定(typed 双键在场恒赢;缺席时 FIFO 猜父)。返回绑到的卡 id。 */
38
+ bind(taskId: string, parentToolCallId?: string): string | undefined;
39
+ /** 未绑的 subagent 形开卡张数(>1 ⇒ 含糊,上层保持 unbound)。 */
40
+ unboundCandidates(): number;
41
+ /** 行的车道证明:绑到卡的 = subagent lane(带父卡 id),未绑的 = 主 lane。 */
42
+ laneOf(taskId: string): LaneProof;
43
+ /** 这条行已经绑过卡了吗(含糊回落的前置判位;别拿 `laneOf(...) === MAIN` 代替 —— 那是
44
+ * 按引用相等在推断,判据锚在会决定结果的量上才对)。 */
45
+ isBound(taskId: string): boolean;
46
+ /** 本 turn 发布过面板行了吗(首 tick 判别 = 子代在壳侧可观测的起点)。 */
47
+ isLive(taskId: string): boolean;
48
+ /** 登记「本 turn 发布过面板行」。 */
49
+ markLive(taskId: string): void;
50
+ /** #6 常驻台账:live-bound ⇒ 清常驻;inert/unbound ⇒ 记常驻(module store 的**唯一**写口之一)。 */
51
+ noteResidency(taskId: string): void;
52
+ /** SubagentStart(类型恒记,fire 一生一次)。 */
53
+ start(taskId: string, agentType: string): Generator<AdapterOutput>;
54
+ /** 关卡 settle(cardId)或 turn 末/abort 防御 sweep(cardId===null)。 */
55
+ settle(cardId: string | null, isError: boolean, report?: string): Generator<AdapterOutput>;
56
+ /** #6 通知-settle 边(推送半场):真终态在此落行,session 常驻行不再被 turn sweep 假结。 */
57
+ settleFromNotification(taskId: string, isError: boolean): Generator<AdapterOutput>;
58
+ }
59
+ export declare function createPanelTaskLedger(ctx: AdapterContext, cards: OpenCardView, inst: AdapterInstanceLedger): PanelTaskLedger;
@@ -0,0 +1,193 @@
1
+ import { clearEnginePanelTaskResident, markEnginePanelTaskResident } from '../engineAgentPanelStore.js';
2
+ import { chrome, MAIN } from './ids.js';
3
+ import { SUBAGENT_TOOL_NAMES } from './wireShapes.js';
4
+ export function createPanelTaskLedger(ctx, cards, inst) {
5
+ /** taskId → 绑定的 open tool_use id。 */
6
+ const taskCardBinding = new Map();
7
+ /** 已被某 taskId 认领的 tool_use id(一张卡只认一个子代)。 */
8
+ const boundToolCalls = new Set();
9
+ /** 本 turn 发布过面板行的全部 taskId(绑定与否都算)。 */
10
+ const livePanelTasks = new Set();
11
+ /** 已落终态的行(settle 幂等)。 */
12
+ const endedPanelTasks = new Set();
13
+ /**
14
+ * #6 —— 绑定时那张卡**正开着**的 taskId(= "live" 绑定)。只有这些行属本 turn 生命周期、
15
+ * 可被 turn 末防御 sweep 结掉。inert(显式父指向已关/没见过的卡 —— 跨 turn bg 子代的典型形)
16
+ * 与 unbound 行是 **session 常驻**:终态只认 task_notification / bg_notification。
17
+ */
18
+ const liveBoundPanelTasks = new Set();
19
+ /** taskId → 子代类型(Stop hook 查它;cli 同为 turn 局部)。 */
20
+ const subagentTypeById = new Map();
21
+ const laneOf = (taskId) => {
22
+ const card = taskCardBinding.get(taskId);
23
+ return card !== undefined ? { lane: 'subagent', parentToolCallId: card } : MAIN;
24
+ };
25
+ return {
26
+ laneOf,
27
+ isBound: (taskId) => taskCardBinding.has(taskId),
28
+ /**
29
+ * T32 行绑定。**typed 双键在场恒赢**(B6 定案):wire 点名了 `parentToolCallId` 时,下面的
30
+ * FIFO 臂**一次都不会被考虑** —— 连「点名的卡不合格」那一支也是直接返回 unbound,而不是
31
+ * 退回去猜。判据锚在这里:显式支的两条出口(绑定 / 拒绝)都不落进 else。
32
+ */
33
+ bind(taskId, parentToolCallId) {
34
+ const existing = taskCardBinding.get(taskId);
35
+ if (existing !== undefined)
36
+ return existing;
37
+ let target;
38
+ if (parentToolCallId !== undefined) {
39
+ // P1② 精确绑定 —— wire 点名了委派卡。信但不无条件信([1617]①,frame-lane-matrix #1):
40
+ // 被点名的卡**开着**且**不是** subagent 形工具(Task/Agent/Fork)时拒绝绑定 —— 否则
41
+ // workflow/Bash 卡一关就会把这条行 settle 掉、还会收走它的 inline stats(错归因)。
42
+ // 行保持 unbound(taskId 键的 rollup 仍是真的)。已关/没见过的 id 仍**惰性绑定**(inert):
43
+ // 终态由通知落,严格优于错绑另一张开着的卡。
44
+ const named = cards.peek(parentToolCallId);
45
+ if (named !== undefined && !SUBAGENT_TOOL_NAMES.has(named.name))
46
+ return undefined;
47
+ target = parentToolCallId;
48
+ }
49
+ else {
50
+ // ── old-engine fallback(FIFO 猜父)—— B6 P1-2 归属定案:**不删** ────────────────────
51
+ // 🔴 判据先摆正:这条臂不是「P1-2 之前的临时权宜」,它是 **tolerate-absent 的保护臂**。
52
+ // 决定后果方向的量 = 「这条子代行有没有归属」:
53
+ // · 回落在 ⇒ 老引擎(不发 parentToolCallId)上行仍绑到唯一那张开着的委派卡 ⇒ 行有归属、
54
+ // inline stats 有去处、关卡时被 settle;
55
+ // · 回落删掉 ⇒ 老引擎上行恒 unbound ⇒ 转录里那条群组行永远不长统计、卡关了也不结,
56
+ // 用户看到一条转圈到 turn 末的行。
57
+ // ⇒ **保护型缺席 = 漏保护**,与 `parked → awaiting approval` 同判(不删)。
58
+ // 猜错的风险已被两道门收窄:只考虑**未绑**且**subagent 形**的卡;≥2 张候选时上层
59
+ // 直接判含糊、保持 unbound(见 task_progress 臂的 unboundCandidates() > 1)。
60
+ // 退役条件(登记在 compensations.ts T32):P1-2 到货 **且** 达标线 ≥ server 1.290 —— 即
61
+ // 「不再支持不发 typed 双键的引擎」那一天,才是删它的日子;只要还能连老引擎就得留着。
62
+ // 覆盖:diff 门 14 条 task_progress 帧里有 6 条不带 parentToolCallId(其中 2 条走 workflow
63
+ // lane 门、不到这里),真正压这条回落臂的是 4 条(FIFO 命中 / 含糊拒绝 / settle / 跨 turn)。
64
+ // 🔴 依赖 `cards.entries()` 的**开卡顺序**(Map 插入序 = FIFO 语义本身),换成任何
65
+ // 无序容器都会静默改判别结果。
66
+ for (const [id, p] of cards.entries()) {
67
+ if (!boundToolCalls.has(id) && SUBAGENT_TOOL_NAMES.has(p.name)) {
68
+ target = id;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ if (target === undefined)
74
+ return undefined;
75
+ taskCardBinding.set(taskId, target);
76
+ boundToolCalls.add(target);
77
+ // #6 live vs inert:只有绑到**此刻开着**的卡才把行系进本 turn 的生命周期
78
+ // (FIFO 目标恒开着;显式目标可能已关 = inert)。
79
+ if (cards.has(target))
80
+ liveBoundPanelTasks.add(taskId);
81
+ // cli 的 `SEMA_BIND_PROBE` 文件探针 → 注入口(载荷逐字同形,见 ADAPTER_DIVERGENCES-5)。
82
+ ctx.probe?.('bind', `bind ${taskId} -> ${target} via=${parentToolCallId !== undefined ? 'explicit' : 'fifo'}`);
83
+ return target;
84
+ },
85
+ /** 对抗复审修(w0zwpa251 #1):≥2 张未绑的 subagent 形卡开着且 wire 没点名 ⇒ 任何猜都是错归因。 */
86
+ unboundCandidates() {
87
+ let n = 0;
88
+ for (const [id, p] of cards.entries()) {
89
+ if (!boundToolCalls.has(id) && SUBAGENT_TOOL_NAMES.has(p.name))
90
+ n++;
91
+ }
92
+ return n;
93
+ },
94
+ isLive: (taskId) => livePanelTasks.has(taskId),
95
+ markLive: (taskId) => {
96
+ livePanelTasks.add(taskId);
97
+ },
98
+ // #6 session 常驻台账:live-bound(卡本 turn 开着)= 本 turn 生命周期,turn 末可 sweep;
99
+ // inert/unbound(跨 turn bg 子代形)= 常驻,两处 sweep 都放行,终态只认通知帧。
100
+ noteResidency: (taskId) => {
101
+ if (liveBoundPanelTasks.has(taskId))
102
+ clearEnginePanelTaskResident(taskId);
103
+ else
104
+ markEnginePanelTaskResident(taskId);
105
+ },
106
+ /** SubagentStart(cli fireSubagentStartHook 的臂化):类型恒记,fire 一生一次。 */
107
+ *start(taskId, agentType) {
108
+ subagentTypeById.set(taskId, agentType); // Stop 的类型查找恒记(fire 与否无关)
109
+ // 🔴 判重 + 记号都在**唯一那条 yield 之前**(矩阵 §2.1②:零残余窗,别挪)。
110
+ if (inst.hasFiredSubagentStart(taskId))
111
+ return;
112
+ inst.markFiredSubagentStart(taskId);
113
+ ctx.probe?.('hook', `start ${taskId} type=${agentType}`);
114
+ yield chrome({ kind: 'subagent_lifecycle', laneProof: laneOf(taskId), phase: 'start', taskId, agentType });
115
+ },
116
+ /**
117
+ * 结掉一张关闭中的卡所绑的面板行(`cardId`),或 turn 末/abort 结掉**本 turn 活绑**的全部行
118
+ * (`cardId===null` 的防御 sweep —— 子代从不发终态 tick)。
119
+ * 拍1 查看态:显式关卡臂携委派卡的 output(= 子代最终报告)到 end 事件上;sweep 无报告。
120
+ */
121
+ *settle(cardId, isError, report) {
122
+ // inline 群组行孪生(clay dogfood 2026-07-03):冻结该卡的 tick 终值,免得"N agents finished"
123
+ // 行把真实工具数/token 重置回 0。null-cardId 的 sweep 结掉每一条活着的 inline 条目。
124
+ if (cardId !== null) {
125
+ yield chrome({ kind: 'inline_task_stats', laneProof: { lane: 'subagent', parentToolCallId: cardId }, op: 'settle', cardId, isError });
126
+ }
127
+ else {
128
+ yield chrome({ kind: 'inline_task_stats', laneProof: MAIN, op: 'settle_all' });
129
+ }
130
+ for (const taskId of livePanelTasks) {
131
+ if (cardId !== null && taskCardBinding.get(taskId) !== cardId)
132
+ continue;
133
+ // #6 —— 防御 sweep 只结**本 turn 活绑**的行(流被截断的形)。session 常驻行
134
+ // (inert 绑定 / unbound = 跨 turn bg 子代)绝不在这里冻成假 completed:它的终态真相
135
+ // 由 task_notification / bg_notification 落(通知-settle 边)。
136
+ if (cardId === null && !liveBoundPanelTasks.has(taskId))
137
+ continue;
138
+ if (endedPanelTasks.has(taskId))
139
+ continue;
140
+ endedPanelTasks.add(taskId);
141
+ const lane = laneOf(taskId);
142
+ const agentType = subagentTypeById.get(taskId);
143
+ yield chrome({
144
+ kind: 'subagent_lifecycle',
145
+ laneProof: lane,
146
+ phase: 'stop',
147
+ taskId,
148
+ ...(agentType !== undefined ? { agentType } : {}),
149
+ });
150
+ yield chrome({
151
+ kind: 'panel_task',
152
+ laneProof: lane,
153
+ event: {
154
+ kind: 'end',
155
+ taskId,
156
+ isError,
157
+ ...(cardId !== null && report !== undefined ? { report } : {}),
158
+ },
159
+ });
160
+ }
161
+ },
162
+ /**
163
+ * 通知臂(task_notification)那半场,整段收进来 —— 拆分前它在臂里直写 `endedPanelTasks`
164
+ * 与直读 `firedSubagentStartHookTaskIds`,是矩阵点名的两条跨模块面。
165
+ * 产出与判重次序**逐字保持**拆分前的形:先清常驻 → 发 end 行 → 再判重 → 再 if-started stop。
166
+ */
167
+ *settleFromNotification(taskId, isError) {
168
+ // #6 通知-settle 边(推送半场):session 常驻的 bg 子代行不再被 turn sweep 假结,
169
+ // 真终态在此落行(failed/killed ⇒ isError 真归因)。
170
+ clearEnginePanelTaskResident(taskId);
171
+ yield chrome({
172
+ kind: 'panel_task',
173
+ laneProof: MAIN,
174
+ event: { kind: 'end', taskId, isError },
175
+ });
176
+ if (!endedPanelTasks.has(taskId)) {
177
+ endedPanelTasks.add(taskId);
178
+ // SubagentStop 只对**真 fire 过 Start** 的子代(workflow runId / 外来 id 不配对乱 fire)。
179
+ // cli 同门 `firedSubagentStartHookTaskIds.has(taskId)` —— B4 前本臂无条件发 stop,
180
+ // 宿主若照 guard 注释自建台账才不会乱 fire;台账在库里就该库来判。
181
+ if (inst.hasFiredSubagentStart(taskId)) {
182
+ yield chrome({
183
+ kind: 'subagent_lifecycle',
184
+ laneProof: MAIN,
185
+ phase: 'stop',
186
+ taskId,
187
+ guard: 'if-started',
188
+ });
189
+ }
190
+ }
191
+ },
192
+ };
193
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * adapt/textStream.ts — M1 **流合并器**(A 族拆分 REF-CC-SPLIT-02;矩阵 §1.1 十二行状态)。
3
+ *
4
+ * 收编 ADAPT-RW-MATRIX §1.1 的 12 个变量(answer / answerSegment / thinking /
5
+ * emittedAssistantText / textPending / thinkingPending / textBlockOpen / thinkingBlockOpen /
6
+ * lastFlushAt / flushEveryMs / segmentAnchor / thinkingAnchor)+ 三个闭包(C12 takeThinking /
7
+ * C13 takeAnswerSegment / C14 drainLive)+ 两处原本长在驱动壳里的判据:
8
+ * · `hasPendingContent()` = D3 IDLE-FLUSH 竞速的四项 or(矩阵说的「接口化即封闭」);
9
+ * · `maybeFlushOnBeat(now)` = D5 COALESCER 节拍尾。
10
+ * 收进来之后这 12 行里**只剩两行**还跨模块(矩阵 §1.8):`answer` 与 `emittedAssistantText` ——
11
+ * 它们由 `appendAnswer()` / `markEmittedText()` 两个具名动作暴露给 assistant / result 两臂,
12
+ * 而不是把裸变量交出去(裸 `= true` 在拆分后没有任何人能追得住)。
13
+ *
14
+ * 🔴 不变量(段序铁律,本包最高不变量之一):**活体增量 → 思考 committed → 文本段 committed**。
15
+ * 三个 take/drain 的**相对次序**由调用点决定,本模块只保证每一个自身的内部序;拆分后
16
+ * 收口三形(result 臂 / abort 尾 / T60 尾)的次序差异登记在 ADAPT-RW-MATRIX §2.4,别归一。
17
+ *
18
+ * 🔴 时序坑(矩阵 §2.3 ⑤,拆分专属):`drainLive()` 里两处 `pending = ''` 在 `yield` **之后**,
19
+ * 载荷是在 yield 之前**按值捕获**的,所以逻辑正确。**机械地把清空前移会读到已清空的串** ——
20
+ * 谁想「统一成 reset-before-yield」,先读这一条。
21
+ */
22
+ import type { AdapterContext, AdapterOutput } from '../seam.js';
23
+ import { type Frame, type IdOf } from './ids.js';
24
+ /** M1 对外的六个动作 + 两个读位(矩阵 §3.2 的 #2/#3 两条跨模块接口就是最后那三件)。 */
25
+ export interface TextStream {
26
+ /** A5 `text_delta` 半场:MOD-1 思考→回答边界(思考在场就先 committed 上屏)+ 段锚 + 三缓冲累加。 */
27
+ feedText(delta: string, frame: Frame): Generator<AdapterOutput>;
28
+ /** A5 `thinking_delta` 半场:首条推理增量点亮活动行(P2d elapsed 锚)。 */
29
+ feedThinking(delta: string, frame: Frame): Generator<AdapterOutput>;
30
+ /** 合并后的活体增量下泄(思考先于文本 = 持久臂序)。 */
31
+ drainLive(): Generator<AdapterOutput>;
32
+ /** 提交累积的思考块。 */
33
+ takeThinking(): Generator<AdapterOutput>;
34
+ /** 提交累积的文本段(#27 段序铁律)。 */
35
+ takeAnswerSegment(): Generator<AdapterOutput>;
36
+ /** D3 IDLE-FLUSH 竞速判据:四个缓冲有任何一个非空。 */
37
+ hasPendingContent(): boolean;
38
+ /** D5 攒批节拍:一个间隔至多一次 flush(判据与 `lastFlushAt` 一起收在本模块内)。 */
39
+ maybeFlushOnBeat(nowMs: number): Generator<AdapterOutput>;
40
+ /**
41
+ * A1 durable text 臂:把整块 text 计进 `answer`(它不走增量通道)。
42
+ *
43
+ * ── ADAPT-F8 留/删裁定(2026-08-02;矩阵 §338 点名要的那条裁定,此前仓内零落痕)──────────
44
+ * 事实:`answer` 的**唯一读者**是驱动壳 D8 收口的 `ctx.log?.('debug', … answerLen=…)`;拆分把
45
+ * 旧本一行 `void answer`(自陈「保留供调试/后续批」)换成了 `appendAnswer` + `answerLength`
46
+ * 两个模块级出口 —— 净效果与矩阵意图相反(包袱没裁,反而升了格)。
47
+ * **裁定 = 留**,理由与代价一起记明:
48
+ * · 留:D8 那行是「这个 turn 到底产没产出文本」的**唯一**分诊锚,而它恰好是本域最常翻车的
49
+ * 一类现象(终答兜底/段序/markEmittedText 三处任一漏配都在这里现形)。删掉等于把一条真在
50
+ * 用的诊断腿换成「下次再从头加仪器」。它不是「不报错的妥协」,是有读者的可观测面。
51
+ * · 代价(认领,不粉饰):两个出口是 turn 级状态的模块 API 面,`appendAnswer` 的调用点漂了
52
+ * 没有门会说话(答案长度只进 debug 行,没人对账)。所以它**不许再长第三个读者** —— 想读
53
+ * 正文的人去读段缓冲,别把这个累加器当答案来源。
54
+ */
55
+ appendAnswer(text: string): void;
56
+ /** 「本 turn 已经产过 assistant 文本」的具名写口(A1 durable / A7 终答兜底两处)。 */
57
+ markEmittedText(): void;
58
+ /** A7 终答兜底的门 + D8 收口日志读位。 */
59
+ readonly emittedAssistantText: boolean;
60
+ /** D8 收口日志读位(`answer` 本体不出模块)。 */
61
+ readonly answerLength: number;
62
+ }
63
+ export declare function createTextStream(ctx: AdapterContext, idOf: IdOf): TextStream;
@@ -0,0 +1,141 @@
1
+ import { chrome, MAIN, transcript } from './ids.js';
2
+ import { estimateCjkTokens, FLUSH_INTERVAL_MS } from './wireShapes.js';
3
+ export function createTextStream(ctx, idOf) {
4
+ // ── turn 级状态(cli generator 局部变量的等价;矩阵 §1.1 逐行对位)──────────────────────────
5
+ let answer = '';
6
+ let answerSegment = '';
7
+ let thinking = '';
8
+ let emittedAssistantText = false;
9
+ let textPending = '';
10
+ let thinkingPending = '';
11
+ let textBlockOpen = false;
12
+ let thinkingBlockOpen = false;
13
+ let lastFlushAt = 0;
14
+ /** T29 攒批节拍:宿主策略优先(桌面/web 可传 16ms 对齐 CC 桌面端),缺席=207 CLI 口径 100ms。 */
15
+ const flushEveryMs = typeof ctx.coalesceIntervalMs === 'number' && ctx.coalesceIntervalMs >= 0
16
+ ? ctx.coalesceIntervalMs
17
+ : FLUSH_INTERVAL_MS;
18
+ /** committed 消息的 id 锚 —— 该段/该思考块的**第一帧**(重放确定性:同流同 id)。 */
19
+ let segmentAnchor = null;
20
+ let thinkingAnchor = null;
21
+ /** 提交累积的思考块(cli takeThinking:committed 形 + 关活体块 + 收活动行)。 */
22
+ function* takeThinking() {
23
+ if (thinking.length === 0)
24
+ return;
25
+ const anchor = thinkingAnchor ?? {};
26
+ const msg = {
27
+ type: 'assistant',
28
+ message: { role: 'assistant', content: [{ type: 'thinking', thinking }] },
29
+ uuid: idOf(anchor, 'thinking'),
30
+ session_id: ctx.sessionId,
31
+ parent_tool_use_id: null,
32
+ };
33
+ thinking = '';
34
+ thinkingAnchor = null;
35
+ thinkingBlockOpen = false;
36
+ // P2d:活体 "∴ Thinking…" 行在 committed 块落地的**同一刻**收掉(先收行再上消息,cli 同序)
37
+ yield chrome({ kind: 'thinking_activity', laneProof: MAIN, active: false });
38
+ yield transcript(msg);
39
+ }
40
+ /** 提交累积的文本段(cli takeAnswerSegment:#27 段序铁律——工具卡前/turn 末各 flush 一次)。 */
41
+ function* takeAnswerSegment() {
42
+ if (answerSegment.length === 0)
43
+ return;
44
+ const anchor = segmentAnchor ?? {};
45
+ const msg = {
46
+ type: 'assistant',
47
+ message: { role: 'assistant', content: [{ type: 'text', text: answerSegment }] },
48
+ uuid: idOf(anchor, 'text'),
49
+ session_id: ctx.sessionId,
50
+ parent_tool_use_id: null,
51
+ };
52
+ answerSegment = '';
53
+ segmentAnchor = null;
54
+ emittedAssistantText = true;
55
+ textBlockOpen = false;
56
+ yield transcript(msg);
57
+ }
58
+ /** 合并后的活体增量下泄(cli drainLive:思考先于文本 = 持久臂序)。 */
59
+ function* drainLive() {
60
+ if (thinkingPending.length > 0) {
61
+ const opening = !thinkingBlockOpen;
62
+ thinkingBlockOpen = true;
63
+ yield chrome({
64
+ kind: 'stream_delta',
65
+ laneProof: MAIN,
66
+ channel: 'thinking',
67
+ text: thinkingPending,
68
+ estimatedTokens: estimateCjkTokens(thinkingPending),
69
+ opening,
70
+ });
71
+ thinkingPending = '';
72
+ }
73
+ if (textPending.length > 0) {
74
+ const opening = !textBlockOpen;
75
+ textBlockOpen = true;
76
+ // B3:cli 的 `SEMA_STREAM_PROBE` 文件探针换成 ctx.probe 口(体检 #5 同款用途:Ink 挂载后
77
+ // console 被吞,harness 只能靠这条证明「引擎是**逐 token 增量**到的」而不是整块一次性 dump)。
78
+ // 载荷格式与 cli 逐字同形,只把 Date.now() 换成 ctx.now()(注入时钟纪律)。
79
+ ctx.probe?.('stream', `${ctx.now()} text_flush len=${textPending.length}`);
80
+ yield chrome({
81
+ kind: 'stream_delta',
82
+ laneProof: MAIN,
83
+ channel: 'text',
84
+ text: textPending,
85
+ estimatedTokens: estimateCjkTokens(textPending),
86
+ opening,
87
+ });
88
+ textPending = '';
89
+ }
90
+ }
91
+ return {
92
+ *feedText(delta, frame) {
93
+ // MOD-1 思考→回答边界:回答一起头就把思考块 committed 上屏(它推理的内容之前)。
94
+ if (thinking.length > 0 || thinkingPending.length > 0) {
95
+ yield* drainLive();
96
+ yield* takeThinking();
97
+ }
98
+ if (answerSegment.length === 0)
99
+ segmentAnchor = frame;
100
+ answer += delta;
101
+ answerSegment += delta;
102
+ textPending += delta;
103
+ },
104
+ *feedThinking(delta, frame) {
105
+ if (thinking.length === 0) {
106
+ thinkingAnchor = frame;
107
+ // P2d:elapsed 锚在**首条** leader 推理增量(token 累计由 stream_delta.estimatedTokens
108
+ // 承载,宿主自加;>30s 无增量的 stall 提示同样由宿主按增量时间戳判)。
109
+ yield chrome({ kind: 'thinking_activity', laneProof: MAIN, active: true });
110
+ }
111
+ thinking += delta;
112
+ thinkingPending += delta;
113
+ },
114
+ drainLive,
115
+ takeThinking,
116
+ takeAnswerSegment,
117
+ hasPendingContent: () => thinking.length > 0 ||
118
+ answerSegment.length > 0 ||
119
+ thinkingPending.length > 0 ||
120
+ textPending.length > 0,
121
+ *maybeFlushOnBeat(nowMs) {
122
+ // COALESCER 节拍:一个间隔至多一次 flush(同步 SSE 突发先攒着,过闸再一次泄干)。
123
+ if ((textPending.length > 0 || thinkingPending.length > 0) && nowMs - lastFlushAt >= flushEveryMs) {
124
+ yield* drainLive();
125
+ lastFlushAt = nowMs;
126
+ }
127
+ },
128
+ appendAnswer: (text) => {
129
+ answer += text;
130
+ },
131
+ markEmittedText: () => {
132
+ emittedAssistantText = true;
133
+ },
134
+ get emittedAssistantText() {
135
+ return emittedAssistantText;
136
+ },
137
+ get answerLength() {
138
+ return answer.length;
139
+ },
140
+ };
141
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * adapt/toolCards.ts — M2 **工具卡台账**(A 族拆分 REF-CC-SPLIT-03;矩阵 §1.2 三行状态)。
3
+ *
4
+ * 收编 `pending`(开卡表)/ `assistantResponseId`(S1 per-RESPONSE id)/ `lastLiveTodos`
5
+ * (TodoWrite 划删线 diff 的 oldTodos)三行状态 + C4 mintResponseId / C10 buildToolResult /
6
+ * C11 sweepOpenCards 三个闭包 + `PendingToolUse` 形本身。
7
+ *
8
+ * 🔴 拆分硬约束(SPLIT-03,codex verdict 2 采纳形):**S1 复位在 `close()` 内部、第一个 `yield`
9
+ * 之前**。理由是生成器语义 —— yield 之后的语句只有消费者再要下一个值时才跑,消费者早退
10
+ * (`it.return()` / break)时永不执行。把复位留在调用方,调用方就还能写出「yield 后复位」的形;
11
+ * 收进被调方之后,这条时序由**一处**保证。原 `tool_end_result` 臂 :1054-1056(delete → 复位 →
12
+ * yield)与 sweep :743-747 是同一个三步,现在是同一段代码。
13
+ *
14
+ * 🔴 同批把 assistant 臂 reject/cancel(:970)与 error(:990)两条路径的复位**前移到各自 yield 之前**
15
+ * —— 矩阵 §2.2 的定性:那两处是**形不一致的技术债、不是缺陷**(`assistantResponseId` 是 L1
16
+ * turn 级,两行之后紧跟 `break`,早退时生成器已死、再无读者 ⇒ 零可观测后果),所以前移是
17
+ * **纯一致性改动、零行为差**。反钉见矩阵 §2.2:前移不影响 mint —— 影响窗口只有
18
+ * 「复位语句与原复位点(yield 之后)」之间那几行,窗口内无 mint 点;三条支随即退出本臂,
19
+ * 后续帧的 mint 走各自臂的常规路径,与拆分前同(codex 复审校准:break 退出的是臂不是 turn)。
20
+ *
21
+ * 🔴 `pending` 是唯一跨 M2→M3 的数据面(矩阵 §1.2 注):面板族只读两个字段(`name` 判
22
+ * SUBAGENT_TOOL_NAMES / `rawInput` T50 双形读)。所以对外给的是 `OpenCardView` **只读视图**,
23
+ * 不交 Map 本体 —— 交出去 M3 就能 `delete`,而「一张开卡一条 tool_result」是本模块的不变量。
24
+ */
25
+ import type { AdapterContext, AdapterOutput } from '../seam.js';
26
+ import { type Frame, type IdOf } from './ids.js';
27
+ /** 一张开着的卡(assistant tool_use 臂开、tool_end_result 臂关)。 */
28
+ export interface PendingToolUse {
29
+ id: string;
30
+ name: string;
31
+ rawInput: unknown;
32
+ eventId?: string;
33
+ parentToolCallId?: string;
34
+ }
35
+ /** `tool_end_result` 帧上与关卡有关的那几位(原 `buildToolResult` 的内联入参形,具名化)。 */
36
+ export interface ToolEndPayload {
37
+ output?: unknown;
38
+ structured?: unknown;
39
+ truncated?: boolean;
40
+ isError?: boolean;
41
+ eventId?: string | undefined;
42
+ parentToolCallId?: string | undefined;
43
+ }
44
+ /** 交给面板族(M3)的**只读**视图 —— 不含任何写口。 */
45
+ export interface OpenCardView {
46
+ /** 这张卡此刻还开着吗(#6 live vs inert 的判位)。 */
47
+ has(id: string): boolean;
48
+ /** 读卡(面板族只用 `name` 与 `rawInput` 两个字段)。 */
49
+ peek(id: string): PendingToolUse | undefined;
50
+ /** 按**开卡顺序**遍历(FIFO 猜父臂依赖这个顺序)。 */
51
+ entries(): Iterable<readonly [string, PendingToolUse]>;
52
+ }
53
+ export interface ToolCardLedger extends OpenCardView {
54
+ /** 开卡(assistant tool_use 臂)。 */
55
+ open(p: PendingToolUse): void;
56
+ /** 关一张卡:出栈 + S1 复位 + 铸 tool_result(三步都在第一个 yield 之前完成前两步)。 */
57
+ close(p: PendingToolUse, end?: ToolEndPayload): Generator<AdapterOutput>;
58
+ /** 关掉所有还开着的卡(流被截断 / 无 result 收尾;abort 路径**不做**)。 */
59
+ sweep(): Generator<AdapterOutput>;
60
+ /** S1:per-RESPONSE 铸 id(同一个 model response 内的多张卡共用)。 */
61
+ mintResponseId(frame: Frame): string;
62
+ /** S1:一个结果关闭该 model response(reject/cancel/error 三支的显式复位口)。 */
63
+ resetResponseId(): void;
64
+ }
65
+ export declare function createToolCardLedger(ctx: AdapterContext, idOf: IdOf): ToolCardLedger;