@sema-agent/client-core 0.3.0 → 0.5.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 (69) hide show
  1. package/README.md +57 -4
  2. package/dist/adapt.d.ts +30 -3
  3. package/dist/adapt.js +490 -354
  4. package/dist/adapter/downstream/eventToSdkMessage.d.ts +81 -0
  5. package/dist/adapter/downstream/eventToSdkMessage.js +330 -0
  6. package/dist/adapter/downstream/terminalToSdkResult.d.ts +71 -0
  7. package/dist/adapter/downstream/terminalToSdkResult.js +370 -0
  8. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +26 -0
  9. package/dist/adapter/downstream/turnUsageToModelUsage.js +12 -0
  10. package/dist/adapter/runStream.d.ts +38 -0
  11. package/dist/adapter/runStream.js +177 -0
  12. package/dist/adapter/types.d.ts +73 -0
  13. package/dist/adapter/types.js +23 -0
  14. package/dist/agentsWireCaps.d.ts +112 -0
  15. package/dist/agentsWireCaps.js +319 -0
  16. package/dist/attachmentsWireCaps.d.ts +47 -0
  17. package/dist/attachmentsWireCaps.js +46 -0
  18. package/dist/classifierVerdictWire.d.ts +45 -0
  19. package/dist/classifierVerdictWire.js +76 -0
  20. package/dist/clientContextWireCaps.d.ts +46 -0
  21. package/dist/clientContextWireCaps.js +48 -0
  22. package/dist/cloudConfigWireCaps.d.ts +110 -0
  23. package/dist/cloudConfigWireCaps.js +228 -0
  24. package/dist/controlRouter.d.ts +191 -0
  25. package/dist/controlRouter.js +244 -0
  26. package/dist/effortWire.d.ts +34 -0
  27. package/dist/effortWire.js +38 -0
  28. package/dist/engineWireSdk.d.ts +49 -0
  29. package/dist/engineWireSdk.js +61 -0
  30. package/dist/forkWireCaps.d.ts +25 -0
  31. package/dist/forkWireCaps.js +42 -0
  32. package/dist/hostEnv.d.ts +16 -0
  33. package/dist/hostEnv.js +17 -0
  34. package/dist/imagesWireCaps.d.ts +28 -0
  35. package/dist/imagesWireCaps.js +49 -0
  36. package/dist/index.d.ts +52 -0
  37. package/dist/index.js +62 -0
  38. package/dist/liveModelCatalog.d.ts +62 -0
  39. package/dist/liveModelCatalog.js +79 -0
  40. package/dist/mcpWireCaps.d.ts +41 -0
  41. package/dist/mcpWireCaps.js +51 -0
  42. package/dist/modelBudgetRule.d.ts +44 -0
  43. package/dist/modelBudgetRule.js +73 -0
  44. package/dist/modelWireCaps.d.ts +17 -0
  45. package/dist/modelWireCaps.js +16 -0
  46. package/dist/notifications.d.ts +150 -4
  47. package/dist/notifications.js +472 -0
  48. package/dist/permissionWireCaps.d.ts +37 -0
  49. package/dist/permissionWireCaps.js +46 -0
  50. package/dist/promptProfileWireCaps.d.ts +17 -0
  51. package/dist/promptProfileWireCaps.js +20 -0
  52. package/dist/retainBackgroundWireCaps.d.ts +50 -0
  53. package/dist/retainBackgroundWireCaps.js +58 -0
  54. package/dist/rewindWireCaps.d.ts +36 -0
  55. package/dist/rewindWireCaps.js +41 -0
  56. package/dist/seam.d.ts +97 -2
  57. package/dist/selfOrchestrationWireCaps.d.ts +40 -0
  58. package/dist/selfOrchestrationWireCaps.js +48 -0
  59. package/dist/sessionModelLatch.d.ts +35 -0
  60. package/dist/sessionModelLatch.js +63 -0
  61. package/dist/skillsWireCaps.d.ts +47 -0
  62. package/dist/skillsWireCaps.js +41 -0
  63. package/dist/sseIdleTriage.d.ts +94 -0
  64. package/dist/sseIdleTriage.js +142 -0
  65. package/dist/ultracodeWireCaps.d.ts +90 -0
  66. package/dist/ultracodeWireCaps.js +159 -0
  67. package/dist/webSearchWireCaps.d.ts +54 -0
  68. package/dist/webSearchWireCaps.js +76 -0
  69. package/package.json +2 -2
