@sema-agent/client-core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,8 +23,8 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
23
23
 
24
24
  ## Scope
25
25
 
26
- - **Today (0.2.0)** — the adapter seam plus the first half of the `adapt()` pipeline (detail
27
- below), migrated as-is from `wire-cc-adapter` 0.1.x.
26
+ - **Today (0.5.0)** — the adapter seam, the `adapt()` pipeline, the notification/caps/model
27
+ families, and the adapter kernel (stream driver + downstream projectors).
28
28
  - **Planned** — the shell's remaining shared session logic lands here in batches: the
29
29
  five-layer frame dispatch, the compensation arms, the notification/dedup ledgers, the
30
30
  fleet/panel projections. The TUI then keeps only its Ink rendering and keyboard interaction;
@@ -106,16 +106,47 @@ behaviour checks.
106
106
  - 🔴 One host assembly duty: `installNotificationQueuePort()`. Without it, task-notification
107
107
  delivery is dropped — `notificationQueuePortMisses()` must stay 0.
108
108
 
109
+ 0.5.0 moved in the **adapter kernel** — the stream driver and the downstream projectors — and
110
+ grew the host context to carry what a non-Node host must supply:
111
+
112
+ - `AdapterContext` gained five optional ports (**minor, not breaking**): `log`, `probe`, `signal`,
113
+ `setTimer`/`clearTimer`, `coalesceIntervalMs`. Every one is native in a browser
114
+ (`console`, omit, `AbortController.signal`, `setTimeout`, a number) — no polyfill. Absent means
115
+ *that capability is off*, never a fallback: the kernel does not reach for `console`, `setTimeout`
116
+ or a filesystem probe on its own.
117
+ - `adapter/runStream.ts` + `adapter/downstream/*` + the runtime half of `adapter/types.ts` moved in.
118
+ The driver used to drag **1,571 files / 26 Node builtins / 412 react files** into its transitive
119
+ closure through two dynamic imports back into the shell; both are now `ChromeEvent` arms
120
+ (`last_turn_usage`, `plan_review_park`) with their host duty written on the arm. Measured closure
121
+ after the cut: **6 files, zero Node builtins, zero react**.
122
+ - Six stream-driver behaviours landed with it: the turn-open triple-clear (the third clear —
123
+ stale prompt suggestions — plus the fifth sub-flow cut-out), the 1.5 s IDLE-FLUSH race that
124
+ surfaces already-generated content while the model is emitting a long tool argument, manual
125
+ iterator cleanup, the abort branch, and the no-`result` tail leg.
126
+ - The coalescing **cadence** is now a host knob. CC itself runs two values in two hosts (its
127
+ desktop host coalesces single-frame deltas at 16 ms; the 2.1.207 CLI line we track uses 100 ms),
128
+ so the cadence is host render policy while the merge *semantics* stay here, in one place.
129
+
109
130
  Known intentional deltas from the CLI are enumerated in `ADAPTER_DIVERGENCES`.
110
131
 
111
132
  ## Guards
112
133
 
113
134
  ```bash
114
135
  npm install
115
- node scripts/run-client-core-pure-test.mjs # 370 consumer-view checks (22 + 140 + 208); no CLI tree needed
116
- node scripts/run-client-core-diff-test.mjs # 203 checks: differential + replay-id + ledger
136
+ node scripts/run-client-core-pure-test.mjs # 429 consumer-view checks (22 + 140 + 208 + 59); no CLI tree needed
137
+ node scripts/run-client-core-portability-test.mjs # 14 checks: kernel closure + browser bundle
138
+ node scripts/run-client-core-diff-test.mjs # 284 checks: differential + replay-id + ledger
117
139
  ```
118
140
 
