@bolloon/bolloon-agent 0.5.5 → 0.6.1

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 (158) hide show
  1. package/dist/agents/execution-entry.js +10 -0
  2. package/dist/agents/goal-flywheel/wiring/seams.js +5 -1
  3. package/dist/agents/parse-tool-call.js +6 -1
  4. package/dist/agents/pi-sdk-session-factory.js +36 -3
  5. package/dist/agents/pi-sdk-tools.js +18 -8
  6. package/dist/agents/pi-sdk.js +749 -1390
  7. package/dist/agents/run-context.js +20 -0
  8. package/dist/agents/runner-resolver.js +3 -1
  9. package/dist/agents/tool-capability/index.js +51 -0
  10. package/dist/agents/workflow-pivot-loop.js +31 -10
  11. package/dist/agents/write-verify.js +60 -0
  12. package/dist/bollharness-integration/integration.js +8 -0
  13. package/dist/bollharness-integration/skill-adapter.js +32 -3
  14. package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +5 -1
  15. package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -1
  16. package/dist/electron-build/agents/parse-tool-call.js +6 -1
  17. package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
  18. package/dist/electron-build/agents/pi-sdk-session-factory.js +36 -3
  19. package/dist/electron-build/agents/pi-sdk-session-factory.js.map +1 -1
  20. package/dist/electron-build/agents/pi-sdk-tools.js +21 -11
  21. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  22. package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
  23. package/dist/electron-build/agents/pi-sdk.js +750 -1389
  24. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  25. package/dist/electron-build/agents/run-context.js +24 -0
  26. package/dist/electron-build/agents/run-context.js.map +1 -0
  27. package/dist/electron-build/agents/runner-resolver.js +3 -1
  28. package/dist/electron-build/agents/runner-resolver.js.map +1 -1
  29. package/dist/electron-build/agents/tool-capability/index.js +90 -0
  30. package/dist/electron-build/agents/tool-capability/index.js.map +1 -0
  31. package/dist/electron-build/agents/workflow-pivot-loop.js +31 -10
  32. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  33. package/dist/electron-build/agents/write-verify.js +99 -0
  34. package/dist/electron-build/agents/write-verify.js.map +1 -0
  35. package/dist/electron-build/bollharness-integration/integration.js +8 -0
  36. package/dist/electron-build/bollharness-integration/integration.js.map +1 -1
  37. package/dist/electron-build/bollharness-integration/skill-adapter.js +32 -3
  38. package/dist/electron-build/bollharness-integration/skill-adapter.js.map +1 -1
  39. package/dist/electron-build/kernel/channel-actor.js +339 -0
  40. package/dist/electron-build/kernel/channel-actor.js.map +1 -0
  41. package/dist/electron-build/kernel/control.js +102 -0
  42. package/dist/electron-build/kernel/control.js.map +1 -0
  43. package/dist/electron-build/kernel/model-runtime.js +508 -0
  44. package/dist/electron-build/kernel/model-runtime.js.map +1 -0
  45. package/dist/electron-build/kernel/run-lifecycle.js +146 -0
  46. package/dist/electron-build/kernel/run-lifecycle.js.map +1 -0
  47. package/dist/electron-build/kernel/transport.js +119 -0
  48. package/dist/electron-build/kernel/transport.js.map +1 -0
  49. package/dist/electron-build/llm/native-adapter.js +122 -0
  50. package/dist/electron-build/llm/native-adapter.js.map +1 -0
  51. package/dist/index.js +43 -19
  52. package/dist/ios/bolloon-web.json +3 -3
  53. package/dist/ios/client.js +13 -7
  54. package/dist/ios/mobile-core.js +7762 -6289
  55. package/dist/ios/mobile.js +64 -0
  56. package/dist/ios/routes-mobile-tasks.js +2 -1
  57. package/dist/ios/routes-tasks.js +16 -1
  58. package/dist/ios/routes-x402-info.js +28 -1
  59. package/dist/ios/server-storage.js +26 -5
  60. package/dist/ios/server.js +840 -862
  61. package/dist/ios/sw.js +1 -1
  62. package/dist/ios/ui/message-renderer.js +473 -29
  63. package/dist/ios/ui/step-timeline.js +13 -12
  64. package/dist/kernel/channel-actor.js +321 -0
  65. package/dist/kernel/control.js +95 -0
  66. package/dist/kernel/gate-scan.js +1350 -0
  67. package/dist/kernel/model-runtime.js +494 -0
  68. package/dist/kernel/plan-channel-actor.js +305 -0
  69. package/dist/kernel/plan-communication.js +163 -0
  70. package/dist/kernel/plan-constraint.js +152 -0
  71. package/dist/kernel/plan-deletion.js +32 -0
  72. package/dist/kernel/plan-harness.js +78 -0
  73. package/dist/kernel/plan-modelruntime.js +60 -0
  74. package/dist/kernel/plan-runcontext.js +163 -0
  75. package/dist/kernel/plan.js +149 -0
  76. package/dist/kernel/roster.js +244 -0
  77. package/dist/kernel/run-lifecycle.js +138 -0
  78. package/dist/kernel/transport.js +111 -0
  79. package/dist/llm/native-adapter.js +117 -0
  80. package/dist/web/bolloon-web.json +3 -3
  81. package/dist/web/client.js +12 -7
  82. package/dist/web/routes-tasks.js +16 -1
  83. package/dist/web/server.js +838 -862
  84. package/dist/web/sw.js +1 -1
  85. package/dist/web/ui/message-renderer.js +473 -29
  86. package/dist/web/ui/step-timeline.js +13 -12
  87. package/package.json +1 -1
  88. package/scripts/build-web.ts +74 -1
  89. package/dist/constraint-runtime/_archive_helper.d.ts +0 -1
  90. package/dist/constraint-runtime/_archive_helper.js +0 -9
  91. package/dist/constraint-runtime/assistant/index.d.ts +0 -11
  92. package/dist/constraint-runtime/assistant/index.js +0 -12
  93. package/dist/constraint-runtime/bootstrap/index.d.ts +0 -11
  94. package/dist/constraint-runtime/bootstrap/index.js +0 -12
  95. package/dist/constraint-runtime/bridge/index.d.ts +0 -11
  96. package/dist/constraint-runtime/bridge/index.js +0 -12
  97. package/dist/constraint-runtime/buddy/index.d.ts +0 -11
  98. package/dist/constraint-runtime/buddy/index.js +0 -12
  99. package/dist/constraint-runtime/cli/index.d.ts +0 -11
  100. package/dist/constraint-runtime/cli/index.js +0 -12
  101. package/dist/constraint-runtime/components/index.d.ts +0 -11
  102. package/dist/constraint-runtime/components/index.js +0 -12
  103. package/dist/constraint-runtime/constants/index.d.ts +0 -11
  104. package/dist/constraint-runtime/constants/index.js +0 -12
  105. package/dist/constraint-runtime/coordinator/index.d.ts +0 -11
  106. package/dist/constraint-runtime/coordinator/index.js +0 -12
  107. package/dist/constraint-runtime/cost_hook.d.ts +0 -2
  108. package/dist/constraint-runtime/cost_hook.js +0 -4
  109. package/dist/constraint-runtime/entrypoints/index.d.ts +0 -11
  110. package/dist/constraint-runtime/entrypoints/index.js +0 -12
  111. package/dist/constraint-runtime/execution_registry.d.ts +0 -20
  112. package/dist/constraint-runtime/execution_registry.js +0 -44
  113. package/dist/constraint-runtime/hooks/index.d.ts +0 -11
  114. package/dist/constraint-runtime/hooks/index.js +0 -12
  115. package/dist/constraint-runtime/ink.d.ts +0 -1
  116. package/dist/constraint-runtime/ink.js +0 -4
  117. package/dist/constraint-runtime/keybindings/index.d.ts +0 -11
  118. package/dist/constraint-runtime/keybindings/index.js +0 -12
  119. package/dist/constraint-runtime/memdir/index.d.ts +0 -11
  120. package/dist/constraint-runtime/memdir/index.js +0 -12
  121. package/dist/constraint-runtime/migrations/index.d.ts +0 -11
  122. package/dist/constraint-runtime/migrations/index.js +0 -12
  123. package/dist/constraint-runtime/moreright/index.d.ts +0 -11
  124. package/dist/constraint-runtime/moreright/index.js +0 -12
  125. package/dist/constraint-runtime/native_ts/index.d.ts +0 -11
  126. package/dist/constraint-runtime/native_ts/index.js +0 -12
  127. package/dist/constraint-runtime/output_styles/index.d.ts +0 -11
  128. package/dist/constraint-runtime/output_styles/index.js +0 -12
  129. package/dist/constraint-runtime/plugins/index.d.ts +0 -11
  130. package/dist/constraint-runtime/plugins/index.js +0 -12
  131. package/dist/constraint-runtime/port_manifest.d.ts +0 -7
  132. package/dist/constraint-runtime/port_manifest.js +0 -11
  133. package/dist/constraint-runtime/query.d.ts +0 -6
  134. package/dist/constraint-runtime/query.js +0 -1
  135. package/dist/constraint-runtime/remote/index.d.ts +0 -11
  136. package/dist/constraint-runtime/remote/index.js +0 -12
  137. package/dist/constraint-runtime/schemas/index.d.ts +0 -11
  138. package/dist/constraint-runtime/schemas/index.js +0 -12
  139. package/dist/constraint-runtime/screens/index.d.ts +0 -11
  140. package/dist/constraint-runtime/screens/index.js +0 -12
  141. package/dist/constraint-runtime/server/index.d.ts +0 -11
  142. package/dist/constraint-runtime/server/index.js +0 -12
  143. package/dist/constraint-runtime/services/index.d.ts +0 -11
  144. package/dist/constraint-runtime/services/index.js +0 -12
  145. package/dist/constraint-runtime/state/index.d.ts +0 -11
  146. package/dist/constraint-runtime/state/index.js +0 -12
  147. package/dist/constraint-runtime/system_init.d.ts +0 -1
  148. package/dist/constraint-runtime/system_init.js +0 -20
  149. package/dist/constraint-runtime/types/index.d.ts +0 -11
  150. package/dist/constraint-runtime/types/index.js +0 -12
  151. package/dist/constraint-runtime/upstream_proxy/index.d.ts +0 -11
  152. package/dist/constraint-runtime/upstream_proxy/index.js +0 -12
  153. package/dist/constraint-runtime/utils/index.d.ts +0 -11
  154. package/dist/constraint-runtime/utils/index.js +0 -12
  155. package/dist/constraint-runtime/vim/index.d.ts +0 -11
  156. package/dist/constraint-runtime/vim/index.js +0 -12
  157. package/dist/constraint-runtime/voice/index.d.ts +0 -11
  158. package/dist/constraint-runtime/voice/index.js +0 -12
