@sema-agent/client-core 0.2.0 → 0.4.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 (80) hide show
  1. package/README.md +23 -1
  2. package/dist/agentsWireCaps.d.ts +112 -0
  3. package/dist/agentsWireCaps.js +319 -0
  4. package/dist/attachmentsWireCaps.d.ts +47 -0
  5. package/dist/attachmentsWireCaps.js +46 -0
  6. package/dist/classifierVerdictWire.d.ts +45 -0
  7. package/dist/classifierVerdictWire.js +76 -0
  8. package/dist/clientContextWireCaps.d.ts +46 -0
  9. package/dist/clientContextWireCaps.js +48 -0
  10. package/dist/cloudConfigWireCaps.d.ts +110 -0
  11. package/dist/cloudConfigWireCaps.js +228 -0
  12. package/dist/controlRouter.d.ts +191 -0
  13. package/dist/controlRouter.js +244 -0
  14. package/dist/effortWire.d.ts +34 -0
  15. package/dist/effortWire.js +38 -0
  16. package/dist/engineAgentPanelStore.d.ts +111 -0
  17. package/dist/engineAgentPanelStore.js +93 -0
  18. package/dist/engineCapsCache.d.ts +32 -0
  19. package/dist/engineCapsCache.js +64 -0
  20. package/dist/engineInlineTaskStats.d.ts +66 -0
  21. package/dist/engineInlineTaskStats.js +97 -0
  22. package/dist/engineToolLabelStore.d.ts +32 -0
  23. package/dist/engineToolLabelStore.js +55 -0
  24. package/dist/engineWireSdk.d.ts +49 -0
  25. package/dist/engineWireSdk.js +61 -0
  26. package/dist/fleetAgentPanelProjection.d.ts +31 -0
  27. package/dist/fleetAgentPanelProjection.js +0 -0
  28. package/dist/fleetTaskDesc.d.ts +20 -0
  29. package/dist/fleetTaskDesc.js +35 -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 +56 -1
  37. package/dist/index.js +62 -1
  38. package/dist/liveModelCatalog.d.ts +62 -0
  39. package/dist/liveModelCatalog.js +79 -0
  40. package/dist/liveQuestionStore.d.ts +101 -0
  41. package/dist/liveQuestionStore.js +109 -0
  42. package/dist/mcpWireCaps.d.ts +41 -0
  43. package/dist/mcpWireCaps.js +51 -0
  44. package/dist/modelBudgetRule.d.ts +44 -0
  45. package/dist/modelBudgetRule.js +73 -0
  46. package/dist/modelWireCaps.d.ts +17 -0
  47. package/dist/modelWireCaps.js +16 -0
  48. package/dist/notifications.d.ts +150 -4
  49. package/dist/notifications.js +472 -0
  50. package/dist/permissionWireCaps.d.ts +37 -0
  51. package/dist/permissionWireCaps.js +46 -0
  52. package/dist/printToolResultFrame.d.ts +59 -0
  53. package/dist/printToolResultFrame.js +65 -0
  54. package/dist/promptProfileWireCaps.d.ts +17 -0
  55. package/dist/promptProfileWireCaps.js +20 -0
  56. package/dist/retainBackgroundWireCaps.d.ts +50 -0
  57. package/dist/retainBackgroundWireCaps.js +58 -0
  58. package/dist/rewindWireCaps.d.ts +36 -0
  59. package/dist/rewindWireCaps.js +41 -0
  60. package/dist/selfOrchestrationWireCaps.d.ts +40 -0
  61. package/dist/selfOrchestrationWireCaps.js +48 -0
  62. package/dist/sessionModelLatch.d.ts +35 -0
  63. package/dist/sessionModelLatch.js +63 -0
  64. package/dist/skillsWireCaps.d.ts +47 -0
  65. package/dist/skillsWireCaps.js +41 -0
  66. package/dist/sseIdleTriage.d.ts +94 -0
  67. package/dist/sseIdleTriage.js +142 -0
  68. package/dist/steering.d.ts +4 -0
  69. package/dist/steering.js +4 -0
  70. package/dist/subagentContentStore.d.ts +131 -0
  71. package/dist/subagentContentStore.js +322 -0
  72. package/dist/types/engineState.d.ts +116 -0
  73. package/dist/types/engineState.js +17 -0
  74. package/dist/ultracodeWireCaps.d.ts +90 -0
  75. package/dist/ultracodeWireCaps.js +159 -0
  76. package/dist/webSearchWireCaps.d.ts +54 -0
  77. package/dist/webSearchWireCaps.js +76 -0
  78. package/dist/workflow.d.ts +40 -8
  79. package/dist/workflow.js +69 -11
  80. package/package.json +1 -1