141
+ Each suite carries a floor that only moves up — a refactor that stops executing a group of
142
+ assertions is a failure, not a quieter pass.
143
+
144
+ The portability guard walks the value-level import closure of the stream driver (asserting zero
145
+ Node builtins, zero react, zero shell debug edge, closure size under its ceiling) and then really
146
+ bundles it with esbuild `--platform=browser`. It carries its own **positive control**: a
147
+ deliberately poisoned file must be reported by the same scanner, otherwise the run exits 9
148
+ (the tool is broken) rather than green.
149
+
119
150
  The differential half needs the CLI tree (the reference bridge lives there). It is located via
120
151
  `SEMA_CLI_ROOT`, else a sibling checkout of the sema CLI source tree (internal reference bridge; not part of the published package); a pinned `SEMA_CLI_ROOT` without
121
152
  `src/sema/upstreamBridge.ts` fails loudly instead of falling back to another tree. With no CLI
package/dist/adapt.d.ts CHANGED
@@ -1,6 +1,31 @@
1
1
  import type { AdapterContext, AdapterOutput, WireToCcAdapter } from './seam.js';
2
- /** cli COALESCER 节流(207 口径;187 时代是 16ms)。宿主无关,时钟从 ctx.now() 取。 */
2
+ /**
3
+ * 活体增量攒批节拍的**默认值**(时钟从 ctx.now() 取;宿主可用 `ctx.coalesceIntervalMs` 覆盖)。
4
+ *
5
+ * 取值依据(两个都是实测,别把其中一个当"唯一真相"):
6
+ * · **100ms** = 我们对标的 CC 2.1.207 CLI 口径(187 时代是 16ms,207 提到 100ms 降重绘频率,
7
+ * 长回答滚动更稳)—— 壳是 207 fork,所以库的默认值取它,壳消费本包时行为零变化;
8
+ * · **16ms** = CC **桌面端**宿主 `makeStreamDeltaCoalescer` 的单帧攒批节拍(设计稿 R1 实测)。
9
+ * ⇒ 结论:**节拍是宿主渲染策略,不是 wire 语义** —— CC 自己两个端就是两个值。web/桌面宿主
10
+ * 想要 16ms 就传 `coalesceIntervalMs: 16`,合并**语义**(思考先于文本、段边界重开块、
11
+ * committed 恒随后)在库里,一份。
12
+ *
13
+ * 🔴 顺带钉一句反面事实(R1 证伪,别再写进任何注释/文档):**"UI 从不见裸 token" 是错的** ——
14
+ * CC 攒批后 flush 的载荷仍是 `stream_event` 增量 delta,CC 的 UI 照样消费增量;
15
+ * 完整 assistant 消息是 SDK 另发的一路。我们这里的 `stream_delta` chrome 臂同构。
16
+ */
3
17
  export declare const FLUSH_INTERVAL_MS = 100;
18
+ /**
19
+ * T40 IDLE-FLUSH 竞速窗(cli upstreamBridge `IDLE_FLUSH_MS` 逐字同值)。
20
+ *
21
+ * 病:模型转入长工具参数生成(大 Write 的 input_json 不经本桥)时 SSE 增量停摆,而 thinking/
22
+ * answerSegment 里**已经生成**的内容还押着陪等(pty 确证:11s 整屏 hash 恒定)。
23
+ * 修:增量停 ≥1.5s 且缓冲非空 ⇒ 先 drainLive()(活体预览语义不变),再提交思考+文本段
24
+ * (段序铁律不破:本来就该在下一张工具卡前 flush,这里只是提前)。
25
+ * 🔴 这是**过渡修**,正解在 core [817](wire 侧给事件);且**需要宿主定时器**:
26
+ * `ctx.setTimer` 缺席 ⇒ 整条竞速不启用(纯等下一帧),与 0.4.0 逐字同行为。
27
+ */
28
+ export declare const IDLE_FLUSH_MS = 1500;
4
29
  /** cli SUBAGENT_TOOL_NAMES / WORKFLOW_TOOL_NAMES 同源(判别资产,别各自重写)。 */
5
30
  export declare const SUBAGENT_TOOL_NAMES: ReadonlySet<string>;