@@ -0,0 +1,81 @@
1
+ /**
2
+ * ⇄ B3 批搬迁(2026-07-27,多端改造设计稿 §3 B3):cli src/seam/adapter/downstream/eventToSdkMessage.ts
3
+ * **逐字整搬**(闭包实测 5 文件 / 零 Node 内建 / 零 react —— 本来就可移植,一个 import 都不用改)。
4
+ */
5
+ /**
6
+ * DOWNSTREAM projector (T22): `AgentEvent` → CC `SDKMessage` arms, the contract
7
+ * 02 §6 translation table run FORWARD, for the thin slice.
8
+ *
9
+ * Handled arms (contract 02 §2 / 08 CS-1..CS-7, CS-10, CS-11):
10
+ * reasoning → assistant (thinking block) §2.1 / CS-3
11
+ * text → assistant (text block) §2.2 / CS-1
12
+ * reasoning_delta→ stream_event (thinking_delta) §2.3 / CS-4
13
+ * text_delta → stream_event (text_delta) §2.4 / CS-2
14
+ * tool_start → assistant (tool_use block) §2.5 / CS-5
15
+ * tool_end → (internal) tool_use_result arm carrying the §E1 wire output §2.6 / CS-6
16
+ * turn_end → (folded into the terminal/usage rollup; no standalone arm)
17
+ * compacted → system / compact_boundary §2.8 / CS-8
18
+ * status → (internal) retry_status arm (BrainStatus liveness → spinner row) §E3/§E10
19
+ * done → result / success §2.10 / CS-10
20
+ * failed → result / error §2.11 / CS-11
21
+ *
22
+ * Leak guards (contract 02 §5): the `message`/`event` fields are CC's opaque
23
+ * `z.unknown()` placeholders. We populate them with NEUTRAL, CC-native content
24
+ * blocks the renderer reads — NEVER the raw Anthropic `APIAssistantMessage`
25
+ * block array or `RawMessageStreamEvent` wire, never `signature`/
26
+ * `redacted_thinking`/`signature_delta`.
27
+ *
28
+ * §E1 update (was STALE): `tool_end` DOES carry the model-facing `output` (the
29
+ * action-card BODY) on the wire now (events.d.ts:75-86). We carry it through —
30
+ * but NOT as a raw Anthropic `tool_result` wire block: a NEUTRAL internal arm
31
+ * `{type:'tool_end_result', toolCallId, isError, output, truncated, eventId}` the
32
+ * upstream bridge correlates to the open card by `toolCallId`. The output is
33
+ * 🔴 UNTRUSTED / OBSERVABILITY-ONLY (events.d.ts:80-85): render it, never re-feed
34
+ * a truncated body to a model.
35
+ */
36
+ import type { AgentEvent, CheckpointGate } from '@sema-agent/sdk';
37
+ import { type SDKMessage, type EmitContext, type ModelUsage } from '../types.js';
38
+ /**
39
+ * Project one durable/live `AgentEvent` into zero-or-one renderable `SDKMessage`.
40
+ *
41
+ * `tool_end` projects to a NEUTRAL internal `tool_end_result` arm (NOT a CC
42
+ * SDKMessage the renderer reads) carrying the §E1 wire `output` so the bridge can
43
+ * close the open card with the real body.
44
+ *
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
+ * Terminal `done`/`failed` are delegated to `terminalToSdkResult`.
51
+ */
52
+ export declare function eventToSdkMessage(ev: AgentEvent, ctx: EmitContext): SDKMessage | null;
53
+ /**
54
+ * CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
55
+ * costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
56
+ * standalone usage SDKMessage arm; the run driver folds it into the footer /
57
+ * terminal rollup. Returns `undefined` when the optional `usage` is absent.
58
+ */
59
+ export declare function turnEndUsage(ev: Extract<AgentEvent, {
60
+ type: 'turn_end';
61
+ }>): ModelUsage | undefined;
62
+ /**
63
+ * Defensive open-set read of a `suspended` gate kind for the run driver's HITL
64
+ * branch — known kinds plus a generic fallback (contract 02 §2.9 / §3: never
65
+ * crash on an unknown kind). Kept here so the §6 mapping lives in one file.
66
+ *
67
+ * ⚠️ DEAD CODE — 不是「HITL 分支漏接」(2026-07-26 全树核查,task #81 顺带件):
68
+ * · 全树(src/ + mock/ + build/ + 测试)**零调用点**,adapter facade(src/seam/adapter/index.ts)
69
+ * 也没把它再导出去 —— 没有任何一处「本该调它却没调」。
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`。
76
+ * · 关键:每个真实消费方都同时需要 `toolName`(挑 Ask overlay vs fs 写门三选卡),而本函数只吐
77
+ * 一个 kind 字符串 —— 签名比任何真实需求都窄,接进去反而要再读一次 gate。
78
+ * 结论 = 可删的死码(留档不擅删,按 task #81 口径只标记);真要收,连同 `CheckpointGate` 的
79
+ * type-only import 一起摘。
80
+ */
81
+ export declare function gateKind(gate: CheckpointGate | null | undefined): string;
@@ -0,0 +1,330 @@
1
+ import { stamp, } from '../types.js';
2
+ import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
3
+ // #51: `stamp<T extends { uuid?: string; session_id?: string }>` — passing an
4
+ // arm-body object literal straight to `stamp` triggers a TS quirk: the
5
+ // literal gets excess-property-checked against the CONSTRAINT (which only
6
+ // knows uuid/session_id) before T is fully inferred, so every other field
7
+ // (`type`, `taskId`, …) reads as "unknown property". Routing the literal
8
+ // through this identity helper first breaks that early check without
9
+ // touching runtime behavior (pure pass-through; `stamp` still assigns
10
+ // uuid/session_id exactly as before).
11
+ function armBody(body) {
12
+ return body;
13
+ }
14
+ /** Wrap neutral content blocks in the CC `assistant` message envelope. */
15
+ function assistantArm(ctx, content) {
16
+ return stamp(ctx, armBody({
17
+ type: 'assistant',
18
+ // `message` is coreSchemas APIAssistantMessagePlaceholder = z.unknown(): we
19
+ // hand the renderer a neutral { role, content } it can read, not the raw wire.
20
+ message: { role: 'assistant', content },
21
+ parent_tool_use_id: null,
22
+ }));
23
+ }
24
+ /** Wrap a neutral delta in the CC `stream_event` (partial assistant) envelope. */
25
+ function streamEventArm(ctx, event) {
26
+ return stamp(ctx, armBody({
27
+ type: 'stream_event',
28
+ // `event` is RawMessageStreamEventPlaceholder = z.unknown(): a neutral
29
+ // fragment descriptor, NOT a `content_block_delta` wire frame.
30
+ event,
31
+ parent_tool_use_id: null,
32
+ }));
33
+ }
34
+ /**
35
+ * Project one durable/live `AgentEvent` into zero-or-one renderable `SDKMessage`.
36
+ *
37
+ * `tool_end` projects to a NEUTRAL internal `tool_end_result` arm (NOT a CC
38
+ * SDKMessage the renderer reads) carrying the §E1 wire `output` so the bridge can
39
+ * close the open card with the real body.
40
+ *
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
+ * Terminal `done`/`failed` are delegated to `terminalToSdkResult`.
47
+ */
48
+ export function eventToSdkMessage(ev, ctx) {
49
+ // 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.
53
+ const raw = ev;
54
+ if (raw.type === 'workflow_complete') {
55
+ if (typeof raw.runId !== 'string')
56
+ return null;
57
+ return stamp(ctx, armBody({
58
+ type: 'workflow_complete',
59
+ runId: raw.runId,
60
+ status: raw.status === 'failed' ? 'failed' : 'completed',
61
+ 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
+ }));
123
+ }
124
+ switch (ev.type) {
125
+ // CS-1 §2.2 — turn-aggregated answer text (durable). Drop raw block array.
126
+ case 'text':
127
+ return assistantArm(ctx, [{ type: 'text', text: ev.text }]);
128
+ // CS-3 §2.1 — turn-aggregated thinking (durable). Distinct collapsible block.
129
+ case 'reasoning':
130
+ return assistantArm(ctx, [{ type: 'thinking', thinking: ev.text }]);
131
+ // CS-2 §2.4 — per-token answer fragment (live only). Typewriter append.
132
+ case 'text_delta':
133
+ return streamEventArm(ctx, { type: 'text_delta', delta: ev.delta });
134
+ // CS-4 §2.3 — per-token thinking fragment (live only). Drop signature_delta.
135
+ // §E2 identity (service 1.78 unblanked parentToolCallId on the wire, blackboard dogfood #2): thread it
136
+ // so the bridge can EXCLUDE a sub-flow's reasoning (orchestration/subagent) from the LEADER's spinner
137
+ // counter + thinking preview — the "134k tokens" overcount root cause (upstreamBridge TODO #9).
138
+ case 'reasoning_delta':
139
+ return streamEventArm(ctx, {
140
+ type: 'thinking_delta',
141
+ delta: ev.delta,
142
+ ...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
143
+ });
144
+ // CS-5 §2.5 — action-card open. args is `unknown`: render generically.
145
+ // §E2 — thread the wire eventId (+ parentToolCallId) so the bridge stamps it (SB-2).
146
+ case 'tool_start':
147
+ return assistantArm(ctx, [
148
+ {
149
+ type: 'tool_use',
150
+ id: ev.toolCallId,
151
+ name: ev.toolName,
152
+ input: ev.args,
153
+ // P0-2 — core 展示名(见 NeutralBlock.label 的完整对照记录)。防御读:SDK 0.0.103 已定型
154
+ // `label?: string`,但旧引擎/旧 server 缺席 ⇒ 键不出现,形状与修前逐字相同。
155
+ ...(typeof ev.label === 'string' && ev.label.length > 0 ? { label: ev.label } : {}),
156
+ ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
157
+ ...(ev.parentToolCallId !== undefined
158
+ ? { parentToolCallId: ev.parentToolCallId }
159
+ : {}),
160
+ },
161
+ ]);
162
+ // CS-8 §2.8 — compaction divider. tokensBefore ⇐ pre_tokens. trigger rides through when the
163
+ // engine sends it (core 1.156 manual verb → "manual"; absent ⇒ 'auto'). core 1.219
164
+ // (attachWorkingFiles default ON): `attachedFiles:[{path,chars,truncated}]` rides through raw so
165
+ // the bridge can project the CC post-compact "Read {path}" attachment rows (blackboard ask ②).
166
+ case 'compacted': {
167
+ const attachedFiles = ev.attachedFiles;
168
+ return stamp(ctx, armBody({
169
+ type: 'system',
170
+ subtype: 'compact_boundary',
171
+ compact_metadata: {
172
+ trigger: (ev.trigger === 'manual' ? 'manual' : 'auto'),
173
+ pre_tokens: ev.tokensBefore ?? 0,
174
+ },
175
+ ...(Array.isArray(attachedFiles) && attachedFiles.length > 0 ? { attachedFiles } : {}),
176
+ }));
177
+ }
178
+ // CS-6 §2.6 — tool_end now carries the §E1 model-facing `output` (the action-card
179
+ // BODY) + isError + the §E2 eventId. Emit a NEUTRAL internal arm the bridge correlates
180
+ // to the open card by `toolCallId`; `output`/`truncated` ride through as-is (UNTRUSTED,
181
+ // observability-only). `output === undefined` ⇒ the tool produced no body → the bridge
182
+ // falls back to the mock synthesizer (events.d.ts:79). Not a renderable CC SDKMessage —
183
+ // a side-channel arm `sdkMessagesToCcEvents` reads; CC's own renderer never sees it.
184
+ case 'tool_end': {
185
+ // design/64 [363] — `structured` is the engine's opt-in CC-shaped details field (core 1.199+
186
+ // AgentToolResult.details on the wire; not yet in the SDK tool_end arm — raw defensive read).
187
+ // Rides the neutral arm so the bridge can render the REAL rich card (diff / stdout panels).
188
+ const structured = ev.structured;
189
+ return stamp(ctx, armBody({
190
+ type: 'tool_end_result',
191
+ toolCallId: ev.toolCallId,
192
+ toolName: ev.toolName,
193
+ isError: ev.isError,
194
+ // P0-2([1725] 清单A)— `tool_end.label` 同 tool_start 待遇(server trace/project.js:61 白名单
195
+ // 本体产出)。此臂是壳内部侧信道(不进 transcript),所以这里带着无 provider 风险;
196
+ // tool_start 先到时 bridge 已登记过,这条是补位(只有 tool_end 带 label 的场景)。
197
+ ...(typeof ev.label === 'string' && ev.label.length > 0 ? { label: ev.label } : {}),
198
+ ...(ev.output !== undefined ? { output: ev.output } : {}),
199
+ ...(structured !== undefined ? { structured } : {}),
200
+ ...(ev.truncated !== undefined ? { truncated: ev.truncated } : {}),
201
+ ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
202
+ ...(ev.parentToolCallId !== undefined
203
+ ? { parentToolCallId: ev.parentToolCallId }
204
+ : {}),
205
+ }));
206
+ }
207
+ // §E3/§E10 — BrainStatus liveness (events.d.ts:44/:80 — rate_limited/retrying/reconnecting/
208
+ // circuit_open). LIVE-only + EPHEMERAL (never persisted/replayed). Project to a NEUTRAL internal
209
+ // `retry_status` arm forwarding the provider-neutral fields verbatim (phase/detail/retryInSec) —
210
+ // NOT a renderable CC SDKMessage. The upstream bridge (sema layer) maps phase→SpinnerRetryStatus +
211
+ // publishes to the sema retry-status store the spinner override reads (187's per-agent
212
+ // `afa(agentId).retryStatus` app-state analog, ui-modules/0157_036_ui_Klo.js:31). The seam stays a
213
+ // dumb forwarder: the provider-neutral→UI-shape mapping is the renderer layer's job (retryStatusStore).
214
+ case 'status':
215
+ return stamp(ctx, armBody({
216
+ type: 'retry_status',
217
+ phase: ev.phase,
218
+ ...(ev.detail !== undefined ? { detail: ev.detail } : {}),
219
+ ...(ev.retryInSec !== undefined ? { retryInSec: ev.retryInSec } : {}),
220
+ }));
221
+ // MF-10 (design/99, events.d.ts task_progress) — a SUBAGENT PROGRESS TICK: the delegated sub-run's
222
+ // CUMULATIVE usage at each turn boundary (LIVE/EPHEMERAL, never persisted/replayed). Project to a
223
+ // NEUTRAL internal `task_progress` arm — NOT a renderable CC SDKMessage. The upstream bridge binds it
224
+ // to the open Task/Agent tool_use card and synthesizes CC-native `progress` messages so CC's own
225
+ // Task-card rollup (tool uses / tokens climbing) animates for an ENGINE-side subagent exactly like a
226
+ // local AgentTool one. Fields are read DEFENSIVELY: the service wire whitelist (server.ts:1334-1342)
227
+ // strips `status` + EventIdentity and may omit `name` — mirror what actually rides the wire, not the
228
+ // richer core-side arm.
229
+ case 'task_progress': {
230
+ if (typeof ev.taskId !== 'string')
231
+ return null;
232
+ // core 1.262.0 [558]B / service 1.154.0 [579] — `currentAction`: the child's most recent tool
233
+ // intent as ONE human line ("Bash npm test"), derived from the child's tool_start (tool name +
234
+ // argument head) and service-redacted. SDK ≥0.0.108 已定型(events.d.ts task_progress 臂),
235
+ // cast 已退;runtime 门保留(旧 server 缺席 ⇒ 键不 stamp)。
236
+ const currentAction = ev.currentAction;
237
+ // W1 段1([1616]/[1617]②,frame-lane-matrix #2)— core 1.401 起前台 workflow 子代 tick 带
238
+ // `workflowRunId`/`workflowAgentLabel`(lane 归属自描述位)。此前这层 spread 白名单把两键剥掉,
239
+ // bridge 想判 lane 也判不了(「送到了但没人读」的精确坐标其实是「没转」)。additive 透传,
240
+ // 防御读(旧 core/server 缺席 ⇒ 形状不变)。注意 server 侧 project.ts 投影白名单在 1.272.0
241
+ // 仍剥这两键(server a76bd77 才修,黑板 [1619])——真 wire 于 1.273+ 才带,此处先备好通路。
242
+ const workflowRunId = ev.workflowRunId;
243
+ const workflowAgentLabel = ev.workflowAgentLabel;
244
+ return stamp(ctx, armBody({
245
+ type: 'task_progress',
246
+ taskId: ev.taskId,
247
+ ...(ev.name !== undefined ? { name: ev.name } : {}),
248
+ ...(ev.usage !== undefined ? { usage: ev.usage } : {}),
249
+ ...(typeof currentAction === 'string' && currentAction.length > 0
250
+ ? { currentAction }
251
+ : {}),
252
+ ...(typeof workflowRunId === 'string' && workflowRunId.length > 0
253
+ ? { workflowRunId }
254
+ : {}),
255
+ ...(typeof workflowAgentLabel === 'string' && workflowAgentLabel.length > 0
256
+ ? { workflowAgentLabel }
257
+ : {}),
258
+ ...(ev.parentToolCallId !== undefined
259
+ ? { parentToolCallId: ev.parentToolCallId }
260
+ : {}),
261
+ }));
262
+ }
263
+ // P0-1([1725] 清单A)— E12 `suggestions`:run 跑完(status:"completed")后 core 一次 LLM pass 产的
264
+ // 「下一步可问什么」建议**批**。此前这个臂掉进 `default: return null` ⇒ 全链死信。
265
+ // 🔴 语义(别按 CC 的单条模型套):CC 的 `prompt_suggestion` 是**每轮一条** `suggestion: string`;
266
+ // 引擎这条是**完成后一批** `string[]`(events.d.ts:132-135)——按批渲染/按批投影。
267
+ // 🔴 UNTRUSTED 模型文本(service 已 redact)、仅 UI、**绝不回喂模型** —— 所以投成 NEUTRAL 内部臂
268
+ // (不是可渲染的 CC SDKMessage):TUI 由 upstreamBridge 推给 chips store,print 由 seamQueryEngine
269
+ // 折成 CC 原生 `prompt_suggestion` 帧(coreSchemas.ts:1905 契约既有)。
270
+ // 🔴 产出面实测(2026-07-26,钉版 server 1.277.1 / core 1.416 直连):**只有 durable
271
+ // `/v1/runs` 腿产**(server runs.js:300-303 `spec.suggestNextPrompts && done && completed`);
272
+ // `/v1/tasks/stream` 带 `suggestNextPrompts:true` 也一帧不发(已真跑证伪)。壳的交互 turn 走
273
+ // tasks/stream ⇒ 这条通路今天只在 durable 车道(runs.create + runs.events:Ctrl+B/`/background`/
274
+ // ControlRouter)活。防御性上界:批量截 8 条、单条截 200 字(UI 面,不是模型面)。
275
+ 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));
280
+ if (list.length === 0)
281
+ return null;
282
+ return stamp(ctx, armBody({
283
+ type: 'prompt_suggestions',
284
+ suggestions: list,
285
+ }));
286
+ }
287
+ // §2.7 — turn_end is usage-only. §2.9 — suspended is HITL registration.
288
+ case 'turn_end':
289
+ case 'suspended':
290
+ return null;
291
+ // CS-10 / CS-11 — terminals are projected by terminalToSdkResult.
292
+ case 'done':
293
+ case 'failed':
294
+ return null;
295
+ default:
296
+ // Forward-compat: log+skip an arm this slice predates (contract 02 §3).
297
+ return null;
298
+ }
299
+ }
300
+ /**
301
+ * CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
302
+ * costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
303
+ * standalone usage SDKMessage arm; the run driver folds it into the footer /
304
+ * terminal rollup. Returns `undefined` when the optional `usage` is absent.
305
+ */
306
+ export function turnEndUsage(ev) {
307
+ return ev.usage ? turnUsageToModelUsage(ev.usage) : undefined;
308
+ }
309
+ /**
310
+ * Defensive open-set read of a `suspended` gate kind for the run driver's HITL
311
+ * branch — known kinds plus a generic fallback (contract 02 §2.9 / §3: never
312
+ * crash on an unknown kind). Kept here so the §6 mapping lives in one file.
313
+ *
314
+ * ⚠️ DEAD CODE — 不是「HITL 分支漏接」(2026-07-26 全树核查,task #81 顺带件):
315
+ * · 全树(src/ + mock/ + build/ + 测试)**零调用点**,adapter facade(src/seam/adapter/index.ts)
316
+ * 也没把它再导出去 —— 没有任何一处「本该调它却没调」。
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`。
323
+ * · 关键:每个真实消费方都同时需要 `toolName`(挑 Ask overlay vs fs 写门三选卡),而本函数只吐
324
+ * 一个 kind 字符串 —— 签名比任何真实需求都窄,接进去反而要再读一次 gate。
325
+ * 结论 = 可删的死码(留档不擅删,按 task #81 口径只标记);真要收,连同 `CheckpointGate` 的
326
+ * type-only import 一起摘。
327
+ */
328
+ export function gateKind(gate) {
329
+ return gate?.kind ?? 'human';
330
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ⇄ B3 批搬迁(2026-07-27,多端改造设计稿 §3 B3):cli src/seam/adapter/downstream/terminalToSdkResult.ts
3
+ * **逐字整搬**(闭包实测 4 文件 / 零 Node 内建)。`Date.now()`(elapsedMs)是浏览器原生,原样保留。
4
+ */
5
+ /**
6
+ * Terminal projector (T22): `done` → CC `SDKResultSuccess`, `failed` →
7
+ * `SDKResultError` (contract 02 §2.10/§2.11; 08 CS-10/CS-11/UC-2).
8
+ *
9
+ * done : result.result ⇐ TaskResult.result; result.model ⇐ TaskResult.model
10
+ * (MF-25 — the served model id, e.g. "deepseek-v4-pro"); stats ⇐
11
+ * flatten(TaskStats); modelUsage ⇐ stats.modelUsage (per-model split).
12
+ * DROP CC stop_reason / duration_api_ms / structured_output /
13
+ * fast_mode_state (contract 02 §2.10).
14
+ * Budget gate is a RESULT branch, not a throw: a `done` whose
15
+ * status/errorCode signals a budget cap still settles as a result
16
+ * (08 CS-10) — surfaced via TaskResult.errorCode upstream, not here.
17
+ * failed : flatten CC's 4 SDKResultError subtypes + errors[] into the single
18
+ * {errorCode, errorMessage} pair. A user-cancelled run settles here
19
+ * with errorCode:"cancelled" (08 CS-11).
20
+ * park : done{status:'suspended'|'needs_review'}(HITL/plan-review park)在无
21
+ * overlay 消费面(-p / 非 REPL)也走 SDKResultError 臂 —— 绝不投影成
22
+ * 空成功([884]A1,见下方分支注释)。
23
+ *
24
+ * ── 终态词表全枚举分流([909]B1,2026-07-16)────────────────────────────────────────────
25
+ * 引擎终态 = core 1.298 TaskStatus(dist/core/types.d.ts:315)全集,共 6 词;done 帧
26
+ * result.status 原样骑该词表(server runs.js append("done",{result:safe}) 透传)。此前只分流
27
+ * failed/suspended/needs_review,timeout/blocked 落 success 臂 = TB 实锤「result 帧恒
28
+ * success/is_error 恒 false,含 4 个引擎已记 timeout 的 trial」。CC 2.1.207 SDKResultError
29
+ * subtype 词表(真源 pretty.js:765781-765786 zod enum)只有 4 词。对表:
30
+ *
31
+ * TaskStatus errorCode(assemble-result.js) → subtype is_error
32
+ * ─────────────────────────────────────────────────────────────────────────────────────
33
+ * completed — → 'success' false
34
+ * failed budget.exceeded|budget.precall→ 'error_max_budget_usd' true
35
+ * failed limit.max_turns → 'error_max_turns' true
36
+ * failed output.invalid → 'error_max_structured_output_retries' true
37
+ * failed 其余(含 cancelled/conflict…) → 'error_during_execution' true
38
+ * timeout limit.timeout → 'error_max_turns' true
39
+ * (CC 无 wall-clock deadline 概念;词表内最贴 = 唯一 run-limit 族词
40
+ * error_max_turns。真实语义靠 errors[] 文案 + errorCode 透传辨认。)
41
+ * blocked —(engine 只给 blockedReason)→ 'error_during_execution' true
42
+ * suspended — → 'error_during_execution' true([884]A1)
43
+ * needs_review review.pending → 'error_during_execution' true([884]A1)
44
+ * 〈未知新词〉 — → 'error_during_execution' true(防御:
45
+ * 非 completed 的终态一律≠空成功;errors[] 里如实报出该 status 字面)
46
+ *
47
+ * errorCode 透传:引擎 errorCode 骑 additive seam 字段 `errorCode`(CC schema 无此字段——
48
+ * 与 success 臂的 `model` 同款超集姿势),集成面可精确分流(limit.timeout vs cancelled …)。
49
+ * timeout salvage:写出窗兑现的 r.result(非空)骑 additive `result` 字段——CC error 帧无
50
+ * result,但把引擎已救回的最终文本静默丢弃对 -p 消费者是净损失。
51
+ *
52
+ * costUsd === 0 is AMBIGUOUS (free vs. no MODEL_COST_* configured): disambiguate
53
+ * upstream via Capabilities.pricingConfigured (contract 02 §2.10 VERIFY); this
54
+ * projector passes the number through verbatim.
55
+ */
56
+ import type { AgentEvent } from '@sema-agent/sdk';
57
+ import { type SDKMessage, type EmitContext } from '../types.js';
58
+ /** `done` → SDKResultSuccess (contract 02 §2.10 / 08 CS-10). */
59
+ export declare function doneToSdkResult(ev: Extract<AgentEvent, {
60
+ type: 'done';
61
+ }>, ctx: EmitContext): SDKMessage;
62
+ /** `failed` → SDKResultError (contract 02 §2.11 / 08 CS-11). */
63
+ export declare function failedToSdkResult(ev: Extract<AgentEvent, {
64
+ type: 'failed';
65
+ }>, ctx: EmitContext): SDKMessage;
66
+ /** Dispatch a terminal AgentEvent to its SDKResult arm. */
67
+ export declare function terminalToSdkResult(ev: Extract<AgentEvent, {
68
+ type: 'done';
69
+ } | {
70
+ type: 'failed';
71
+ }>, ctx: EmitContext): SDKMessage;