@@ -0,0 +1,93 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineAgentPanelStore.ts 逐字搬入(零 import;壳侧改 re-export)。
3
+ * 🔴 同 subagentContentStore:buffer/listener/residentTaskIds 是单实例语义,发布侧(bridge/fleet
4
+ * 投影)与订阅侧(chrome-agentprogress 的面板 hook)必须同实例,否则事件发进一个副本、面板订另一个。
5
+ */
6
+ /**
7
+ * engineAgentPanelStore — dep-free side-channel store bridging ENGINE-side subagent progress
8
+ * (`task_progress` ticks, design/99 MF-10) to CC's CoordinatorTaskPanel (the footer live-agent panel).
9
+ *
10
+ * WHY A STORE (the retryStatusStore / liveQuestionStore pattern): the upstream bridge is a plain
11
+ * generator with no React/AppState access, while the panel reads `AppState.tasks` — so the bridge
12
+ * PUBLISHES neutral events here and a sema-owned hook inside chrome-agentprogress.tsx (already mounted
13
+ * under the footer) SUBSCRIBES and applies them to AppState via registerTask/updateTaskState. CC files
14
+ * stay verbatim; the AppState write side lives in the sema override that owns the panel.
15
+ *
16
+ * WHY THE PANEL (CC-187 parity): a local sync AgentTool renders an inline progress TRAIL from the
17
+ * subagent's real messages. An engine tick carries ONLY rollup numbers ({toolUses, totalTokens}) — the
18
+ * exact shape of CC's `AgentProgress` — and CC's own precedent for out-of-process agents (remote agent
19
+ * tasks) is the TASK PANEL + task-notification, not a synthetic inline trail. Faithful mapping, no
20
+ * fabricated subagent messages.
21
+ *
22
+ * Buffering: ticks published before the panel mounts are buffered (bounded) and replayed to the first
23
+ * subscriber, so early frames in a fast turn aren't lost to mount timing.
24
+ */
25
+ // ── #6(frame-lane-matrix)session 常驻行台账 ─────────────────────────────────────────────────
26
+ // 行寿命 ≠ turn 寿命:跨 turn 存活的 bg 子代行(tick 绑不到本 turn 开着的卡 = inert/unbound)不该被
27
+ // turn 末防御 sweep(bridge settlePanelTasks(null) / seamQuery 'sweep' 事件)结成假 completed——一旦
28
+ // 假结,消费端 tick 门(status!=='running' ⇒ return prev)会把后续真 tick 全丢,真终态(failed)也
29
+ // 改不动(冻死)。台账 module-level(publisher=bridge 按绑定活性写;两个 sweep 消费点按它跳过;
30
+ // 真终态通知(task_notification 帧 / enqueueBgChildNotification 合成)settle 时摘除)。
31
+ const residentTaskIds = new Set();
32
+ export function markEnginePanelTaskResident(taskId) {
33
+ residentTaskIds.add(taskId);
34
+ }
35
+ export function clearEnginePanelTaskResident(taskId) {
36
+ residentTaskIds.delete(taskId);
37
+ }
38
+ export function isEnginePanelTaskResident(taskId) {
39
+ return residentTaskIds.has(taskId);
40
+ }
41
+ const MAX_BUFFER = 200;
42
+ let listener = null;
43
+ const buffer = [];
44
+ export function publishEngineAgentPanelEvent(ev) {
45
+ if (listener) {
46
+ try {
47
+ listener(ev);
48
+ }
49
+ catch {
50
+ // A consumer render error must never tear down the bridge's stream drain.
51
+ }
52
+ return;
53
+ }
54
+ // Buffered (panel not mounted yet / temporarily unmounted). Ticks COALESCE per taskId (only the
55
+ // latest rollup matters — usage is cumulative), so lifecycle events ('end'/'sweep') are never the
56
+ // ones squeezed out by a long unmount (adversarial review w0zwpa251 #4: dropping a buffered 'end'
57
+ // left a row running forever).
58
+ if (ev.kind === 'tick') {
59
+ const i = buffer.findIndex(b => b.kind === 'tick' && b.taskId === ev.taskId);
60
+ if (i >= 0) {
61
+ buffer[i] = ev;
62
+ return;
63
+ }
64
+ }
65
+ if (buffer.length < MAX_BUFFER)
66
+ buffer.push(ev);
67
+ else if (ev.kind !== 'tick') {
68
+ // Lifecycle events must survive a full buffer: evict the oldest tick to make room.
69
+ const i = buffer.findIndex(b => b.kind === 'tick');
70
+ if (i >= 0)
71
+ buffer.splice(i, 1);
72
+ buffer.push(ev);
73
+ }
74
+ }
75
+ /** Single consumer (the panel hook). Replays any buffered events, returns an unsubscribe. */
76
+ export function subscribeEngineAgentPanel(fn) {
77
+ listener = fn;
78
+ if (buffer.length > 0) {
79
+ const pending = buffer.splice(0, buffer.length);
80
+ for (const ev of pending) {
81
+ try {
82
+ fn(ev);
83
+ }
84
+ catch {
85
+ // ignore — same guard as publish
86
+ }
87
+ }
88
+ }
89
+ return () => {
90
+ if (listener === fn)
91
+ listener = null;
92
+ };
93
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineCapsCache.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ * 🔴 **本批刻意不做「三套 caps 缓存收编成一套」**(设计稿 §2.4.1-B 风险点 + census §2.4):收编是
4
+ * 行为改动,要单独一条测试与单独一批(B1 只搬现行为,不夹带)。selfKnowledge / sideChannel 的
5
+ * 两套单键探测仍在壳侧,记账给下一批。
6
+ */
7
+ /**
8
+ * engineCapsCache — 引擎 /v1/capabilities 的通用 boot-kick 缓存(同步读口)。
9
+ *
10
+ * selfKnowledge/sideChannel 各自有单键探测(先行件,语义已钉测试);本模块是后续键的通用面
11
+ * (首个消费者:compactionModel,[1487]② server 1.243 接线的 cli 半场)。纪律同款:
12
+ * · kick 幂等(in-flight 去重),绝不 throw;
13
+ * · 判定固化;探测失败不缓存(引擎未起/瞬断 → 下次 kick 再判);
14
+ * · 同步读口 boolean(未判 = false = 调用方回落,version-safe)。
15
+ * 与单键探测不同处:缓存整个 caps 对象(一次探测服务后续所有键),false 不设 TTL——本缓存随
16
+ * 每次 createLiveConversationClient 构造重 kick(引擎温切重启后新构造自然重探)。
17
+ */
18
+ /** 构造期 kick(async 幂等);probe = client.capabilities 薄闭包。 */
19
+ export declare function kickEngineCapsProbe(baseUrl: string, probe: () => Promise<unknown>): void;
20
+ /** 同步读口:该 base 的 caps 里 key 是否字面 true(未判/缺键 = false)。 */
21
+ export declare function engineCapTrue(baseUrl: string | undefined, key: string): boolean;
22
+ /**
23
+ * 同步读口(字符串键):该 base 的 caps 里 key 的字符串值,未判/缺键/非字符串 ⇒ undefined。
24
+ *
25
+ * 首个消费者 = `hooksWireCaps` 的 `/goal` CC-Stop 语义判档,它要读 `capabilities.version`
26
+ * (SDK `Capabilities.version`,server 侧 = `serviceVersion()`,与 `/health.version` 同源)。
27
+ * 🔴 纪律同 {@link engineCapTrue}:**未判即 undefined**,调用方必须按「探不到 ⇒ 降级」处理 ——
28
+ * 引擎能力位缺席时任何「假定它有」的分支都是对用户/模型的谎报。
29
+ */
30
+ export declare function engineCapString(baseUrl: string | undefined, key: string): string | undefined;
31
+ /** 测试钩子。 */
32
+ export declare function __resetEngineCapsCacheForTests(): void;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineCapsCache.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ * 🔴 **本批刻意不做「三套 caps 缓存收编成一套」**(设计稿 §2.4.1-B 风险点 + census §2.4):收编是
4
+ * 行为改动,要单独一条测试与单独一批(B1 只搬现行为,不夹带)。selfKnowledge / sideChannel 的
5
+ * 两套单键探测仍在壳侧,记账给下一批。
6
+ */
7
+ /**
8
+ * engineCapsCache — 引擎 /v1/capabilities 的通用 boot-kick 缓存(同步读口)。
9
+ *
10
+ * selfKnowledge/sideChannel 各自有单键探测(先行件,语义已钉测试);本模块是后续键的通用面
11
+ * (首个消费者:compactionModel,[1487]② server 1.243 接线的 cli 半场)。纪律同款:
12
+ * · kick 幂等(in-flight 去重),绝不 throw;
13
+ * · 判定固化;探测失败不缓存(引擎未起/瞬断 → 下次 kick 再判);
14
+ * · 同步读口 boolean(未判 = false = 调用方回落,version-safe)。
15
+ * 与单键探测不同处:缓存整个 caps 对象(一次探测服务后续所有键),false 不设 TTL——本缓存随
16
+ * 每次 createLiveConversationClient 构造重 kick(引擎温切重启后新构造自然重探)。
17
+ */
18
+ const capsByBase = new Map();
19
+ const inFlight = new Set();
20
+ /** 构造期 kick(async 幂等);probe = client.capabilities 薄闭包。 */
21
+ export function kickEngineCapsProbe(baseUrl, probe) {
22
+ if (!baseUrl || capsByBase.has(baseUrl) || inFlight.has(baseUrl))
23
+ return;
24
+ inFlight.add(baseUrl);
25
+ void (async () => {
26
+ try {
27
+ const caps = await probe();
28
+ if (caps && typeof caps === 'object') {
29
+ capsByBase.set(baseUrl, caps);
30
+ }
31
+ }
32
+ catch {
33
+ // 失败不缓存:下次 kick 再判
34
+ }
35
+ finally {
36
+ inFlight.delete(baseUrl);
37
+ }
38
+ })();
39
+ }
40
+ /** 同步读口:该 base 的 caps 里 key 是否字面 true(未判/缺键 = false)。 */
41
+ export function engineCapTrue(baseUrl, key) {
42
+ if (!baseUrl)
43
+ return false;
44
+ return capsByBase.get(baseUrl)?.[key] === true;
45
+ }
46
+ /**
47
+ * 同步读口(字符串键):该 base 的 caps 里 key 的字符串值,未判/缺键/非字符串 ⇒ undefined。
48
+ *
49
+ * 首个消费者 = `hooksWireCaps` 的 `/goal` CC-Stop 语义判档,它要读 `capabilities.version`
50
+ * (SDK `Capabilities.version`,server 侧 = `serviceVersion()`,与 `/health.version` 同源)。
51
+ * 🔴 纪律同 {@link engineCapTrue}:**未判即 undefined**,调用方必须按「探不到 ⇒ 降级」处理 ——
52
+ * 引擎能力位缺席时任何「假定它有」的分支都是对用户/模型的谎报。
53
+ */
54
+ export function engineCapString(baseUrl, key) {
55
+ if (!baseUrl)
56
+ return undefined;
57
+ const v = capsByBase.get(baseUrl)?.[key];
58
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
59
+ }
60
+ /** 测试钩子。 */
61
+ export function __resetEngineCapsCacheForTests() {
62
+ capsByBase.clear();
63
+ inFlight.clear();
64
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineInlineTaskStats.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ * 🔴 单实例语义:写方 = bridge 的 task_progress tick,读方 = 行内 group 行的 useSyncExternalStore。
4
+ */
5
+ /**
6
+ * engineInlineTaskStats — dep-free side-channel store bridging ENGINE-side subagent progress
7
+ * (`task_progress` ticks) to the INLINE transcript group row ("Running N agents…" / AgentProgressLine).
8
+ *
9
+ * WHY (clay dogfood 2026-07-03): in engine mode the shell AgentTool's call() never runs, so the inline
10
+ * group row's per-agent stats (tool uses / tokens) and status line — both computed from LOCAL
11
+ * ProgressMessages (overrides/agent.tsx calculateAgentStats / extractLastToolInfo) — stay frozen at
12
+ * `0 tool uses` / `Initializing…` for the whole run and after completion. The engine's `task_progress`
13
+ * ticks DO carry the rollup ({toolUses, totalTokens, durationMs}) and the bridge already binds each
14
+ * tick to its delegating tool_use card (bindTaskCard, P1② explicit parentToolCallId) — but only fed
15
+ * the FOOTER panel store. This store is the inline twin: keyed by the delegating tool_use id (cardId),
16
+ * latest cumulative tick per card, frozen on the card's close (tool_end) / turn-end sweep so the
17
+ * finished group row keeps the final values.
18
+ *
19
+ * Same pattern as engineAgentPanelStore (module-level, no React/AppState deps on the write side); the
20
+ * read side is a useSyncExternalStore hook inside the sema-owned AgentProgressLine override — a
21
+ * subscription, NOT a render-time map read, because the memoized transcript tree won't re-render on
22
+ * ticks by itself. Local (non-engine) AgentTool runs never write here, so the local path is untouched
23
+ * (fallback only consulted when the card id has an entry).
24
+ */
25
+ export interface EngineInlineTaskStats {
26
+ /** Cumulative tool uses off the latest tick (frozen at settle). */
27
+ toolUses: number;
28
+ /** Cumulative tokens off the latest tick (frozen at settle). */
29
+ totalTokens: number;
30
+ /** Cumulative run duration off the latest tick — feeds the "Working…" status line. */
31
+ durationMs: number;
32
+ /** core 1.262.0 [558]B (service 1.154.0 wire) — the child's most recent tool intent as ONE human
33
+ * line ("Bash npm test"), service-redacted. Replaces the generic "Working…" status line while the
34
+ * card is live; absent on old engines ⇒ the status line is unchanged. */
35
+ currentAction?: string;
36
+ /** The card closed (tool_end or turn-end sweep): values above are final. */
37
+ done: boolean;
38
+ isError: boolean;
39
+ }
40
+ /** Bridge write side — latest cumulative tick for the card. No-op once the card settled (frozen finals). */
41
+ export declare function publishEngineInlineTaskTick(cardId: string, tick: {
42
+ toolUses: number;
43
+ totalTokens: number;
44
+ durationMs: number;
45
+ currentAction?: string;
46
+ }): void;
47
+ /** Bridge write side — the delegating card closed: freeze the finals. Idempotent. */
48
+ export declare function settleEngineInlineTaskStats(cardId: string, isError: boolean): void;
49
+ /** Turn-end / abort sweep twin — settle every card still live (a subagent never emits a terminal tick). */
50
+ export declare function settleAllEngineInlineTaskStats(): void;
51
+ /** Read side — reference-stable snapshot per card (object replaced only on update). */
52
+ export declare function getEngineInlineTaskStats(cardId: string): EngineInlineTaskStats | undefined;
53
+ /**
54
+ * Read side, id-less — the SOLE live (un-settled) card's stats, or undefined when 0 or ≥2 are live.
55
+ *
56
+ * WHY (#64 B 面 pty 实测,2026-07-11): the SINGLE-card in-flight render path
57
+ * (overrides/agent.tsx renderToolUseProgressMessage, zero local progressMessages in engine mode)
58
+ * has NO toolUseID in scope — the CC Tool-interface method signature doesn't carry it, and the
59
+ * call-site wrapper (AssistantToolUseMessage.tsx) is CC-verbatim. Attribution without an id is only
60
+ * safe when it is unambiguous, so this returns the stats ONLY when exactly one live card exists
61
+ * (the adversarial-review posture of bindTaskCard: better honest-generic than bound wrong; with ≥2
62
+ * live engine subagents the row keeps today's Initializing…). Reference-stable (returns the same
63
+ * per-card snapshot object the keyed getter hands out).
64
+ */
65
+ export declare function getSoleLiveEngineInlineTaskStats(): EngineInlineTaskStats | undefined;
66
+ export declare function subscribeEngineInlineTaskStats(fn: () => void): () => void;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineInlineTaskStats.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ * 🔴 单实例语义:写方 = bridge 的 task_progress tick,读方 = 行内 group 行的 useSyncExternalStore。
4
+ */
5
+ /**
6
+ * engineInlineTaskStats — dep-free side-channel store bridging ENGINE-side subagent progress
7
+ * (`task_progress` ticks) to the INLINE transcript group row ("Running N agents…" / AgentProgressLine).
8
+ *
9
+ * WHY (clay dogfood 2026-07-03): in engine mode the shell AgentTool's call() never runs, so the inline
10
+ * group row's per-agent stats (tool uses / tokens) and status line — both computed from LOCAL
11
+ * ProgressMessages (overrides/agent.tsx calculateAgentStats / extractLastToolInfo) — stay frozen at
12
+ * `0 tool uses` / `Initializing…` for the whole run and after completion. The engine's `task_progress`
13
+ * ticks DO carry the rollup ({toolUses, totalTokens, durationMs}) and the bridge already binds each
14
+ * tick to its delegating tool_use card (bindTaskCard, P1② explicit parentToolCallId) — but only fed
15
+ * the FOOTER panel store. This store is the inline twin: keyed by the delegating tool_use id (cardId),
16
+ * latest cumulative tick per card, frozen on the card's close (tool_end) / turn-end sweep so the
17
+ * finished group row keeps the final values.
18
+ *
19
+ * Same pattern as engineAgentPanelStore (module-level, no React/AppState deps on the write side); the
20
+ * read side is a useSyncExternalStore hook inside the sema-owned AgentProgressLine override — a
21
+ * subscription, NOT a render-time map read, because the memoized transcript tree won't re-render on
22
+ * ticks by itself. Local (non-engine) AgentTool runs never write here, so the local path is untouched
23
+ * (fallback only consulted when the card id has an entry).
24
+ */
25
+ const statsByCardId = new Map();
26
+ const listeners = new Set();
27
+ function emit() {
28
+ for (const fn of listeners) {
29
+ try {
30
+ fn();
31
+ }
32
+ catch {
33
+ // a consumer render error must never disturb the bridge's stream drain
34
+ }
35
+ }
36
+ }
37
+ /** Bridge write side — latest cumulative tick for the card. No-op once the card settled (frozen finals). */
38
+ export function publishEngineInlineTaskTick(cardId, tick) {
39
+ const prev = statsByCardId.get(cardId);
40
+ if (prev?.done)
41
+ return;
42
+ statsByCardId.set(cardId, { ...tick, done: false, isError: false });
43
+ emit();
44
+ }
45
+ /** Bridge write side — the delegating card closed: freeze the finals. Idempotent. */
46
+ export function settleEngineInlineTaskStats(cardId, isError) {
47
+ const prev = statsByCardId.get(cardId);
48
+ if (!prev || prev.done)
49
+ return;
50
+ statsByCardId.set(cardId, { ...prev, done: true, isError });
51
+ emit();
52
+ }
53
+ /** Turn-end / abort sweep twin — settle every card still live (a subagent never emits a terminal tick). */
54
+ export function settleAllEngineInlineTaskStats() {
55
+ let changed = false;
56
+ for (const [cardId, s] of statsByCardId) {
57
+ if (s.done)
58
+ continue;
59
+ statsByCardId.set(cardId, { ...s, done: true, isError: false });
60
+ changed = true;
61
+ }
62
+ if (changed)
63
+ emit();
64
+ }
65
+ /** Read side — reference-stable snapshot per card (object replaced only on update). */
66
+ export function getEngineInlineTaskStats(cardId) {
67
+ return statsByCardId.get(cardId);
68
+ }
69
+ /**
70
+ * Read side, id-less — the SOLE live (un-settled) card's stats, or undefined when 0 or ≥2 are live.
71
+ *
72
+ * WHY (#64 B 面 pty 实测,2026-07-11): the SINGLE-card in-flight render path
73
+ * (overrides/agent.tsx renderToolUseProgressMessage, zero local progressMessages in engine mode)
74
+ * has NO toolUseID in scope — the CC Tool-interface method signature doesn't carry it, and the
75
+ * call-site wrapper (AssistantToolUseMessage.tsx) is CC-verbatim. Attribution without an id is only
76
+ * safe when it is unambiguous, so this returns the stats ONLY when exactly one live card exists
77
+ * (the adversarial-review posture of bindTaskCard: better honest-generic than bound wrong; with ≥2
78
+ * live engine subagents the row keeps today's Initializing…). Reference-stable (returns the same
79
+ * per-card snapshot object the keyed getter hands out).
80
+ */
81
+ export function getSoleLiveEngineInlineTaskStats() {
82
+ let sole;
83
+ for (const s of statsByCardId.values()) {
84
+ if (s.done)
85
+ continue;
86
+ if (sole !== undefined)
87
+ return undefined; // ≥2 live — ambiguous, stay honest
88
+ sole = s;
89
+ }
90
+ return sole;
91
+ }
92
+ export function subscribeEngineInlineTaskStats(fn) {
93
+ listeners.add(fn);
94
+ return () => {
95
+ listeners.delete(fn);
96
+ };
97
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineToolLabelStore.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ */
4
+ /**
5
+ * src/sema/engineToolLabelStore.ts — core 工具**展示名**(`tool_start.label` / `tool_end.label`)的
6
+ * 壳侧侧信道(sema-owned 新文件,零 CC 基座改动)。
7
+ *
8
+ * 为什么是侧信道而不是把 label 留在 tool_use block 上(P0-2 的关键判断):
9
+ * tool_use content block 会**进壳 transcript**(jsonl 持久 + /compact 时原样直发 provider)。
10
+ * Anthropic/OpenAI 两侧对 tool_use block 都是严格形 `{type,id,name,input}`,多一个 `label` 键
11
+ * = 400 invalid_request —— 这正是「件1 中断事故批 G」踩过的坑(裸对象 content 落 transcript,
12
+ * /compact 直发 ark/volces 400)。所以 bridge 在渲染前把 label 从 block 上摘掉(同
13
+ * eventId/parentToolCallId 待遇),存到这里,渲染面按 toolCallId 查。
14
+ *
15
+ * 消费面:components/messages/AssistantToolUseMessage.tsx 的 **unknown-tool fallback** ——
16
+ * 也只在那里消费。已注册工具(壳本地 registry 命中)的卡标题恒走 CC 的 `tool.userFacingName()`,
17
+ * 绝不被引擎 label 覆盖(CC 逐字对齐优先;core label 与 CC 的 MCP 卡形 `server - tool (MCP)` 不同形,
18
+ * 覆盖 = 制造分叉)。
19
+ *
20
+ * 今天的真实命中面(2026-07-26 直连钉版 core 1.416 实测):core 只在 `label !== name` 时才上 wire
21
+ * (runtask.js:1406),而唯一这样的一族是 **MCP 远端工具** —— `name = mcp__<server>__<tool>`、
22
+ * `label = <server>:<tool>`(core/mcp.js:902)。即:引擎侧连着、壳侧没连的那台 MCP server 的工具,
23
+ * 修前卡头是裸 `mcp__foo__bar`,修后是 core 的展示名 `foo:bar`。
24
+ *
25
+ * 生命周期:按 toolCallId 键的有界 Map(FIFO 淘汰),纯 chrome 数据,不持久、不回喂模型。
26
+ */
27
+ /** 登记一个 tool call 的 core 展示名(空/非串忽略)。 */
28
+ export declare function recordEngineToolLabel(toolCallId: unknown, label: unknown): void;
29
+ /** 读一个 tool call 的 core 展示名(未登记 ⇒ undefined,调用方回落原有形状)。 */
30
+ export declare function engineToolLabel(toolCallId: unknown): string | undefined;
31
+ /** 测试用:清空台账。 */
32
+ export declare function resetEngineToolLabels(): void;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/engineToolLabelStore.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ */
4
+ /**
5
+ * src/sema/engineToolLabelStore.ts — core 工具**展示名**(`tool_start.label` / `tool_end.label`)的
6
+ * 壳侧侧信道(sema-owned 新文件,零 CC 基座改动)。
7
+ *
8
+ * 为什么是侧信道而不是把 label 留在 tool_use block 上(P0-2 的关键判断):
9
+ * tool_use content block 会**进壳 transcript**(jsonl 持久 + /compact 时原样直发 provider)。
10
+ * Anthropic/OpenAI 两侧对 tool_use block 都是严格形 `{type,id,name,input}`,多一个 `label` 键
11
+ * = 400 invalid_request —— 这正是「件1 中断事故批 G」踩过的坑(裸对象 content 落 transcript,
12
+ * /compact 直发 ark/volces 400)。所以 bridge 在渲染前把 label 从 block 上摘掉(同
13
+ * eventId/parentToolCallId 待遇),存到这里,渲染面按 toolCallId 查。
14
+ *
15
+ * 消费面:components/messages/AssistantToolUseMessage.tsx 的 **unknown-tool fallback** ——
16
+ * 也只在那里消费。已注册工具(壳本地 registry 命中)的卡标题恒走 CC 的 `tool.userFacingName()`,
17
+ * 绝不被引擎 label 覆盖(CC 逐字对齐优先;core label 与 CC 的 MCP 卡形 `server - tool (MCP)` 不同形,
18
+ * 覆盖 = 制造分叉)。
19
+ *
20
+ * 今天的真实命中面(2026-07-26 直连钉版 core 1.416 实测):core 只在 `label !== name` 时才上 wire
21
+ * (runtask.js:1406),而唯一这样的一族是 **MCP 远端工具** —— `name = mcp__<server>__<tool>`、
22
+ * `label = <server>:<tool>`(core/mcp.js:902)。即:引擎侧连着、壳侧没连的那台 MCP server 的工具,
23
+ * 修前卡头是裸 `mcp__foo__bar`,修后是 core 的展示名 `foo:bar`。
24
+ *
25
+ * 生命周期:按 toolCallId 键的有界 Map(FIFO 淘汰),纯 chrome 数据,不持久、不回喂模型。
26
+ */
27
+ /** 上界:一个长会话里 tool call 无限增长,标签只是渲染辅助,超界按 FIFO 淘汰最旧的。 */
28
+ const MAX_ENTRIES = 500;
29
+ const labels = new Map();
30
+ /** 登记一个 tool call 的 core 展示名(空/非串忽略)。 */
31
+ export function recordEngineToolLabel(toolCallId, label) {
32
+ if (typeof toolCallId !== 'string' || toolCallId.length === 0)
33
+ return;
34
+ if (typeof label !== 'string' || label.trim().length === 0)
35
+ return;
36
+ // 已有值不覆盖:tool_start 先到,tool_end 只是补位(两者同源同值)。
37
+ if (labels.has(toolCallId))
38
+ return;
39
+ if (labels.size >= MAX_ENTRIES) {
40
+ const oldest = labels.keys().next();
41
+ if (!oldest.done)
42
+ labels.delete(oldest.value);
43
+ }
44
+ labels.set(toolCallId, label.trim());
45
+ }
46
+ /** 读一个 tool call 的 core 展示名(未登记 ⇒ undefined,调用方回落原有形状)。 */
47
+ export function engineToolLabel(toolCallId) {
48
+ if (typeof toolCallId !== 'string' || toolCallId.length === 0)
49
+ return undefined;
50
+ return labels.get(toolCallId);
51
+ }
52
+ /** 测试用:清空台账。 */
53
+ export function resetEngineToolLabels() {
54
+ labels.clear();
55
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ⇄ B2 批搬迁(2026-07-27,多端改造设计稿 §3 B2):cli src/sema/engineWireSdk.ts 逐字搬入(纯半场 client 工厂;agentsWireCaps 的静态依赖,故同批过来)。
3
+ */
4
+ /**
5
+ * engineWireSdk — SDK 宪法迁移批(0.0.52)的纯半场 client 工厂:壳内各 wire 子模块(compact/steer/
6
+ * plan-review/auto-title/recap/caps 探测)从手抄 raw fetch 改接 @sema-agent/sdk AgentClient 的共用
7
+ * 构造点。ZERO 产品依赖(只 import 外部零依赖 SDK)——agentsWireCaps/liveInitToolFace 等「纯半场/
8
+ * 零产品图」模块可安全 import,不拖 engineTarget 的 fs 图,亦无循环依赖。
9
+ *
10
+ * 凭证解析 = engineTarget.resolveLiveAuthToken 的逐字镜像(#118 三态;那边是产品半场真源,这里为
11
+ * 纯半场复刻,两处语义必须一致):
12
+ * · token 有 → 原样 Bearer;
13
+ * · 无 token ∧ loopback → { mode: 'loopback-unauthed' }(出站零 Authorization 头,绝不伪造);
14
+ * · 无 token ∧ 非 loopback → 'anon'(fail-closed worker 换来诚实 401;与主车道 replEntry 同姿势)。
15
+ * ⚠️ 迁移差分记账:旧 raw fetch 在「无 token ∧ 非 loopback」时不带任何 Authorization 头;SDK 恒需
16
+ * 合法 authToken 形,这里按主车道姿势出 'anon'——该态本就是残破配置(connect 未登录),两种姿势对
17
+ * fail-closed worker 都是 401,对 owner-gated 路由都是 owner 不匹配,行为面等价。
18
+ *
19
+ * 缺省 maxRetries=0:raw fetch 原语义是单发不重试(探针有自己的预算窗;fire-and-forget 不该静默
20
+ * 翻倍打点)。要 SDK 缺省重试的调用方显式传。principal 缺省 'anon:shell-live'(replEntry live 车道
21
+ * 同款)——SDK Transport 恒 stamp x-agent-principal(principal-first 宪法);旧 raw fetch 在缺
22
+ * principal 时省略该头,迁移后恒出示,对 requirePrincipal 部署是 fail-open 改善,已记账。
23
+ */
24
+ import { AgentClient } from '@sema-agent/sdk';
25
+ export type WireAuthToken = string | {
26
+ mode: 'loopback-unauthed';
27
+ };
28
+ /** loopback 判定(engineTarget.isLoopbackEngineUrl 逐字镜像;解析失败=非 loopback)。 */
29
+ export declare function isLoopbackWireUrl(url: string): boolean;
30
+ /** #118 三态凭证解析(resolveLiveAuthToken 纯半场镜像)。 */
31
+ export declare function wireAuthTokenFor(baseUrl: string, token?: string): WireAuthToken;
32
+ export type EngineWireClientConfig = {
33
+ baseUrl: string;
34
+ /** 真 token 串;缺省经 wireAuthTokenFor 三态解析(loopback-unauthed / 'anon')。 */
35
+ token?: string;
36
+ /** 缺省 'anon:shell-live'(replEntry live 车道同款)。 */
37
+ principal?: string;
38
+ /** 每次请求的超时预算(SDK 缺省 60s)。 */
39
+ timeoutMs?: number;
40
+ /** 缺省 0(raw fetch 单发语义);要 SDK 重试的调用方显式传。 */
41
+ maxRetries?: number;
42
+ /** 测试注入面(探针单测的 fetch stub)。 */
43
+ fetchImpl?: typeof fetch;
44
+ };
45
+ /**
46
+ * 构造一个引擎 wire 用 AgentClient。null = 构造失败(SDK fail-closed 拒绝——不该发生:
47
+ * wireAuthTokenFor 恒给合法形;防御性 fail-soft,调用方按各自离线/探测失败路径处理)。
48
+ */
49
+ export declare function makeEngineWireClient(cfg: EngineWireClientConfig): AgentClient | null;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * ⇄ B2 批搬迁(2026-07-27,多端改造设计稿 §3 B2):cli src/sema/engineWireSdk.ts 逐字搬入(纯半场 client 工厂;agentsWireCaps 的静态依赖,故同批过来)。
3
+ */
4
+ /**
5
+ * engineWireSdk — SDK 宪法迁移批(0.0.52)的纯半场 client 工厂:壳内各 wire 子模块(compact/steer/
6
+ * plan-review/auto-title/recap/caps 探测)从手抄 raw fetch 改接 @sema-agent/sdk AgentClient 的共用
7
+ * 构造点。ZERO 产品依赖(只 import 外部零依赖 SDK)——agentsWireCaps/liveInitToolFace 等「纯半场/
8
+ * 零产品图」模块可安全 import,不拖 engineTarget 的 fs 图,亦无循环依赖。
9
+ *
10
+ * 凭证解析 = engineTarget.resolveLiveAuthToken 的逐字镜像(#118 三态;那边是产品半场真源,这里为
11
+ * 纯半场复刻,两处语义必须一致):
12
+ * · token 有 → 原样 Bearer;
13
+ * · 无 token ∧ loopback → { mode: 'loopback-unauthed' }(出站零 Authorization 头,绝不伪造);
14
+ * · 无 token ∧ 非 loopback → 'anon'(fail-closed worker 换来诚实 401;与主车道 replEntry 同姿势)。
15
+ * ⚠️ 迁移差分记账:旧 raw fetch 在「无 token ∧ 非 loopback」时不带任何 Authorization 头;SDK 恒需
16
+ * 合法 authToken 形,这里按主车道姿势出 'anon'——该态本就是残破配置(connect 未登录),两种姿势对
17
+ * fail-closed worker 都是 401,对 owner-gated 路由都是 owner 不匹配,行为面等价。
18
+ *
19
+ * 缺省 maxRetries=0:raw fetch 原语义是单发不重试(探针有自己的预算窗;fire-and-forget 不该静默
20
+ * 翻倍打点)。要 SDK 缺省重试的调用方显式传。principal 缺省 'anon:shell-live'(replEntry live 车道
21
+ * 同款)——SDK Transport 恒 stamp x-agent-principal(principal-first 宪法);旧 raw fetch 在缺
22
+ * principal 时省略该头,迁移后恒出示,对 requirePrincipal 部署是 fail-open 改善,已记账。
23
+ */
24
+ import { AgentClient } from '@sema-agent/sdk';
25
+ /** loopback 判定(engineTarget.isLoopbackEngineUrl 逐字镜像;解析失败=非 loopback)。 */
26
+ export function isLoopbackWireUrl(url) {
27
+ try {
28
+ const h = new URL(url).hostname;
29
+ return h === '127.0.0.1' || h === 'localhost' || h === '::1' || h === '[::1]';
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /** #118 三态凭证解析(resolveLiveAuthToken 纯半场镜像)。 */
36
+ export function wireAuthTokenFor(baseUrl, token) {
37
+ if (token)
38
+ return token;
39
+ if (isLoopbackWireUrl(baseUrl))
40
+ return { mode: 'loopback-unauthed' };
41
+ return 'anon';
42
+ }
43
+ /**
44
+ * 构造一个引擎 wire 用 AgentClient。null = 构造失败(SDK fail-closed 拒绝——不该发生:
45
+ * wireAuthTokenFor 恒给合法形;防御性 fail-soft,调用方按各自离线/探测失败路径处理)。
46
+ */
47
+ export function makeEngineWireClient(cfg) {
48
+ try {
49
+ return new AgentClient({
50
+ baseUrl: cfg.baseUrl,
51
+ authToken: wireAuthTokenFor(cfg.baseUrl, cfg.token),
52
+ principal: cfg.principal ?? 'anon:shell-live',
53
+ ...(cfg.timeoutMs !== undefined ? { timeoutMs: cfg.timeoutMs } : {}),
54
+ maxRetries: cfg.maxRetries ?? 0,
55
+ ...(cfg.fetchImpl ? { fetch: cfg.fetchImpl } : {}),
56
+ });
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
@@ -0,0 +1,31 @@
1
+ /** 行帧投影后的最小 agent 行形(fleet-tree 的 FleetTask 是它的超集)。 */
2
+ export interface FleetAgentRowLike {
3
+ id: string;
4
+ /** fleet 行 label 列 = agent identity(agentName ?? agentType),如 'fork'。 */
5
+ name?: string;
6
+ /** fleet 行描述列 = 任务描述,如 'alpha-probe'(面板行 label 用它)。 */
7
+ description?: string;
8
+ status?: string;
9
+ tokens?: number;
10
+ /** server 1.278.0 `toolUses` —— 子代**累计**工具调用数。🔴 三态:有键真值(含真 0)/无键
11
+ * `undefined`=老引擎不知道。绝不在本层 `?? 0`(那等于把「不知道」冒充成「跑了 0 个」)。 */
12
+ toolUses?: number;
13
+ /** server 1.278.0 `transcriptId`(子代 childSessionId)—— 委派 prompt 的取件锚,不是 prompt 本身。 */
14
+ transcriptId?: string;
15
+ /** server 1.288.0 / core 1.429.0([1831]②)—— 子代当前工具的结构化形。缺席=旧端或从未跑过工具。 */
16
+ currentTool?: {
17
+ toolName: string;
18
+ target?: string;
19
+ };
20
+ }
21
+ /** 行从投影集消失多久之后按 completed 兜底 settle(重连空窗 / snapshot 清表远小于此)。 */
22
+ export declare const ABSENT_SETTLE_MS = 30000;
23
+ /** 测试钩:清投影台账。 */
24
+ export declare function resetFleetAgentPanelProjection(): void;
25
+ /** 台账读面(测试/诊断):当前记着的行数。 */
26
+ export declare function fleetAgentProjectionSize(): number;
27
+ /**
28
+ * 把一次 fleet 投影(已经过 own 判别 + workflow belt 的 FleetTask[])喂给面板事件流。
29
+ * 幂等、无副作用地重复调用安全 —— 每秒被 footer 调用两次就是常态。
30
+ */
31
+ export declare function projectFleetAgentRows(rows: readonly FleetAgentRowLike[], nowMs?: number): void;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/fleetTaskDesc.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ */
4
+ /**
5
+ * src/sema/fleetTaskDesc.ts — footer fleet 行「label/描述列」纯投影助手(零依赖,从 fleetClient.ts 提出
6
+ * 供单测直取——fleetClient 拖 SDK/appState/notification 重图,无法轻量 bundle;行为与提出前逐字同源)。
7
+ */
8
+ /** collapse a wire label to a single trimmed line (drops embedded newlines/runs). 187 row labels are always
9
+ * one short line; the <FleetTree/> column (Math.min(28,…) + wrap="truncate") does the visible ellipsis, so we
10
+ * only normalize here — no double-ellipsis, faithful to the 187 render. */
11
+ export declare function collapseLabel(s: string): string;
12
+ /** CC row-body fallback(CC 真身锚 2026-07-20 人类剧本帧:`◯ general-purpose t1calc`——row body 从第一帧
13
+ * 起就是 Agent 工具的 description)。引擎子行帧不带 `description` 字段,任务描述只在 `name` 里,且有两形
14
+ * (07-24 D2 wire 实证,engine 1.271.0 帧 dump):
15
+ * ① 出生帧:name = `${agentType}: ${description}` 合成形(如 "fork: t1calc")→ 剥前缀复原;
16
+ * ② 后续帧:引擎把 name 重写为【裸任务描述】(如 "t1calc",无前缀)→ 直接按描述投影——
17
+ * 修前只认①,②一到描述列即塌空(footer 只剩 `◯ fork`,D2 症状)。
18
+ * 防重复列 belt:裸 name 恰为 identity 回声(=agentType 或 =agentName)时不当描述(label 列已渲它)。
19
+ * 纯投影非双源;真进度(last tool name)到帧后仍然优先(调用侧 projectDescription 先行)。 */
20
+ export declare function taskDescFromName(name: string | undefined, agentType: string | undefined, agentName?: string): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/fleetTaskDesc.ts 逐字搬入(零依赖;壳侧改 re-export)。
3
+ */
4
+ /**
5
+ * src/sema/fleetTaskDesc.ts — footer fleet 行「label/描述列」纯投影助手(零依赖,从 fleetClient.ts 提出
6
+ * 供单测直取——fleetClient 拖 SDK/appState/notification 重图,无法轻量 bundle;行为与提出前逐字同源)。
7
+ */
8
+ /** collapse a wire label to a single trimmed line (drops embedded newlines/runs). 187 row labels are always
9
+ * one short line; the <FleetTree/> column (Math.min(28,…) + wrap="truncate") does the visible ellipsis, so we
10
+ * only normalize here — no double-ellipsis, faithful to the 187 render. */
11
+ export function collapseLabel(s) {
12
+ return s.replace(/\s+/g, ' ').trim();
13
+ }
14
+ /** CC row-body fallback(CC 真身锚 2026-07-20 人类剧本帧:`◯ general-purpose t1calc`——row body 从第一帧
15
+ * 起就是 Agent 工具的 description)。引擎子行帧不带 `description` 字段,任务描述只在 `name` 里,且有两形
16
+ * (07-24 D2 wire 实证,engine 1.271.0 帧 dump):
17
+ * ① 出生帧:name = `${agentType}: ${description}` 合成形(如 "fork: t1calc")→ 剥前缀复原;
18
+ * ② 后续帧:引擎把 name 重写为【裸任务描述】(如 "t1calc",无前缀)→ 直接按描述投影——
19
+ * 修前只认①,②一到描述列即塌空(footer 只剩 `◯ fork`,D2 症状)。
20
+ * 防重复列 belt:裸 name 恰为 identity 回声(=agentType 或 =agentName)时不当描述(label 列已渲它)。
21
+ * 纯投影非双源;真进度(last tool name)到帧后仍然优先(调用侧 projectDescription 先行)。 */
22
+ export function taskDescFromName(name, agentType, agentName) {
23
+ if (!name || !agentType)
24
+ return '';
25
+ const prefix = `${agentType}: `;
26
+ if (name.startsWith(prefix))
27
+ return collapseLabel(name.slice(prefix.length));
28
+ const collapsed = collapseLabel(name);
29
+ if (collapsed &&
30
+ collapsed !== collapseLabel(agentType) &&
31
+ collapsed !== collapseLabel(agentName ?? '')) {
32
+ return collapsed;
33
+ }
34
+ return '';
35
+ }