6
31
  export declare const WORKFLOW_TOOL_NAMES: ReadonlySet<string>;
@@ -13,6 +38,15 @@ export declare const CANCEL_MESSAGE = "The user doesn't want to take this action
13
38
  /** CJK 加权 token 估算(cli estimateCjkTokens 逐字:CJK≈0.6,其余≈0.25 tok/char)。 */
14
39
  export declare function estimateCjkTokens(s: string): number;
15
40
  /** 引擎 subagent 卡的短标签(cli shortTaskLabel 逐字)。 */
41
+ /**
42
+ * T8 —— 压平 §E1 wire `output`(**非均匀**:`string` | `(TextContent|ImageContent)[]`,events.d.ts:78)
43
+ * 成一条纯文本(cli `flattenWireOutput` 逐字)。text 块贡献 `.text`,image 块贡献中性占位 `[image]`
44
+ * (转录卡是文本粒度),裸字符串直通。
45
+ * 🔴 载荷 UNTRUSTED + 仅供观测(events.d.ts:80-85):渲染可以,**绝不回喂模型**;`truncated` 的更不行。
46
+ * B4 把它从 B 层(`wireOutputToBody`)提到独立导出:A 层的 `settlePanelTasks(report)` 也要用它
47
+ * (关卡时把子代最终报告串到面板行 end 事件上),两处必须同一份实现。
48
+ */
49
+ export declare function flattenWireOutput(output: unknown): string;
16
50
  export declare function shortTaskLabel(task: string): string;
17
51
  /**
18
52
  * tool_use 块的渲染面消毒(cli assistant 臂逐条搬运,**纯函数**,不改入参):
@@ -28,6 +62,7 @@ export declare function sanitizeToolUseBlock(block: Record<string, unknown>): {
28
62
  rawInput: unknown;
29
63
  eventId?: string;
30
64
  parentToolCallId?: string;
65
+ label?: string;
31
66
  };
32
67
  /** 台账序列化形(#63 回植:重启恢复 = 台账加载 + 纯重放)。 */
