@sema-agent/client-core 0.12.2 → 0.13.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 (127) hide show
  1. package/README.md +14 -2
  2. package/dist/abortableSleep.d.ts +29 -0
  3. package/dist/abortableSleep.js +43 -0
  4. package/dist/adapt/arms.d.ts +58 -0
  5. package/dist/adapt/arms.js +613 -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 +54 -0
  11. package/dist/adapt/panelTasks.js +193 -0
  12. package/dist/adapt/textStream.d.ts +49 -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 +33 -0
  17. package/dist/adapt/turnFlags.js +55 -0
  18. package/dist/adapt/wireShapes.d.ts +93 -0
  19. package/dist/adapt/wireShapes.js +167 -0
  20. package/dist/adapt.d.ts +32 -59
  21. package/dist/adapt.js +78 -1206
  22. package/dist/adapter/downstream/eventToSdkMessage.d.ts +55 -13
  23. package/dist/adapter/downstream/eventToSdkMessage.js +166 -106
  24. package/dist/adapter/downstream/terminalToSdkResult.js +149 -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 +29 -4
  28. package/dist/adapter/runStream.js +129 -13
  29. package/dist/adapter/types.d.ts +2 -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.js +31 -8
  69. package/dist/hitl/askGateWire.d.ts +27 -96
  70. package/dist/hitl/askGateWire.js +69 -618
  71. package/dist/hitl/frameRouter.d.ts +86 -0
  72. package/dist/hitl/frameRouter.js +342 -0
  73. package/dist/hitl/gateLedger.d.ts +107 -0
  74. package/dist/hitl/gateLedger.js +113 -0
  75. package/dist/hitl/hitlBridge.d.ts +75 -15
  76. package/dist/hitl/hitlBridge.js +94 -22
  77. package/dist/hitl/hitlHostSurface.d.ts +49 -0
  78. package/dist/hitl/hitlHostSurface.js +155 -0
  79. package/dist/hitl/parkResolver.d.ts +74 -0
  80. package/dist/hitl/parkResolver.js +241 -0
  81. package/dist/hitl/planReviewWire.d.ts +60 -2
  82. package/dist/hitl/planReviewWire.js +152 -74
  83. package/dist/hitl/toolApprovalWire.d.ts +67 -4
  84. package/dist/hitl/toolApprovalWire.js +119 -31
  85. package/dist/hooksWireCaps.d.ts +1 -82
  86. package/dist/hooksWireCaps.js +34 -235
  87. package/dist/host.d.ts +16 -5
  88. package/dist/index.d.ts +2 -0
  89. package/dist/index.js +15 -1
  90. package/dist/interactiveToolsWire.d.ts +15 -4
  91. package/dist/interactiveToolsWire.js +24 -26
  92. package/dist/limitsWire.js +7 -40
  93. package/dist/liveInitToolFace.d.ts +51 -6
  94. package/dist/liveQuestionStore.d.ts +5 -6
  95. package/dist/model/providerPresets.js +11 -1
  96. package/dist/notifications.d.ts +48 -2
  97. package/dist/notifications.js +223 -46
  98. package/dist/retainBackgroundWireCaps.d.ts +3 -2
  99. package/dist/retainBackgroundWireCaps.js +5 -9
  100. package/dist/sandboxWire.d.ts +9 -31
  101. package/dist/sandboxWire.js +51 -50
  102. package/dist/scenarioWire.d.ts +1 -1
  103. package/dist/scenarioWire.js +25 -36
  104. package/dist/seam.d.ts +23 -6
  105. package/dist/seam.js +40 -30
  106. package/dist/seatContract.d.ts +369 -83
  107. package/dist/seatContract.js +585 -198
  108. package/dist/selfOrchestrationWireCaps.d.ts +6 -5
  109. package/dist/selfOrchestrationWireCaps.js +8 -12
  110. package/dist/sessionSlot.d.ts +8 -0
  111. package/dist/sessionSlot.js +1 -0
  112. package/dist/steering.js +2 -2
  113. package/dist/subagent/engineTaskHandleWire.d.ts +3 -0
  114. package/dist/subagent/engineTaskHandleWire.js +17 -2
  115. package/dist/subagentContentStore.d.ts +5 -5
  116. package/dist/toolResult.d.ts +89 -8
  117. package/dist/toolResult.js +99 -30
  118. package/dist/typePins.d.ts +17 -0
  119. package/dist/typePins.js +1 -0
  120. package/dist/ultracodeWireCaps.js +5 -6
  121. package/dist/unrefTimer.d.ts +19 -0
  122. package/dist/unrefTimer.js +5 -0
  123. package/dist/workflow.d.ts +3 -2
  124. package/dist/workflow.js +3 -2
  125. package/dist/workflowClient.d.ts +7 -0
  126. package/dist/workflowClient.js +47 -12
  127. package/package.json +3 -3
@@ -35,21 +35,62 @@
35
35
  */
36
36
  import type { AgentEvent, CheckpointGate } from '@sema-agent/sdk';
37
37
  import { type SDKMessage, type EmitContext, type ModelUsage } from '../types.js';