@@ -0,0 +1,111 @@
1
+ /**
2
+ * transport.ts — K10 ④: **通信入口端口** (与 `control.ts` / `run-lifecycle.ts` 同一套端口写法)
3
+ *
4
+ * 目标形态 (设计页): `transport → router → channel mailbox` —— 也就是说:
5
+ * **上层只对"通信端口"说话, 不直接对某条传输 (hyperswarm / iroh / p2pNetwork) 说话**。
6
+ * Pi 现在自己持有 3 个薄包装 (`getPeers` / `sendMessage` / `broadcast` → `p2pNetwork`),
7
+ * 那正是"Pi 内部通信发送逻辑"还没迁完的形状 (K10 ④)。
8
+ *
9
+ * 为什么用端口而不是直接调传输:
10
+ * - 传输是可替换的 (hyperswarm / iroh / 以后的东西) ⇒ 上层不该 import 具体实现;
11
+ * - 发送是**有副作用**的动作 ⇒ 要能审计 (谁发的 / 发给谁 / 成没成), 且**未注入即拒** (不静默丢弃消息);
12
+ * - 与 `control.ts` / `run-lifecycle.ts` 保持同一种形状, 免得仓里出现第三种端口写法。
13
+ *
14
+ * 注: 本模块只做"入口收口", **不搬传输实现** (传输仍在 `src/network/**`); 判据见 `src/test/k10-transport-port.test.ts`。
15
+ */
16
+ export const TRANSPORT_OPS = ['send', 'broadcast', 'peers'];
17
+ /** 哪些操作必须有目标 (send 要 peerId; broadcast / peers 不需要) */
18
+ export const TRANSPORT_NEEDS_TARGET = {
19
+ send: true,
20
+ broadcast: false,
21
+ peers: false,
22
+ };
23
+ /**
24
+ * 同步读对端 (K10 ④ 后半) —— 与 `submitTransport` 分开是因为**签名**而不是因为语义:
25
+ * 传输层是异步的, 但上层有同步调用点 (sync 签名不能凭空变 async)。
26
+ * 返回 `null` = 端口未注入 (调用方必须自己决定降级, 不许假装"没有对端" —— 那是两回事)。
27
+ */
28
+ export function transportPeersSync(ports) {
29
+ if (typeof ports?.peersSync !== 'function')
30
+ return null;
31
+ return ports.peersSync();
32
+ }
33
+ /** 端口用 `{ok:false}` 表达拒绝的归一化 (与另外两个端口同口径) */
34
+ function portRefusal(res) {
35
+ if (res && typeof res === 'object' && 'ok' in res) {
36
+ const r = res;
37
+ if (r.ok === false)
38
+ return `端口拒绝: ${String(r.reason ?? '(未给原因)')}`;
39
+ }
40
+ return null;
41
+ }
42
+ const audit = [];
43
+ const AUDIT_MAX = 200;
44
+ /** 审计流水 (最近 N 条; 测试与运维可读) */
45
+ export function transportAudit() {
46
+ return audit;
47
+ }
48
+ export function resetTransportAudit() {
49
+ audit.length = 0;
50
+ }
51
+ /** 提交一次通信动作。**唯一入口** —— 校验 → 派发到 port → 记审计; 一律返回结果对象, 不抛。 */
52
+ export async function submitTransport(req, ports) {
53
+ const op = req?.op;
54
+ const finish = (ok, detail, port, result) => {
55
+ audit.push({
56
+ at: Date.now(),
57
+ op,
58
+ origin: req?.origin ?? '(anonymous)',
59
+ target: String(req?.peerId ?? '(all)'),
60
+ ok,
61
+ detail,
62
+ });
63
+ if (audit.length > AUDIT_MAX)
64
+ audit.splice(0, audit.length - AUDIT_MAX);
65
+ return { ok, op, via: 'kernel-transport', detail, port, result };
66
+ };
67
+ if (!TRANSPORT_OPS.includes(op))
68
+ return finish(false, `未知通信动作: ${String(req?.op)}`);
69
+ if (!req.origin)
70
+ return finish(false, '缺少 origin (审计要求: 不许匿名发送)');
71
+ if (TRANSPORT_NEEDS_TARGET[op] && !req.peerId)
72
+ return finish(false, `${op} 缺少 peerId`);
73
+ const kind = req.kind ?? 'message';
74
+ const payload = req.payload ?? '';
75
+ if (op === 'send') {
76
+ if (typeof ports.send !== 'function')
77
+ return finish(false, 'port 未注入: send');
78
+ try {
79
+ const res = await ports.send(req.peerId, kind, payload);
80
+ const refusal = portRefusal(res);
81
+ return refusal ? finish(false, refusal, 'send', res) : finish(true, undefined, 'send', res);
82
+ }
83
+ catch (err) {
84
+ return finish(false, `send 失败: ${String(err?.message ?? err).slice(0, 160)}`);
85
+ }
86
+ }
87
+ if (op === 'broadcast') {
88
+ if (typeof ports.broadcast !== 'function')
89
+ return finish(false, 'port 未注入: broadcast');
90
+ try {
91
+ const res = await ports.broadcast(kind, payload);
92
+ const refusal = portRefusal(res);
93
+ return refusal ? finish(false, refusal, 'broadcast', res) : finish(true, undefined, 'broadcast', res);
94
+ }
95
+ catch (err) {
96
+ return finish(false, `broadcast 失败: ${String(err?.message ?? err).slice(0, 160)}`);
97
+ }
98
+ }
99
+ if (op === 'peers') {
100
+ if (typeof ports.peers !== 'function')
101
+ return finish(false, 'port 未注入: peers');
102
+ try {
103
+ const res = await ports.peers();
104
+ return finish(true, undefined, 'peers', res);
105
+ }
106
+ catch (err) {
107
+ return finish(false, `peers 失败: ${String(err?.message ?? err).slice(0, 160)}`);
108
+ }
109
+ }
110
+ return finish(false, `未处理的通信动作: ${String(op)}`);
111
+ }
@@ -0,0 +1,117 @@
1
+ import { sanitizeToolsForApi } from './tool-name.js';
2
+ /** 可被 abort 打断的 sleep (退避用) */
3
+ function sleep(ms, signal) {
4
+ return new Promise((resolve, reject) => {
5
+ const t = setTimeout(() => { signal?.removeEventListener?.('abort', onAbort); resolve(); }, ms);
6
+ const onAbort = () => { clearTimeout(t); reject(new Error('aborted during backoff')); };
7
+ if (signal?.aborted)
8
+ onAbort();
9
+ else
10
+ signal?.addEventListener?.('abort', onAbort, { once: true });
11
+ });
12
+ }
13
+ export function createNativeAdapter(cfg) {
14
+ const timeoutMs = cfg.timeoutMs ?? 120_000;
15
+ const maxRetries = cfg.maxRetries ?? 3;
16
+ const url = `${String(cfg.baseUrl || '').replace(/\/+$/, '')}/chat/completions`;
17
+ let calls = 0; // 观测: 真发出去几次 (重试也算)
18
+ let retries = 0;
19
+ const adapter = {
20
+ providerId: cfg.providerId ?? 'native',
21
+ stats: () => ({ calls, retries }),
22
+ async chat(context, systemPrompt, signal, tools, purpose) {
23
+ const messages = [
24
+ { role: 'system', content: String(systemPrompt ?? '') },
25
+ { role: 'user', content: String(context ?? '') },
26
+ ];
27
+ let attempt = 0;
28
+ let lastErr;
29
+ while (attempt <= maxRetries) {
30
+ const ac = new AbortController();
31
+ const onAbort = () => ac.abort();
32
+ if (signal?.aborted)
33
+ ac.abort();
34
+ else
35
+ signal?.addEventListener?.('abort', onAbort, { once: true });
36
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
37
+ try {
38
+ calls += 1;
39
+ // ⚠️ 出程必须**净化工具名** —— 端点只接受 `^[a-zA-Z0-9_-]{1,64}$` (实测: 原样转发 183 个工具
40
+ // 里第 124 个不合规 ⇒ HTTP 400, 整轮空回复)。净化会把映射登记进**全局路由表**,
41
+ // 回程由 pivot 的 `resolveApiToolName` 还原 ⇒ 上下层协议不变 (这正是"适配器边界"的一部分)。
42
+ const wireTools = Array.isArray(tools) && tools.length > 0 ? sanitizeToolsForApi(tools) : undefined;
43
+ const res = await fetch(url, {
44
+ method: 'POST',
45
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${cfg.apiKey}` },
46
+ body: JSON.stringify({
47
+ model: cfg.model,
48
+ messages,
49
+ ...(wireTools ? { tools: wireTools } : {}),
50
+ stream: false,
51
+ }),
52
+ signal: ac.signal,
53
+ });
54
+ if (!res.ok) {
55
+ const text = await res.text().catch(() => '');
56
+ const retryable = res.status === 429 || res.status >= 500;
57
+ if (retryable && attempt < maxRetries) {
58
+ lastErr = new Error(`native-adapter HTTP ${res.status}: ${text.slice(0, 160)}`);
59
+ attempt += 1;
60
+ retries += 1;
61
+ await sleep(1000 * 2 ** (attempt - 1), signal);
62
+ continue;
63
+ }
64
+ // 非 429 的 4xx ⇒ **不重试** (重试无意义)。这里必须打标记: 否则下面的 catch
65
+ // 会把它当"网络错"再退避重试 —— 踩过 (用例⑤: 一个 400 被发了 4 次)。
66
+ const fatal = new Error(`native-adapter HTTP ${res.status}: ${text.slice(0, 200)}`);
67
+ fatal.noRetry = true;
68
+ throw fatal;
69
+ }
70
+ const data = await res.json();
71
+ const msg = data?.choices?.[0]?.message ?? {};
72
+ const toolCalls = Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 ? msg.tool_calls : undefined;
73
+ return {
74
+ reply: String(msg.content ?? ''),
75
+ tokens: data?.usage?.total_tokens,
76
+ toolCalls,
77
+ messages: messages.concat([{ role: 'assistant', content: String(msg.content ?? '') }]),
78
+ };
79
+ }
80
+ catch (e) {
81
+ // 调用方主动 abort ⇒ 原样抛出 (别当网络错去重试)
82
+ if (signal?.aborted)
83
+ throw e;
84
+ if (e?.noRetry)
85
+ throw e; // 非 429 的 4xx 等: 不重试
86
+ lastErr = e;
87
+ if (attempt >= maxRetries)
88
+ throw e;
89
+ attempt += 1;
90
+ retries += 1;
91
+ await sleep(1000 * 2 ** (attempt - 1), signal);
92
+ }
93
+ finally {
94
+ clearTimeout(timer);
95
+ signal?.removeEventListener?.('abort', onAbort);
96
+ }
97
+ }
98
+ throw lastErr ?? new Error('native-adapter: 不可达');
99
+ },
100
+ };
101
+ return adapter;
102
+ }
103
+ /**
104
+ * 从环境装配一个 native 适配器 (供 `BOLLOON_NATIVE_ADAPTER=1` 开关使用)。
105
+ * 缺 key ⇒ 返回 null (调用方回落到原适配器, 不假装可用)。
106
+ */
107
+ export function nativeAdapterFromEnv(env = process.env) {
108
+ const apiKey = env.BOLLOON_NATIVE_API_KEY || env.DEEPSEEK_API_KEY || '';
109
+ if (!apiKey)
110
+ return null;
111
+ return createNativeAdapter({
112
+ baseUrl: env.BOLLOON_NATIVE_BASE_URL || 'https://api.deepseek.com/v1',
113
+ apiKey,
114
+ model: env.BOLLOON_NATIVE_MODEL || 'deepseek-chat',
115
+ providerId: 'native-openai-compatible',
116
+ });
117
+ }
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema": "bolloon-mobile-web/1",
3
- "version": "0.5.5",
3
+ "version": "0.6.1",
4
4
  "channel": "stable",
5
5
  "build": "stable",
6
- "commit": "02aa1a6367f30fb4d85f52c360d8cd457f852ddd",
7
- "builtAt": "2026-10-01T13:11:22.558Z",
6
+ "commit": "18c1afa87b36043e6df5dc9ccef0417cdcf323da",
7
+ "builtAt": "2026-10-04T08:01:30.567Z",
8
8
  "layer": "web",
9
9
  "note": "web 资源层构建戳 (手机端 OTA 的身份第 ③ 级回退; 装机后以 OTA state.json 为准)"
10
10
  }
@@ -571,12 +571,13 @@
571
571
  }
572
572
  return null;
573
573
  }
574
- var SHELL_KEYWORDS, PARSE_DIAG_ON;
574
+ var SHELL_KEYWORDS, _parseEnv, PARSE_DIAG_ON;
575
575
  var init_parse_tool_call = __esm({
576
576
  "src/agents/parse-tool-call.ts"() {
577
577
  "use strict";
578
578
  SHELL_KEYWORDS = ["git", "npx", "npm", "tsx", "tsc", "vitest", "node", "mkdir", "touch", "ls", "echo", "cat", "head", "tail", "wc", "pwd", "date"];
579
- PARSE_DIAG_ON = process.env.BOLLOON_PARSE_DIAG === "1" || process.env.BOLLOON_VERBOSE === "1";
579
+ _parseEnv = typeof process !== "undefined" ? process.env : void 0;
580
+ PARSE_DIAG_ON = !!_parseEnv && (_parseEnv.BOLLOON_PARSE_DIAG === "1" || _parseEnv.BOLLOON_VERBOSE === "1");
580
581
  }
581
582
  });
582
583
 
@@ -2886,6 +2887,11 @@ ${data.error || "channel not found"}`, "error");
2886
2887
  }
2887
2888
  var lastUsedJudgmentIds = [];
2888
2889
  var currentPreviewBubble = null;
2890
+ function retirePreviewBubbles(container) {
2891
+ if (!container) return;
2892
+ container.querySelectorAll(".message-ai.preview").forEach((el) => el.remove());
2893
+ currentPreviewBubble = null;
2894
+ }
2889
2895
  var selfImproveCardSeq = 0;
2890
2896
  function getMessagesContainerForCurrent2() {
2891
2897
  if (currentChannelId && messagesContainers.get(currentChannelId)) {
@@ -3145,9 +3151,7 @@ ${data.error || "channel not found"}`, "error");
3145
3151
  addMessage2(data.content, "user", true, container);
3146
3152
  }
3147
3153
  } else if (data.type === "ai") {
3148
- const allPreviews = container.querySelectorAll(".message-ai.preview");
3149
- allPreviews.forEach((el) => el.remove());
3150
- currentPreviewBubble = null;
3154
+ retirePreviewBubbles(container);
3151
3155
  if (!MR_hasStreamingText()) {
3152
3156
  addMessage2(data.content || "", "ai", true, container, lastUsedJudgmentIds || []);
3153
3157
  } else {
@@ -3157,8 +3161,7 @@ ${data.error || "channel not found"}`, "error");
3157
3161
  sendRokidText(data.content || "", { source: "ai" });
3158
3162
  } else if (data.type === "reply-preview") {
3159
3163
  const previewContent = data.content || "";
3160
- const oldPreviews = container.querySelectorAll(".message-ai.preview");
3161
- oldPreviews.forEach((el) => el.remove());
3164
+ retirePreviewBubbles(container);
3162
3165
  addMessage2(previewContent, "ai", false, container, []);
3163
3166
  const newPreview = container.querySelector(".message-ai:not(.preview):last-of-type") || container.lastElementChild;
3164
3167
  if (newPreview) {
@@ -3180,6 +3183,7 @@ ${data.error || "channel not found"}`, "error");
3180
3183
  handleStepEvent2(data);
3181
3184
  } else if (data.type === "done") {
3182
3185
  finalizeTimelineAsMessage2();
3186
+ retirePreviewBubbles(container);
3183
3187
  hideLoopStatusBar2();
3184
3188
  setSendMode("idle");
3185
3189
  } else if (data.type === "renamed") {
@@ -3193,6 +3197,7 @@ ${data.error || "channel not found"}`, "error");
3193
3197
  }
3194
3198
  } else if (data.type === "error") {
3195
3199
  const errContent = String(data.content || "\u672A\u77E5\u9519\u8BEF");
3200
+ retirePreviewBubbles(container);
3196
3201
  addMessage2(`\u26A0\uFE0F ${errContent}`, "ai", false, container);
3197
3202
  if (typeof showSimpleToast === "function") {
3198
3203
  showSimpleToast("\u26A0\uFE0F " + errContent.slice(0, 200));
@@ -4,6 +4,7 @@
4
4
  * 从 src/web/server.ts 抽出 (~160 行).
5
5
  * 包含 /api/tasks/* (GET, POST, PATCH, DELETE, execute, execute-next)
6
6
  */
7
+ import { deliverThroughActor } from '../kernel/channel-actor.js';
7
8
  import { loadTaskQueue, saveTaskQueue } from './server-storage.js';
8
9
  import { documentReader } from '../documents/reader.js';
9
10
  import { applyCancelRequest, shouldFinalizeAsCancelled } from './task-cancel.js';
@@ -309,7 +310,21 @@ async function executeTask(task, channelId, getAgentForChannel, broadcast) {
309
310
  case 'chat':
310
311
  if (task.description) {
311
312
  broadcast({ type: 'status', content: `执行任务: ${task.title}` }, channelId);
312
- result = await agent.prompt(task.description);
313
+ // 闭包里 TS 不保留收窄 ⇒ 先收成局部 const
314
+ const desc = task.description;
315
+ const agentForRun = agent;
316
+ // 2026-10-02 (K8): 走 Pi 的**唯一执行入口** `runExecution` (K5 步骤⑦ 已建), 不再由通道自己直呼 prompt
317
+ if (typeof agentForRun.runExecution !== 'function') {
318
+ // K8 纪律: 拿不到唯一执行入口 ⇒ **响亮失败**, 不静默回落直呼 prompt (否则收口变成纸面的)
319
+ throw new Error('K8: 该 session 未提供 runExecution (唯一执行入口) ⇒ 拒绝直呼 prompt');
320
+ }
321
+ // 注意: 这里**显式传 channelId** —— ExecutionRequest 要求它, 且语义上本就该绑定 task 的通道。
322
+ // 与旧写法 (prompt(desc), 隐式走 actor 绑定) 相比这是一处**行为差量**: 绑定从隐式变显式。
323
+ // 按纪律记过"待真跑核验", **2026-10-02 已核验通过** (`src/test/k8-routes-tasks-execution.test.ts`,
324
+ // 真 express + 真 HTTP + 真任务队列): ① agent 只给 runExecution (无 prompt) 也能跑通 ⇒ 活路径唯一;
325
+ // ② **两半 id 一致** (解析 agent 用的 id == 绑 run 用的 id) 是自检不变量; ③ 无门时响亮失败且 prompt 调用 0 次。
326
+ // 真变异: 绑成 `channelId + '-mut'` ⇒ 判红; 回落直呼 `prompt` ⇒ 判红; 还原 ⇒ 回绿无残差。
327
+ result = await deliverThroughActor(agentForRun, () => agentForRun.runExecution({ input: desc, channelId }));
313
328
  }
314
329
  break;
315
330
  case 'read':