33
68
  export interface AdapterLedgerState {
@@ -42,14 +77,18 @@ export interface AdapterLedgerState {
42
77
  /** 本批覆盖清单——差分守卫按它区分「已覆盖臂必须逐字段等价」与「已声明未覆盖臂」。 */
43
78
  export declare const ADAPTER_COVERAGE: {
44
79
  /** 已落码的帧臂。 */
45
- readonly frames: readonly ["assistant", "user", "stream_event", "turn_usage", "result", "system", "task_notification", "workflow_complete", "diagnostics", "steering_injected", "workspace_changed", "retry_status", "tool_end_result( response-id 复位 + 开卡台账出栈,不铸 tool_result)"];
80
+ readonly frames: readonly ["assistant", "user", "stream_event", "turn_usage", "result", "system", "task_notification", "workflow_complete", "diagnostics", "steering_injected", "workspace_changed", "prompt_suggestions", "retry_status", "task_progress", "tool_end_result(label 补位 + 关卡 settle + response-id 复位 + 开卡台账出栈;不铸 tool_result)"];
46
81
  /** 已落码臂产出的 transcript 消息类目(差分守卫的比对域)。 */
47
82
  readonly transcriptKinds: readonly ["assistant_text", "assistant_thinking", "assistant_tool_use", "user_tool_result_decision", "user_task_notification", "system_passthrough"];
83
+ /** B3(0.5.0)新落码的臂/半场 —— 从 todo 移过来的,别再在 todo 里留同名条目。 */
84
+ readonly b3: readonly ["T38 三清第三清 + T57 第五处断闸:prompt_suggestions(turn 开场作废 + 子流断闸)", "T40 IDLE-FLUSH 1.5s 竞速(需 ctx.setTimer;缺席=不启用)", "T58 手动 iterator 的 finally cleanup(early break/throw 都要关上游生成器)", "T59 abort 半场(ctx.signal;中断后不产终答)", "T60 无 result 收尾腿(泄尾增量 → 清 liveness → 尾思考 → 尾文本段)"];
85
+ /** B4(0.6.0)新落码的臂/半场 —— A 层收官批,同样别再在 todo 里留同名条目。 */
86
+ readonly b4: readonly ["A 层第 14 臂 task_progress:workflow lane 三级门 + 判据④ belt 回植 + C1 alias + bindTaskCard(显式父/FIFO/含糊即 unbound)+ inline stats + T50 双形读 + SubagentStart + #6 resident 台账 + 面板 tick", "settlePanelTasks 三处 sweep 全接线(tool_end 显式关卡带报告 / result 防御 sweep / turn 末 + abort sweep)", "P0-2 tool label 侧信道(sanitizeToolUseBlock 摘 label + engineToolLabelStore 登记;0.5.0 真缺口,label 会随 tool_use block 落转录 → /compact 直发 provider 400)", "task_notification 的两件宿主义务收回库内自持:dropQueuedNotificationsForRun + clearEnginePanelTaskResident;stop hook 的 if-started 门由库解", "T8 flattenWireOutput 提为独立导出(A 层 settle 报告与 B 层卡体同一份)"];
48
87
  /** 如实留白——写不动/依赖未搬运资产的臂,#52b 起分批接。 */
49
- readonly todo: readonly ["tool_end_result → user tool_result 正常路径(需搬 wireOutputToBody/structuredToToolUseResult/mock 合成器 + TodoWrite/ReportFindings 富卡片,约 600 行;workflow 轮询信封的人话投影已在 workflow.ts 就位,等这条接线)", "tool_end_result 的两个侧产物:Remember → memory_saved 系统消息、task/task-list structured → 面板台账全量同步", "result/turn 末的开卡兜底关闭(同上,依赖合成器)", "task_progress → 面板行绑定(bindTaskCard FIFO/显式父、inline stats、alias、resident 台账、SubagentStart hook、workflow lane 三级门)", "settlePanelTasks 三处 sweep(依赖上面的行台账)", "#117a bg Bash 回执探测(detectEngineBgShellReceipt 文案锚定探测器)", "abort/Esc 半场(AdapterContext 无 signal 位——见 SEAM-GAP-4)", "IDLE_FLUSH 1.5s 竞速(件1 Write 静默窗;需 ctx 侧定时器能力)", "macrotask 让渡(cli 在提交前让出宏任务给 Ink;宿主渲染策略,不进库)"];
88
+ readonly todo: readonly ["tool_end_result → user tool_result 正常路径(需搬 wireOutputToBody/structuredToToolUseResult/mock 合成器 + TodoWrite/ReportFindings 富卡片,约 600 行;workflow 轮询信封的人话投影已在 workflow.ts 就位,等这条接线)= B5", "tool_end_result 的两个侧产物:Remember → memory_saved 系统消息、task/task-list structured → 面板台账全量同步 = B5", "result/turn 末的开卡兜底关闭(同上,依赖合成器)= B5", "#117a bg Bash 回执探测(detectEngineBgShellReceipt 文案锚定探测器)= B5(E 层按工具名分臂)", "abort 半场里依赖开卡合成器的那件(兜底关卡)= B5;行 sweep 半场 B4 已接", "macrotask 让渡(cli 在提交前让出宏任务给 Ink;宿主渲染策略,不进库)"];
50
89
  };
51
90
  /** 本批**已知的、有意的**行为分歧(差分守卫显式登记,绝不当等价) */
52
- export declare const ADAPTER_DIVERGENCES: readonly [string, string, string];
91
+ export declare const ADAPTER_DIVERGENCES: readonly [string, string, string, string, string, string];
53
92
  /** 有台账序列化位的适配器(seam 的 WireToCcAdapter 只定了 exportLedger——见 SEAM-GAP-1)。 */
54
93
  export interface WireToCcAdapterWithLedger extends WireToCcAdapter {
55
94
  exportLedger(): AdapterLedgerState & Record<string, unknown>;