38
+ /** 「本切片不投影这条臂」的**如实分类**(REF-CC-058 / xlate-04)。 */
39
+ export type EventProjectionNoneReason =
40
+ /** `turn_end`:usage 折叠是 run driver 的活(`turnEndUsage`),本层无独立可渲染臂。 */
41
+ 'usage_only'
42
+ /** `done`/`failed`:终帧由 `terminalToSdkResult` 投影。 */
43
+ | 'terminal_delegated'
44
+ /** `suspended`:HITL 登记是 run driver 的活(out of slice)。 */
45
+ | 'hitl_out_of_slice'
46
+ /** 已在 SDK union 里、但本切片**有意**不投影的臂(逐条理由见各 case 注释)。 */
47
+ | 'not_in_slice'
38
48
  /**
39
- * Project one durable/live `AgentEvent` into zero-or-one renderable `SDKMessage`.
49
+ * 臂在本切片内、帧也不畸形,但**载荷筛完是空的**(今天唯一来源:`suggestions` 全被空白过滤掉)。
50
+ * 单列一档的理由与 REF-CC-058 本身同源:把它并进 `not_in_slice` 会说「这条臂我们不投影」——
51
+ * 那是假话(我们投影它,只是这一帧没内容);并进 `dropped` 则是把引擎的正常空批诬告成丢帧。
52
+ */
53
+ | 'empty_payload';
54
+ /** 「这条臂我看不懂/嫌它坏,丢了」的**如实分类**(REF-CC-057/058)。 */
55
+ export type EventProjectionDropReason =
56
+ /** 帧在但必填位畸形(非串 runId / 空 files / 空 source …)—— 引擎发了,我们读不动。 */
57
+ 'malformed'
58
+ /** 类型面根本不认识这条臂(引擎比本包新)。 */
59
+ | 'unknown_arm';
60
+ /**
61
+ * 投影结果三态(REF-CC-058 / xlate-04,A1 禁哨兵值双义 + C6 降级必须可分辨)。
62
+ *
63
+ * 收编前返回型是 `SDKMessage | null`,而那个 `null` 在 10 个 return 点上同时承载三种互不相同的
64
+ * 语义:①设计内无可渲染臂;②帧畸形被丢弃;③未知新臂被吞。调用方拿到 null **无法分辨**「本来
65
+ * 就没东西渲」和「引擎发了一帧但我看不懂」—— 本文件历史上所有「帧到了没人读」事故(suggestions
66
+ * 死信、workflow lane 两键被剥)的载体就是这个塌缩。三态命名后,`dropped` 是可被上报的信号,
67
+ * driver 侧据此闭合 REF-CC-057 的下泄口。
68
+ */
69
+ export type EventProjection = {
70
+ kind: 'message';
71
+ message: SDKMessage;
72
+ } | {
73
+ kind: 'none';
74
+ why: EventProjectionNoneReason;
75
+ } | {
76
+ kind: 'dropped';
77
+ why: EventProjectionDropReason;
78
+ type: string;
79
+ };
80
+ /**
81
+ * Project one durable/live `AgentEvent` into a typed {@link EventProjection}.
40
82
  *
41
83
  * `tool_end` projects to a NEUTRAL internal `tool_end_result` arm (NOT a CC
42
84
  * SDKMessage the renderer reads) carrying the §E1 wire `output` so the bridge can
43
85
  * close the open card with the real body.
44
86
  *
45
- * Returns `null` for arms that carry no standalone renderable message in this
46
- * slice:
47
- * - `turn_end` → usage rollup only, surfaced via `turnEndUsage` below;
48
- * - `suspended` → HITL registration is the run driver's job (out of slice).
49
- *
50
87
  * Terminal `done`/`failed` are delegated to `terminalToSdkResult`.
88
+ *
89
+ * 🔴 BREAKING(REF-CC-058,0.13.0):返回型由 `SDKMessage | null` 改为三态 Result。
90
+ * 迁移判据一句话:`const p = eventToSdkMessage(ev, ctx); if (p.kind === 'message') …` ——
91
+ * 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
51
92
  */
52
- export declare function eventToSdkMessage(ev: AgentEvent, ctx: EmitContext): SDKMessage | null;
93
+ export declare function eventToSdkMessage(ev: AgentEvent, ctx: EmitContext): EventProjection;
53
94
  /**
54
95
  * CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
55
96
  * costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
@@ -67,12 +108,13 @@ export declare function turnEndUsage(ev: Extract<AgentEvent, {
67
108
  * ⚠️ DEAD CODE — 不是「HITL 分支漏接」(2026-07-26 全树核查,task #81 顺带件):
68
109
  * · 全树(src/ + mock/ + build/ + 测试)**零调用点**,adapter facade(src/seam/adapter/index.ts)
69
110
  * 也没把它再导出去 —— 没有任何一处「本该调它却没调」。
70
- * · 它想服务的那条 HITL 分支**已经接好了**,只是没走这个函数:
71
- * - `suspended` 事件臂 → `src/seam/adapter/upstream/hitlBridge.ts:162-168` 的 `observe()`
72
- * 就地做了**同款**防御回落(`ev.gate ? ev.gate : { kind: 'human' }`),而且 latch 的是整个
73
- * CheckpointGate 对象,壳侧 `currentGate()` 自己 branch `gate.kind`;
74
- * - `done{status:'suspended', checkpointGate}` 终帧 `liveHitlAskWire.ts:89-118` /
75
- * `terminalToSdkResult.ts:296-311` 各自读 `checkpointGate.kind` **和** `.toolName`。
111
+ * · 它想服务的那条 HITL 分支**已经接好了**,只是没走这个函数(REF-CC-063:此处原为四个行号锚,
112
+ * 今天全部已腐,改符号锚):
113
+ * - `suspended` 事件臂 → `hitl/hitlBridge.ts` `HitlBridge.observe()` 就地做了**同款**防御
114
+ * 回落(`ev.gate ? ev.gate : { kind: 'human' }`),而且 latch 的是整个 CheckpointGate 对象,
115
+ * 壳侧 `currentGate()` 自己 branch `gate.kind`;
116
+ * - `done{status:'suspended', checkpointGate}` 终帧 `liveHitlAskWire.ts` ask 合成口 /
117
+ * `terminalToSdkResult.ts` 的 park 臂各自读 `checkpointGate.kind` **和** `.toolName`。
76
118
  * · 关键:每个真实消费方都同时需要 `toolName`(挑 Ask overlay vs fs 写门三选卡),而本函数只吐
77
119
  * 一个 kind 字符串 —— 签名比任何真实需求都窄,接进去反而要再读一次 gate。
78
120
  * 结论 = 可删的死码(留档不擅删,按 task #81 口径只标记);真要收,连同 `CheckpointGate` 的
@@ -11,6 +11,14 @@ import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
11
11
  function armBody(body) {
12
12
  return body;
13
13
  }
14
+ /**
15
+ * `suggestions` 批的防御性上界(REF-CC-061 / xlate-07,A3 魔数命名)。UI 面的界,不是模型面 ——
16
+ * 这条臂的文本**绝不回喂模型**(见 `case 'suggestions'` 头注)。
17
+ * 命名的意义不只是可读:裸字面量对域词表门是隐形的,而这两个数决定「用户少看见几条建议」。
18
+ */
19
+ const SUGGESTION_BATCH_CAP = 8;
20
+ /** 单条建议的字符上界;超出截断并留可见痕迹(尾部 `…`)。 */
21
+ const SUGGESTION_CHARS_CAP = 200;
14
22
  /** Wrap neutral content blocks in the CC `assistant` message envelope. */
15
23
  function assistantArm(ctx, content) {
16
24
  return stamp(ctx, armBody({
@@ -31,120 +39,74 @@ function streamEventArm(ctx, event) {
31
39
  parent_tool_use_id: null,
32
40
  }));
33
41
  }
42
+ const projected = (message) => ({ kind: 'message', message });
43
+ const nothing = (why) => ({ kind: 'none', why });
44
+ const dropped = (why, type) => ({ kind: 'dropped', why, type });
45
+ /**
46
+ * B5 穷举卫兵:union 加成员必须在**编译期**打红这个 switch,而不是让新臂在用户面静默丢帧。
47
+ * 运行期仍会走到这里 —— wire 是 JSON,引擎完全可能比本包的类型新一版(那正是 `unknown_arm`)。
48
+ */
49
+ function assertNeverArm(_ev) {
50
+ /* 类型面穷举断言;运行期由调用点返回 typed dropped */
51
+ }
34
52
  /**
35
- * Project one durable/live `AgentEvent` into zero-or-one renderable `SDKMessage`.
53
+ * Project one durable/live `AgentEvent` into a typed {@link EventProjection}.
36
54
  *
37
55
  * `tool_end` projects to a NEUTRAL internal `tool_end_result` arm (NOT a CC
38
56
  * SDKMessage the renderer reads) carrying the §E1 wire `output` so the bridge can
39
57
  * close the open card with the real body.
40
58
  *
41
- * Returns `null` for arms that carry no standalone renderable message in this
42
- * slice:
43
- * - `turn_end` → usage rollup only, surfaced via `turnEndUsage` below;
44
- * - `suspended` → HITL registration is the run driver's job (out of slice).
45
- *
46
59
  * Terminal `done`/`failed` are delegated to `terminalToSdkResult`.
60
+ *
61
+ * 🔴 BREAKING(REF-CC-058,0.13.0):返回型由 `SDKMessage | null` 改为三态 Result。
62
+ * 迁移判据一句话:`const p = eventToSdkMessage(ev, ctx); if (p.kind === 'message') …` ——
63
+ * 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
47
64
  */
48
65
  export function eventToSdkMessage(ev, ctx) {
49
66
  // service 1.75 — `workflow_complete`: an out-of-band background-workflow completion push, ridden onto
50
- // the session's next stream open (BEFORE the run's own events). SDK ≥0.0.108 已入 union(注记过时
51
- // 已修;G8 的收敛方向候 P1-3);raw 读形保留待 B4 帧分派批统一 typed 化。 Projects to a
52
- // NEUTRAL internal arm; the upstream bridge turns it into CC's `<task-notification>` injection.
67
+ // the session's next stream open (BEFORE the run's own events). Projects to a NEUTRAL internal arm;
68
+ // the upstream bridge turns it into CC's `<task-notification>` injection.
69
+ // 🔴 REF-CC-060(xlate-06)真码核对:P1 镜头按注释判「五条 raw 臂里四条已入 union」,逐字核
70
+ // `@sema-agent/sdk` 3.0.0 的 `AgentEvent` 后**部分证伪** —— task_notification / diagnostics /
71
+ // steering_injected / workspace_changed 四条确已入 union(已搬进下方 switch 成为正式 case),
72
+ // 而 `workflow_complete` **至今不在 union 里**(与那几条注释自称的相反)。故本臂是**唯一**
73
+ // 保留的 raw 预分派臂,`raw` 变量也随之收窄到这一条。到期复核:每次 SDK major 提货时重查
74
+ // (判据 = SDK d.ts 里出现 `type: "workflow_complete"`),入 union 当天搬进 switch。
53
75
  const raw = ev;
54
76
  if (raw.type === 'workflow_complete') {
55
77
  if (typeof raw.runId !== 'string')
56
- return null;
57
- return stamp(ctx, armBody({
78
+ return dropped('malformed', 'workflow_complete');
79
+ return projected(stamp(ctx, armBody({
58
80
  type: 'workflow_complete',
59
81
  runId: raw.runId,
60
82
  status: raw.status === 'failed' ? 'failed' : 'completed',
61
83
  summary: typeof raw.summary === 'string' ? raw.summary : '',
62
- }));
63
- }
64
- // core 1.204 G2b — `task_notification` (background-bash/workflow completion, TaskNotificationPayload
65
- // rides on `notification`). SDK ≥0.0.108 已入 union(注记过时已修);raw 读形保留待 B4 帧分派批统一 typed 化. Projects to a NEUTRAL
66
- // internal arm: the REPL bridge drops it (the ENGINE already steer-injects the model-facing XML), the
67
- // print-mode `ask` converts it to CC's `system/task_notification` SDK frame.
68
- if (raw.type === 'task_notification') {
69
- const n = (raw.notification ?? raw);
70
- if (typeof n.task_id !== 'string')
71
- return null;
72
- return stamp(ctx, armBody({
73
- type: 'task_notification',
74
- notification: n,
75
- }));
76
- }
77
- // core 1.220 design/121 — engine LSP `diagnostics` frame ({files:[{uri,diagnostics}], isNew}, service
78
- // §E1 redacted, TOC host lane default ON). SDK ≥0.0.108 已入 union(注记过时已修);raw 读形保留待 B4 帧分派批统一 typed 化. Projects to
79
- // a NEUTRAL internal arm; the REPL bridge maps it into the CC `diagnostics` attachment row
80
- // (DiagnosticsDisplay renders it, zero new UI). parentToolCallId rides through so the bridge can keep
81
- // a sub-flow's diagnostics off the leader transcript.
82
- if (raw.type === 'diagnostics') {
83
- if (!Array.isArray(raw.files) || raw.files.length === 0)
84
- return null;
85
- return stamp(ctx, armBody({
86
- type: 'diagnostics',
87
- files: raw.files,
88
- isNew: raw.isNew === true,
89
- ...(typeof raw.parentToolCallId === 'string' ? { parentToolCallId: raw.parentToolCallId } : {}),
90
- }));
91
- }
92
- // core 1.251 design/133 ([479] ask-1) + 1.253 G1 ([482]/[487]②) — `steering_injected`: the engine
93
- // echoed a SYSTEM steering injection ({source, preview:first-220-chars}, echo-only — the model-facing
94
- // message is server-side). Sources are an OPEN SET (deadline_nudge/finalize + todo_reminder/
95
- // task_reminder/changed_files/plan_mode + background_tasks/tools_delta today; core may extend). Not in
96
- // the SDK union yet — raw defensive read; projects to a NEUTRAL internal arm the REPL bridge maps into
97
- // renderable attachments (steeringAttachments.ts). parentToolCallId rides through so a sub-flow's echo
98
- // stays off the leader transcript (same rule as diagnostics).
99
- if (raw.type === 'steering_injected') {
100
- if (typeof raw.source !== 'string' || raw.source.length === 0)
101
- return null;
102
- return stamp(ctx, armBody({
103
- type: 'steering_injected',
104
- source: raw.source,
105
- preview: typeof raw.preview === 'string' ? raw.preview : '',
106
- ...(typeof raw.parentToolCallId === 'string' ? { parentToolCallId: raw.parentToolCallId } : {}),
107
- }));
108
- }
109
- // core design/99 §E13([1559]二/[1564]4 提货)— `workspace_changed`: the engine moved its logical
110
- // cwd (bash `cd` / EnterWorktree), frame carries the NEW cwd (RAW form). Display-only echo — the
111
- // engine already runs against the new cwd. NOT persistent state: resets to the session root across
112
- // suspend/resume(core 口径,勿当持久态渲染)。SDK ≥0.0.108 已入 union(注记过时已修);raw 读形保留待 B4 帧分派批统一 typed 化;
113
- // projects to a NEUTRAL arm the REPL bridge maps into a dim transcript row. server 透传候班车
114
- // ([1557]三处置),帧到即亮。
115
- if (raw.type === 'workspace_changed') {
116
- if (typeof raw.cwd !== 'string' || raw.cwd.length === 0)
117
- return null;
118
- return stamp(ctx, armBody({
119
- type: 'workspace_changed',
120
- cwd: raw.cwd,
121
- ...(typeof raw.parentToolCallId === 'string' ? { parentToolCallId: raw.parentToolCallId } : {}),
122
- }));
84
+ })));
123
85
  }
124
86
  switch (ev.type) {
125
87
  // CS-1 §2.2 — turn-aggregated answer text (durable). Drop raw block array.
126
88
  case 'text':
127
- return assistantArm(ctx, [{ type: 'text', text: ev.text }]);
89
+ return projected(assistantArm(ctx, [{ type: 'text', text: ev.text }]));
128
90
  // CS-3 §2.1 — turn-aggregated thinking (durable). Distinct collapsible block.
129
91
  case 'reasoning':
130
- return assistantArm(ctx, [{ type: 'thinking', thinking: ev.text }]);
92
+ return projected(assistantArm(ctx, [{ type: 'thinking', thinking: ev.text }]));
131
93
  // CS-2 §2.4 — per-token answer fragment (live only). Typewriter append.
132
94
  case 'text_delta':
133
- return streamEventArm(ctx, { type: 'text_delta', delta: ev.delta });
95
+ return projected(streamEventArm(ctx, { type: 'text_delta', delta: ev.delta }));
134
96
  // CS-4 §2.3 — per-token thinking fragment (live only). Drop signature_delta.
135
97
  // §E2 identity (service 1.78 unblanked parentToolCallId on the wire, blackboard dogfood #2): thread it
136
98
  // so the bridge can EXCLUDE a sub-flow's reasoning (orchestration/subagent) from the LEADER's spinner
137
99
  // counter + thinking preview — the "134k tokens" overcount root cause (upstreamBridge TODO #9).
138
100
  case 'reasoning_delta':
139
- return streamEventArm(ctx, {
101
+ return projected(streamEventArm(ctx, {
140
102
  type: 'thinking_delta',
141
103
  delta: ev.delta,
142
104
  ...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
143
- });
105
+ }));
144
106
  // CS-5 §2.5 — action-card open. args is `unknown`: render generically.
145
107
  // §E2 — thread the wire eventId (+ parentToolCallId) so the bridge stamps it (SB-2).
146
108
  case 'tool_start':
147
- return assistantArm(ctx, [
109
+ return projected(assistantArm(ctx, [
148
110
  {
149
111
  type: 'tool_use',
150
112
  id: ev.toolCallId,
@@ -158,14 +120,14 @@ export function eventToSdkMessage(ev, ctx) {
158
120
  ? { parentToolCallId: ev.parentToolCallId }
159
121
  : {}),
160
122
  },
161
- ]);
123
+ ]));
162
124
  // CS-8 §2.8 — compaction divider. tokensBefore ⇐ pre_tokens. trigger rides through when the
163
125
  // engine sends it (core 1.156 manual verb → "manual"; absent ⇒ 'auto'). core 1.219
164
126
  // (attachWorkingFiles default ON): `attachedFiles:[{path,chars,truncated}]` rides through raw so
165
127
  // the bridge can project the CC post-compact "Read {path}" attachment rows (blackboard ask ②).
166
128
  case 'compacted': {
167
129
  const attachedFiles = ev.attachedFiles;
168
- return stamp(ctx, armBody({
130
+ return projected(stamp(ctx, armBody({
169
131
  type: 'system',
170
132
  subtype: 'compact_boundary',
171
133
  compact_metadata: {
@@ -173,7 +135,7 @@ export function eventToSdkMessage(ev, ctx) {
173
135
  pre_tokens: ev.tokensBefore ?? 0,
174
136
  },
175
137
  ...(Array.isArray(attachedFiles) && attachedFiles.length > 0 ? { attachedFiles } : {}),
176
- }));
138
+ })));
177
139
  }
178
140
  // CS-6 §2.6 — tool_end now carries the §E1 model-facing `output` (the action-card
179
141
  // BODY) + isError + the §E2 eventId. Emit a NEUTRAL internal arm the bridge correlates
@@ -186,7 +148,7 @@ export function eventToSdkMessage(ev, ctx) {
186
148
  // AgentToolResult.details on the wire; not yet in the SDK tool_end arm — raw defensive read).
187
149
  // Rides the neutral arm so the bridge can render the REAL rich card (diff / stdout panels).
188
150
  const structured = ev.structured;
189
- return stamp(ctx, armBody({
151
+ return projected(stamp(ctx, armBody({
190
152
  type: 'tool_end_result',
191
153
  toolCallId: ev.toolCallId,
192
154
  toolName: ev.toolName,
@@ -202,7 +164,7 @@ export function eventToSdkMessage(ev, ctx) {
202
164
  ...(ev.parentToolCallId !== undefined
203
165
  ? { parentToolCallId: ev.parentToolCallId }
204
166
  : {}),
205
- }));
167
+ })));
206
168
  }
207
169
  // §E3/§E10 — BrainStatus liveness (events.d.ts:44/:80 — rate_limited/retrying/reconnecting/
208
170
  // circuit_open). LIVE-only + EPHEMERAL (never persisted/replayed). Project to a NEUTRAL internal
@@ -212,12 +174,12 @@ export function eventToSdkMessage(ev, ctx) {
212
174
  // `afa(agentId).retryStatus` app-state analog, ui-modules/0157_036_ui_Klo.js:31). The seam stays a
213
175
  // dumb forwarder: the provider-neutral→UI-shape mapping is the renderer layer's job (retryStatusStore).
214
176
  case 'status':
215
- return stamp(ctx, armBody({
177
+ return projected(stamp(ctx, armBody({
216
178
  type: 'retry_status',
217
179
  phase: ev.phase,
218
180
  ...(ev.detail !== undefined ? { detail: ev.detail } : {}),
219
181
  ...(ev.retryInSec !== undefined ? { retryInSec: ev.retryInSec } : {}),
220
- }));
182
+ })));
221
183
  // MF-10 (design/99, events.d.ts task_progress) — a SUBAGENT PROGRESS TICK: the delegated sub-run's
222
184
  // CUMULATIVE usage at each turn boundary (LIVE/EPHEMERAL, never persisted/replayed). Project to a
223
185
  // NEUTRAL internal `task_progress` arm — NOT a renderable CC SDKMessage. The upstream bridge binds it
@@ -228,7 +190,7 @@ export function eventToSdkMessage(ev, ctx) {
228
190
  // richer core-side arm.
229
191
  case 'task_progress': {
230
192
  if (typeof ev.taskId !== 'string')
231
- return null;
193
+ return dropped('malformed', 'task_progress');
232
194
  // core 1.262.0 [558]B / service 1.154.0 [579] — `currentAction`: the child's most recent tool
233
195
  // intent as ONE human line ("Bash npm test"), derived from the child's tool_start (tool name +
234
196
  // argument head) and service-redacted. SDK ≥0.0.108 已定型(events.d.ts task_progress 臂),
@@ -241,7 +203,7 @@ export function eventToSdkMessage(ev, ctx) {
241
203
  // 仍剥这两键(server a76bd77 才修,黑板 [1619])——真 wire 于 1.273+ 才带,此处先备好通路。
242
204
  const workflowRunId = ev.workflowRunId;
243
205
  const workflowAgentLabel = ev.workflowAgentLabel;
244
- return stamp(ctx, armBody({
206
+ return projected(stamp(ctx, armBody({
245
207
  type: 'task_progress',
246
208
  taskId: ev.taskId,
247
209
  ...(ev.name !== undefined ? { name: ev.name } : {}),
@@ -258,7 +220,72 @@ export function eventToSdkMessage(ev, ctx) {
258
220
  ...(ev.parentToolCallId !== undefined
259
221
  ? { parentToolCallId: ev.parentToolCallId }
260
222
  : {}),
261
- }));
223
+ })));
224
+ }
225
+ // ── REF-CC-060(xlate-06):以下四臂**已在 SDK union 里**,从 switch 之前的手搓 raw 预分派
226
+ // 搬进来成为正式 case。搬之前它们永远到不了 switch,`ev.type` 的穷举性对它们失效 ——
227
+ // 也就是说 union 给这四臂新增/改名任何位,编译期一声不响。防御读(`typeof`/`Array.isArray`)
228
+ // 一条不减:wire 是 JSON,类型不在运行期存在(M0/M1)。
229
+ //
230
+ // core 1.204 G2b — `task_notification` (background-bash/workflow completion, TaskNotificationPayload
231
+ // rides on `notification`). Projects to a NEUTRAL internal arm: the REPL bridge drops it (the ENGINE
232
+ // already steer-injects the model-facing XML), the print-mode `ask` converts it to CC's
233
+ // `system/task_notification` SDK frame.
234
+ case 'task_notification': {
235
+ // 载荷两形:嵌在 `notification` 里(server 早期形)或直接摊在帧上(今天的 union 形)。
236
+ const nested = ev.notification;
237
+ const n = (nested ?? ev);
238
+ if (typeof n.task_id !== 'string')
239
+ return dropped('malformed', 'task_notification');
240
+ return projected(stamp(ctx, armBody({
241
+ type: 'task_notification',
242
+ notification: n,
243
+ })));
244
+ }
245
+ // core 1.220 design/121 — engine LSP `diagnostics` frame ({files:[{uri,diagnostics}], isNew}, service
246
+ // §E1 redacted, TOC host lane default ON). Projects to a NEUTRAL internal arm; the REPL bridge maps it
247
+ // into the CC `diagnostics` attachment row (DiagnosticsDisplay renders it, zero new UI).
248
+ // parentToolCallId rides through so the bridge can keep a sub-flow's diagnostics off the leader transcript.
249
+ case 'diagnostics': {
250
+ if (!Array.isArray(ev.files) || ev.files.length === 0)
251
+ return dropped('malformed', 'diagnostics');
252
+ return projected(stamp(ctx, armBody({
253
+ type: 'diagnostics',
254
+ files: ev.files,
255
+ isNew: ev.isNew === true,
256
+ ...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
257
+ })));
258
+ }
259
+ // core 1.251 design/133 ([479] ask-1) + 1.253 G1 ([482]/[487]②) — `steering_injected`: the engine
260
+ // echoed a SYSTEM steering injection ({source, preview:first-220-chars}, echo-only — the model-facing
261
+ // message is server-side). Sources are an OPEN SET (deadline_nudge/finalize + todo_reminder/
262
+ // task_reminder/changed_files/plan_mode + background_tasks/tools_delta today; core may extend).
263
+ // Projects to a NEUTRAL internal arm the REPL bridge maps into renderable attachments
264
+ // (steeringAttachments.ts). parentToolCallId rides through so a sub-flow's echo stays off the leader
265
+ // transcript (same rule as diagnostics).
266
+ case 'steering_injected': {
267
+ if (typeof ev.source !== 'string' || ev.source.length === 0)
268
+ return dropped('malformed', 'steering_injected');
269
+ return projected(stamp(ctx, armBody({
270
+ type: 'steering_injected',
271
+ source: ev.source,
272
+ preview: typeof ev.preview === 'string' ? ev.preview : '',
273
+ ...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
274
+ })));
275
+ }
276
+ // core design/99 §E13([1559]二/[1564]4 提货)— `workspace_changed`: the engine moved its logical
277
+ // cwd (bash `cd` / EnterWorktree), frame carries the NEW cwd (RAW form). Display-only echo — the
278
+ // engine already runs against the new cwd. NOT persistent state: resets to the session root across
279
+ // suspend/resume(core 口径,勿当持久态渲染);projects to a NEUTRAL arm the REPL bridge maps into
280
+ // a dim transcript row.
281
+ case 'workspace_changed': {
282
+ if (typeof ev.cwd !== 'string' || ev.cwd.length === 0)
283
+ return dropped('malformed', 'workspace_changed');
284
+ return projected(stamp(ctx, armBody({
285
+ type: 'workspace_changed',
286
+ cwd: ev.cwd,
287
+ ...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
288
+ })));
262
289
  }
263
290
  // P0-1([1725] 清单A)— E12 `suggestions`:run 跑完(status:"completed")后 core 一次 LLM pass 产的
264
291
  // 「下一步可问什么」建议**批**。此前这个臂掉进 `default: return null` ⇒ 全链死信。
@@ -273,28 +300,60 @@ export function eventToSdkMessage(ev, ctx) {
273
300
  // tasks/stream ⇒ 这条通路今天只在 durable 车道(runs.create + runs.events:Ctrl+B/`/background`/
274
301
  // ControlRouter)活。防御性上界:批量截 8 条、单条截 200 字(UI 面,不是模型面)。
275
302
  case 'suggestions': {
276
- const list = (Array.isArray(ev.suggestions) ? ev.suggestions : [])
277
- .filter((s) => typeof s === 'string' && s.trim().length > 0)
278
- .slice(0, 8)
279
- .map((s) => (s.length > 200 ? `${s.slice(0, 200)}…` : s));
303
+ const filtered = (Array.isArray(ev.suggestions) ? ev.suggestions : []).filter((s) => typeof s === 'string' && s.trim().length > 0);
304
+ const list = filtered
305
+ .slice(0, SUGGESTION_BATCH_CAP)
306
+ .map((s) => (s.length > SUGGESTION_CHARS_CAP ? `${s.slice(0, SUGGESTION_CHARS_CAP)}…` : s));
280
307
  if (list.length === 0)
281
- return null;
282
- return stamp(ctx, armBody({
308
+ return nothing('empty_payload');
309
+ return projected(stamp(ctx, armBody({
283
310
  type: 'prompt_suggestions',
284
311
  suggestions: list,
285
- }));
312
+ // 🔴 REF-CC-061(xlate-07,H2 无声上限零容忍):**单条**截断本来就是诚实的(尾部 `…` 是
313
+ // 可见痕迹),**批量**截断此前不是 —— 引擎给 12 条时第 9-12 条无声蒸发,臂上没有任何位
314
+ // 说出「还有更多」。additive 位,与本文件 tool_end 臂的 `truncated` 同款姿势;渲不渲是
315
+ // 渲染面的决定,数据面必须先说出来。
316
+ ...(filtered.length > list.length ? { truncatedCount: filtered.length - list.length } : {}),
317
+ })));
286
318
  }
287
- // §2.7 — turn_end is usage-only. §2.9 suspended is HITL registration.
319
+ // §2.7 — turn_end is usage-only(usage `turnEndUsage` 单独出口,run driver 折叠)。
288
320
  case 'turn_end':
321
+ return nothing('usage_only');
322
+ // §2.9 — suspended 的 HITL 登记是 run driver 的活(hitlBridge.observe),本层不投影。
289
323
  case 'suspended':
290
- return null;
324
+ return nothing('hitl_out_of_slice');
291
325
  // CS-10 / CS-11 — terminals are projected by terminalToSdkResult.
292
326
  case 'done':
293
327
  case 'failed':
294
- return null;
328
+ return nothing('terminal_delegated');
329
+ // ── 已入 SDK union、本切片**有意**不投影的臂(REF-CC-060:进 switch 才受穷举保护)────────
330
+ // `meta`:流首帧的 taskId/sessionId 一等信号 —— 身份面由 SDK/传输层自己消费(runStream 只按
331
+ // `eventSeq` 去重),没有可渲染内容。
332
+ case 'meta':
333
+ // `file_link` / `prompt_assembled` / `model_usage` / `context_usage` / `config_assembled` /
334
+ // `message_committed`:引擎的可观测/审计面帧,本切片(CC transcript 投影)无对位渲染物;
335
+ // 消费它们的是 fleet/审计面而不是会话转录。
336
+ case 'file_link':
337
+ case 'prompt_assembled':
338
+ case 'model_usage':
339
+ case 'context_usage':
340
+ case 'config_assembled':
341
+ case 'message_committed':
342
+ // `needs_review`:plan-review park 的**事件**形;壳消费的是 done{status:'needs_review'} 终帧
343
+ // (runStream 的 plan_review_park chrome 臂),事件形在本切片重复且更早,故不投影。
344
+ case 'needs_review':
345
+ // `compaction_outcome`(sdk 4.0.1 新臂,[2373]A-2 三腿同源):压缩**非 compacted 结局**报告
346
+ // (mooted/failed 等)。CC transcript 无对位物(compact_boundary 只对成功压缩,由 `compacted`
347
+ // 臂投)——本切片不投影。⚠️ 「压缩失败让用户看见」是 chrome/HUD 面的活,今天两端都还没接
348
+ // (adapt 臂表同样无此臂),缺口记 0.13.0 候选,不许由本切片顺手编一个假 transcript 形。
349
+ case 'compaction_outcome':
350
+ return nothing('not_in_slice');
295
351
  default:
296
- // Forward-compat: log+skip an arm this slice predates (contract 02 §3).
297
- return null;
352
+ // B5 穷举:SDK union 加成员必须在**编译期**打红这一行(而不是让新臂在用户面静默丢帧)
353
+ assertNeverArm(ev);
354
+ // 运行期:wire 是 JSON,引擎完全可能比本包的类型新一版 —— 如实报 dropped,由 run driver
355
+ // 上报(REF-CC-057:此处此前写着 `log+skip` 而全函数零 log,那句承诺在码里根本不存在)。
356
+ return dropped('unknown_arm', String(ev.type ?? 'unknown'));
298
357
  }
299
358
  }
300
359
  /**
@@ -314,12 +373,13 @@ export function turnEndUsage(ev) {
314
373
  * ⚠️ DEAD CODE — 不是「HITL 分支漏接」(2026-07-26 全树核查,task #81 顺带件):
315
374
  * · 全树(src/ + mock/ + build/ + 测试)**零调用点**,adapter facade(src/seam/adapter/index.ts)
316
375
  * 也没把它再导出去 —— 没有任何一处「本该调它却没调」。
317
- * · 它想服务的那条 HITL 分支**已经接好了**,只是没走这个函数:
318
- * - `suspended` 事件臂 → `src/seam/adapter/upstream/hitlBridge.ts:162-168` 的 `observe()`
319
- * 就地做了**同款**防御回落(`ev.gate ? ev.gate : { kind: 'human' }`),而且 latch 的是整个
320
- * CheckpointGate 对象,壳侧 `currentGate()` 自己 branch `gate.kind`;
321
- * - `done{status:'suspended', checkpointGate}` 终帧 `liveHitlAskWire.ts:89-118` /
322
- * `terminalToSdkResult.ts:296-311` 各自读 `checkpointGate.kind` **和** `.toolName`。
376
+ * · 它想服务的那条 HITL 分支**已经接好了**,只是没走这个函数(REF-CC-063:此处原为四个行号锚,
377
+ * 今天全部已腐,改符号锚):
378
+ * - `suspended` 事件臂 → `hitl/hitlBridge.ts` `HitlBridge.observe()` 就地做了**同款**防御
379
+ * 回落(`ev.gate ? ev.gate : { kind: 'human' }`),而且 latch 的是整个 CheckpointGate 对象,
380
+ * 壳侧 `currentGate()` 自己 branch `gate.kind`;
381
+ * - `done{status:'suspended', checkpointGate}` 终帧 `liveHitlAskWire.ts` ask 合成口 /
382
+ * `terminalToSdkResult.ts` 的 park 臂各自读 `checkpointGate.kind` **和** `.toolName`。
323
383
  * · 关键:每个真实消费方都同时需要 `toolName`(挑 Ask overlay vs fs 写门三选卡),而本函数只吐
324
384
  * 一个 kind 字符串 —— 签名比任何真实需求都窄,接进去反而要再读一次 gate。
325
385
  * 结论 = 可删的死码(留档不擅删,按 task #81 口径只标记);真要收,连同 `CheckpointGate` 的