@bolloon/bolloon-agent 0.5.4 → 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.
Files changed (222) hide show
  1. package/dist/agents/agent-identity-store.js +41 -5
  2. package/dist/agents/background-notices.js +124 -0
  3. package/dist/agents/channel-identity.js +54 -0
  4. package/dist/agents/code-write-gate.js +47 -0
  5. package/dist/agents/deny-pipeline.js +11 -2
  6. package/dist/agents/egress-sanitize.js +69 -0
  7. package/dist/agents/error-classifier.js +67 -5
  8. package/dist/agents/experience-review.js +226 -0
  9. package/dist/agents/gateway-group.js +153 -18
  10. package/dist/agents/iteration-budget.js +64 -0
  11. package/dist/agents/lesson-to-skill.js +211 -0
  12. package/dist/agents/managed-services.js +64 -0
  13. package/dist/agents/parse-tool-call.js +43 -32
  14. package/dist/agents/pi-sdk-session-factory.js +36 -3
  15. package/dist/agents/pi-sdk-session-manager.js +72 -3
  16. package/dist/agents/pi-sdk-tools.js +201 -32
  17. package/dist/agents/pi-sdk.js +798 -206
  18. package/dist/agents/plan-store.js +36 -0
  19. package/dist/agents/process-runner.js +64 -2
  20. package/dist/agents/run-context.js +20 -0
  21. package/dist/agents/runner-resolver.js +2 -1
  22. package/dist/agents/shell-guard.js +109 -13
  23. package/dist/agents/skill-health.js +90 -0
  24. package/dist/agents/skill-ledger.js +129 -0
  25. package/dist/agents/tool-loop-guard.js +303 -0
  26. package/dist/agents/tool-path-args.js +53 -0
  27. package/dist/agents/tool-result-gate.js +50 -0
  28. package/dist/agents/tool-subset.js +109 -0
  29. package/dist/agents/tool-telemetry.js +97 -0
  30. package/dist/bootstrap/context-collector.js +37 -7
  31. package/dist/bootstrap/lifecycle-hooks.js +12 -3
  32. package/dist/bootstrap/persona-init.js +174 -0
  33. package/dist/bootstrap/persona-loader.js +5 -1
  34. package/dist/cli/ink-app.js +153 -6
  35. package/dist/cli/input-paste.js +218 -0
  36. package/dist/cli/loading-tui.js +3 -1
  37. package/dist/cli/log-gate.js +7 -0
  38. package/dist/cli/mention-data.js +30 -0
  39. package/dist/cli/reasoning-view.js +103 -0
  40. package/dist/cli/startup-notice.js +25 -0
  41. package/dist/cli/status-segments.js +87 -2
  42. package/dist/cli/trace-line.js +54 -0
  43. package/dist/context-compaction/auto-compact.js +37 -2
  44. package/dist/electron-build/agents/agent-identity-store.js +43 -5
  45. package/dist/electron-build/agents/agent-identity-store.js.map +1 -1
  46. package/dist/electron-build/agents/background-notices.js +138 -0
  47. package/dist/electron-build/agents/background-notices.js.map +1 -0
  48. package/dist/electron-build/agents/channel-identity.js +64 -0
  49. package/dist/electron-build/agents/channel-identity.js.map +1 -0
  50. package/dist/electron-build/agents/code-write-gate.js +55 -0
  51. package/dist/electron-build/agents/code-write-gate.js.map +1 -0
  52. package/dist/electron-build/agents/deny-pipeline.js +11 -2
  53. package/dist/electron-build/agents/deny-pipeline.js.map +1 -1
  54. package/dist/electron-build/agents/egress-sanitize.js +76 -0
  55. package/dist/electron-build/agents/egress-sanitize.js.map +1 -0
  56. package/dist/electron-build/agents/error-classifier.js +68 -5
  57. package/dist/electron-build/agents/error-classifier.js.map +1 -1
  58. package/dist/electron-build/agents/experience-review.js +243 -0
  59. package/dist/electron-build/agents/experience-review.js.map +1 -0
  60. package/dist/electron-build/agents/iteration-budget.js +73 -0
  61. package/dist/electron-build/agents/iteration-budget.js.map +1 -0
  62. package/dist/electron-build/agents/lesson-to-skill.js +228 -0
  63. package/dist/electron-build/agents/lesson-to-skill.js.map +1 -0
  64. package/dist/electron-build/agents/managed-services.js +69 -0
  65. package/dist/electron-build/agents/managed-services.js.map +1 -0
  66. package/dist/electron-build/agents/parse-tool-call.js +43 -32
  67. package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
  68. package/dist/electron-build/agents/pi-sdk-session-factory.js +36 -3
  69. package/dist/electron-build/agents/pi-sdk-session-factory.js.map +1 -1
  70. package/dist/electron-build/agents/pi-sdk-session-manager.js +75 -3
  71. package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
  72. package/dist/electron-build/agents/pi-sdk-tools.js +200 -31
  73. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  74. package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
  75. package/dist/electron-build/agents/pi-sdk.js +799 -206
  76. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  77. package/dist/electron-build/agents/plan-store.js +37 -0
  78. package/dist/electron-build/agents/plan-store.js.map +1 -1
  79. package/dist/electron-build/agents/process-runner.js +71 -2
  80. package/dist/electron-build/agents/process-runner.js.map +1 -1
  81. package/dist/electron-build/agents/run-context.js +24 -0
  82. package/dist/electron-build/agents/run-context.js.map +1 -0
  83. package/dist/electron-build/agents/runner-resolver.js +2 -1
  84. package/dist/electron-build/agents/runner-resolver.js.map +1 -1
  85. package/dist/electron-build/agents/shell-guard.js +110 -14
  86. package/dist/electron-build/agents/shell-guard.js.map +1 -1
  87. package/dist/electron-build/agents/skill-health.js +103 -0
  88. package/dist/electron-build/agents/skill-health.js.map +1 -0
  89. package/dist/electron-build/agents/skill-ledger.js +146 -0
  90. package/dist/electron-build/agents/skill-ledger.js.map +1 -0
  91. package/dist/electron-build/agents/tool-loop-guard.js +319 -0
  92. package/dist/electron-build/agents/tool-loop-guard.js.map +1 -0
  93. package/dist/electron-build/agents/tool-path-args.js +63 -0
  94. package/dist/electron-build/agents/tool-path-args.js.map +1 -0
  95. package/dist/electron-build/agents/tool-result-gate.js +59 -0
  96. package/dist/electron-build/agents/tool-result-gate.js.map +1 -0
  97. package/dist/electron-build/agents/tool-subset.js +116 -0
  98. package/dist/electron-build/agents/tool-subset.js.map +1 -0
  99. package/dist/electron-build/agents/tool-telemetry.js +108 -0
  100. package/dist/electron-build/agents/tool-telemetry.js.map +1 -0
  101. package/dist/electron-build/bootstrap/context-collector.js +38 -7
  102. package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
  103. package/dist/electron-build/bootstrap/lifecycle-hooks.js +12 -3
  104. package/dist/electron-build/bootstrap/lifecycle-hooks.js.map +1 -1
  105. package/dist/electron-build/bootstrap/persona-init.js +216 -0
  106. package/dist/electron-build/bootstrap/persona-init.js.map +1 -0
  107. package/dist/electron-build/bootstrap/persona-loader.js +5 -1
  108. package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
  109. package/dist/electron-build/context-compaction/auto-compact.js +38 -2
  110. package/dist/electron-build/context-compaction/auto-compact.js.map +1 -1
  111. package/dist/electron-build/external-engines/delegate.js +22 -0
  112. package/dist/electron-build/external-engines/delegate.js.map +1 -1
  113. package/dist/electron-build/kernel/channel-actor.js +314 -0
  114. package/dist/electron-build/kernel/channel-actor.js.map +1 -0
  115. package/dist/electron-build/orbitdb/cid-database.js +55 -0
  116. package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
  117. package/dist/electron-build/orbitdb/ipfs-node.js +36 -11
  118. package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
  119. package/dist/electron-build/web/server-storage.js +26 -5
  120. package/dist/electron-build/web/server-storage.js.map +1 -1
  121. package/dist/external-engines/delegate.js +21 -0
  122. package/dist/index.js +526 -95
  123. package/dist/kernel/channel-actor.js +297 -0
  124. package/dist/kernel/control.js +95 -0
  125. package/dist/kernel/gate-scan.js +1036 -0
  126. package/dist/kernel/model-runtime.js +472 -0
  127. package/dist/kernel/plan-channel-actor.js +298 -0
  128. package/dist/kernel/plan-constraint.js +165 -0
  129. package/dist/kernel/plan-deletion.js +32 -0
  130. package/dist/kernel/plan-modelruntime.js +60 -0
  131. package/dist/kernel/plan-runcontext.js +163 -0
  132. package/dist/kernel/plan.js +153 -0
  133. package/dist/kernel/roster.js +242 -0
  134. package/dist/orbitdb/cid-database.js +55 -0
  135. package/dist/orbitdb/event-index.js +370 -0
  136. package/dist/orbitdb/event-query.js +131 -0
  137. package/dist/orbitdb/event-shell.js +510 -0
  138. package/dist/orbitdb/group-access.js +381 -0
  139. package/dist/orbitdb/group-shards.js +140 -0
  140. package/dist/orbitdb/ipfs-node.js +36 -11
  141. package/dist/web/bolloon-web.json +3 -3
  142. package/dist/web/client.js +4 -2
  143. package/dist/web/mobile-core.js +7762 -6289
  144. package/dist/web/routes-mobile-tasks.js +2 -1
  145. package/dist/web/routes-tasks.js +5 -1
  146. package/dist/web/server-storage.js +26 -5
  147. package/dist/web/server.js +60 -18
  148. package/dist/web/sw.js +1 -1
  149. package/dist/web/ui/message-renderer.js +473 -29
  150. package/dist/web/ui/step-timeline.js +13 -12
  151. package/package.json +2 -1
  152. package/scripts/build-web.ts +74 -1
  153. package/dist/constraint-runtime/_archive_helper.d.ts +0 -1
  154. package/dist/constraint-runtime/_archive_helper.js +0 -9
  155. package/dist/constraint-runtime/assistant/index.d.ts +0 -11
  156. package/dist/constraint-runtime/assistant/index.js +0 -12
  157. package/dist/constraint-runtime/bootstrap/index.d.ts +0 -11
  158. package/dist/constraint-runtime/bootstrap/index.js +0 -12
  159. package/dist/constraint-runtime/bridge/index.d.ts +0 -11
  160. package/dist/constraint-runtime/bridge/index.js +0 -12
  161. package/dist/constraint-runtime/buddy/index.d.ts +0 -11
  162. package/dist/constraint-runtime/buddy/index.js +0 -12
  163. package/dist/constraint-runtime/cli/index.d.ts +0 -11
  164. package/dist/constraint-runtime/cli/index.js +0 -12
  165. package/dist/constraint-runtime/components/index.d.ts +0 -11
  166. package/dist/constraint-runtime/components/index.js +0 -12
  167. package/dist/constraint-runtime/constants/index.d.ts +0 -11
  168. package/dist/constraint-runtime/constants/index.js +0 -12
  169. package/dist/constraint-runtime/coordinator/index.d.ts +0 -11
  170. package/dist/constraint-runtime/coordinator/index.js +0 -12
  171. package/dist/constraint-runtime/cost_hook.d.ts +0 -2
  172. package/dist/constraint-runtime/cost_hook.js +0 -4
  173. package/dist/constraint-runtime/entrypoints/index.d.ts +0 -11
  174. package/dist/constraint-runtime/entrypoints/index.js +0 -12
  175. package/dist/constraint-runtime/execution_registry.d.ts +0 -20
  176. package/dist/constraint-runtime/execution_registry.js +0 -44
  177. package/dist/constraint-runtime/hooks/index.d.ts +0 -11
  178. package/dist/constraint-runtime/hooks/index.js +0 -12
  179. package/dist/constraint-runtime/ink.d.ts +0 -1
  180. package/dist/constraint-runtime/ink.js +0 -4
  181. package/dist/constraint-runtime/keybindings/index.d.ts +0 -11
  182. package/dist/constraint-runtime/keybindings/index.js +0 -12
  183. package/dist/constraint-runtime/memdir/index.d.ts +0 -11
  184. package/dist/constraint-runtime/memdir/index.js +0 -12
  185. package/dist/constraint-runtime/migrations/index.d.ts +0 -11
  186. package/dist/constraint-runtime/migrations/index.js +0 -12
  187. package/dist/constraint-runtime/moreright/index.d.ts +0 -11
  188. package/dist/constraint-runtime/moreright/index.js +0 -12
  189. package/dist/constraint-runtime/native_ts/index.d.ts +0 -11
  190. package/dist/constraint-runtime/native_ts/index.js +0 -12
  191. package/dist/constraint-runtime/output_styles/index.d.ts +0 -11
  192. package/dist/constraint-runtime/output_styles/index.js +0 -12
  193. package/dist/constraint-runtime/plugins/index.d.ts +0 -11
  194. package/dist/constraint-runtime/plugins/index.js +0 -12
  195. package/dist/constraint-runtime/port_manifest.d.ts +0 -7
  196. package/dist/constraint-runtime/port_manifest.js +0 -11
  197. package/dist/constraint-runtime/query.d.ts +0 -6
  198. package/dist/constraint-runtime/query.js +0 -1
  199. package/dist/constraint-runtime/remote/index.d.ts +0 -11
  200. package/dist/constraint-runtime/remote/index.js +0 -12
  201. package/dist/constraint-runtime/schemas/index.d.ts +0 -11
  202. package/dist/constraint-runtime/schemas/index.js +0 -12
  203. package/dist/constraint-runtime/screens/index.d.ts +0 -11
  204. package/dist/constraint-runtime/screens/index.js +0 -12
  205. package/dist/constraint-runtime/server/index.d.ts +0 -11
  206. package/dist/constraint-runtime/server/index.js +0 -12
  207. package/dist/constraint-runtime/services/index.d.ts +0 -11
  208. package/dist/constraint-runtime/services/index.js +0 -12
  209. package/dist/constraint-runtime/state/index.d.ts +0 -11
  210. package/dist/constraint-runtime/state/index.js +0 -12
  211. package/dist/constraint-runtime/system_init.d.ts +0 -1
  212. package/dist/constraint-runtime/system_init.js +0 -20
  213. package/dist/constraint-runtime/types/index.d.ts +0 -11
  214. package/dist/constraint-runtime/types/index.js +0 -12
  215. package/dist/constraint-runtime/upstream_proxy/index.d.ts +0 -11
  216. package/dist/constraint-runtime/upstream_proxy/index.js +0 -12
  217. package/dist/constraint-runtime/utils/index.d.ts +0 -11
  218. package/dist/constraint-runtime/utils/index.js +0 -12
  219. package/dist/constraint-runtime/vim/index.d.ts +0 -11
  220. package/dist/constraint-runtime/vim/index.js +0 -12
  221. package/dist/constraint-runtime/voice/index.d.ts +0 -11
  222. package/dist/constraint-runtime/voice/index.js +0 -12
@@ -167,7 +167,8 @@ export function registerMobileTaskRoutes(app, opts = {}) {
167
167
  if (kind === 'group_create' || kind === 'group_join' || kind === 'group_leave') {
168
168
  const { createGroup, joinGroup, leaveGroup } = await import('../agents/gateway-group.js');
169
169
  if (kind === 'group_create') {
170
- const r = await createGroup(String(request.groupRef || '').trim());
170
+ // 手机端建群与 Web 端同一语义 (微信式群聊) ⇒ 显式声明开放写入
171
+ const r = await createGroup(String(request.groupRef || '').trim(), { acl: 'open' });
171
172
  if (!r.ok || !r.group)
172
173
  return fail(res, 409, 'GROUP_CREATE_FAILED', `建群失败: ${r.error || '未知原因'}`);
173
174
  return res.json({ ok: true, kind, group: { id: r.group.id, name: r.group.name }, text: `已建群「${r.group.name}」` });
@@ -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,10 @@ 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
+ result = await deliverThroughActor(agentForRun, () => agentForRun.prompt(desc));
313
317
  }
314
318
  break;
315
319
  case 'read':
@@ -11,6 +11,8 @@
11
11
  * Channel / Session / Task / SessionMessage / SessionSummary type 来自 ./server-types.ts.
12
12
  */
13
13
  import * as fs from 'fs/promises';
14
+ import * as path from 'path';
15
+ import { channelsPaths, assertTestWriteTarget } from '../agents/agent-identity-store.js';
14
16
  import { CHANNELS_PATH, SESSION_CACHE_PATH, TASK_QUEUE_PATH, THEME_PATH, } from './server-types.js';
15
17
  import { saveWindow as saveSessionWindow, loadWindow as loadSessionWindow } from '../bootstrap/session-window.js';
16
18
  import { parentsSatisfied } from './task-deps.js';
@@ -42,7 +44,7 @@ export async function updateChannels(fn) {
42
44
  }
43
45
  async function rawLoadChannels() {
44
46
  try {
45
- const data = await fs.readFile(CHANNELS_PATH, 'utf-8');
47
+ const data = await fs.readFile(channelsPathNow(), 'utf-8');
46
48
  return JSON.parse(data);
47
49
  }
48
50
  catch (readErr) {
@@ -50,11 +52,11 @@ async function rawLoadChannels() {
50
52
  if (readErr?.code !== 'ENOENT') {
51
53
  console.warn('[loadChannels] channels.json 解析失败, 尝试从 .tmp 恢复:', readErr?.message?.slice(0, 80));
52
54
  try {
53
- const tmpData = await fs.readFile(CHANNELS_PATH + '.tmp', 'utf-8');
55
+ const tmpData = await fs.readFile(channelsPathNow() + '.tmp', 'utf-8');
54
56
  const recovered = JSON.parse(tmpData);
55
57
  console.log(`[loadChannels] 从 .tmp 恢复成功: ${recovered.length} 个 channel`);
56
58
  // 立即把恢复的内容写回主文件
57
- await fs.writeFile(CHANNELS_PATH, tmpData, 'utf-8');
59
+ await fs.writeFile(channelsPathNow(), tmpData, 'utf-8');
58
60
  return recovered;
59
61
  }
60
62
  catch (tmpErr) {
@@ -64,6 +66,22 @@ async function rawLoadChannels() {
64
66
  return [];
65
67
  }
66
68
  }
69
+ /**
70
+ * channels.json 的路径**每次调用时**解析 (2026-10-01, 数据事故修复)。
71
+ *
72
+ * 事故: `server-types.CHANNELS_PATH` 是 import 时定死的常量 (按那一刻的 HOME 解析),
73
+ * 于是测试里后设的 process.env.HOME **完全不起作用** ⇒ 测试夹具把用户的真实
74
+ * ~/.bolloon/sessions/channels.json 整表覆盖 (4 个智能体只剩 1 条)。
75
+ * 现在改成按**当前** HOME 解析 (与 CLI 侧 agent-identity-store.channelsPaths 同一口径)。
76
+ */
77
+ function channelsPathNow() {
78
+ try {
79
+ return channelsPaths()[0];
80
+ }
81
+ catch {
82
+ return CHANNELS_PATH;
83
+ }
84
+ }
67
85
  async function rawSaveChannels(channels) {
68
86
  const sanitized = channels.map(ch => {
69
87
  const { didDocument: _omit, ...rest } = ch;
@@ -75,9 +93,12 @@ async function rawSaveChannels(channels) {
75
93
  lastChannelsJson = jsonStr;
76
94
  console.log('[saveChannels] 保存频道数据, 数量:', sanitized.length);
77
95
  // 2026-07-24: 原子写入 — 先写 .tmp 再 rename, 防止崩溃导致 channels.json 损坏
78
- const tmpPath = CHANNELS_PATH + '.tmp';
96
+ const target = channelsPathNow();
97
+ // 测试进程只允许写临时目录 —— 真实数据目录一律拒绝 (第二道闸)
98
+ assertTestWriteTarget(path.dirname(path.dirname(target)), 'channels.json');
99
+ const tmpPath = target + '.tmp';
79
100
  await fs.writeFile(tmpPath, jsonStr, 'utf-8');
80
- await fs.rename(tmpPath, CHANNELS_PATH);
101
+ await fs.rename(tmpPath, target);
81
102
  lastChannelsWriteAt = Date.now();
82
103
  }
83
104
  export async function loadChannels() {
@@ -87,6 +87,8 @@ console.log = (...args) => {
87
87
  import { createHyperswarmCommunicator, createTopic, KeyManager, AgentAuthManager, } from '@diap/sdk';
88
88
  import { initMinimax } from '../constraints/index.js';
89
89
  import { createAgentSession } from '../agents/pi-sdk.js';
90
+ // **K5 步骤④ (入口投递)**: 把一次入口执行投进会话 Actor 的 mailbox ⇒ 同一会话身份的输入排队执行
91
+ import { deliverThroughActor } from '../kernel/channel-actor.js';
90
92
  import { irohTransport } from '../network/iroh-transport.js';
91
93
  import { createAgentDelegateApp } from './agent-delegate-server.js';
92
94
  import { createIrohDelegateTransport } from './iroh-delegate-transport.js';
@@ -585,7 +587,7 @@ async function triggerRemoteFollowup(channelId, remoteReply, fromPublicKey, roun
585
587
  broadcast({ type: 'followup-step', ...event, channelId }, channelId);
586
588
  }
587
589
  };
588
- const fullResponse = await agent.promptStream(markedPrompt, streamCallback, undefined, channelId);
590
+ const fullResponse = await deliverThroughActor(agent, () => agent.promptStream(markedPrompt, streamCallback, undefined, channelId));
589
591
  if (!fullResponse.trim())
590
592
  return;
591
593
  // 广播续看结果给 UI
@@ -913,7 +915,7 @@ async function handleV3P2PMessage(parsed, conn, comm) {
913
915
  }
914
916
  };
915
917
  const agent = await getAgentForChannel(channelId, ch.did || '', ch.name, ch.didDocRef);
916
- fullResponse = await agent.promptStream(fullPrompt, streamCallback, undefined, channelId);
918
+ fullResponse = await deliverThroughActor(agent, () => agent.promptStream(fullPrompt, streamCallback, undefined, channelId));
917
919
  // 2026-07-06: 防御性兜底
918
920
  if (!fullResponse.trim()) {
919
921
  fullResponse = '⚠️ AI 未返回内容, 请重试';
@@ -1065,7 +1067,7 @@ async function handleV3P2PMessage(parsed, conn, comm) {
1065
1067
  // 临时用 channelId = "__collab__" 拿个一次性 agent
1066
1068
  const collabChannelId = `__collab_${senderKey.substring(0, 8)}__`;
1067
1069
  const agent = await getAgentForChannel(collabChannelId, '', `collab-${senderKey.substring(0, 8)}`, undefined);
1068
- const resultText = await agent.prompt(fullPrompt);
1070
+ const resultText = await deliverThroughActor(agent, () => agent.prompt(fullPrompt));
1069
1071
  const reply = JSON.stringify({
1070
1072
  v: 3,
1071
1073
  op: 'agent.collab.reply',
@@ -1679,7 +1681,7 @@ export async function createWebServer(port = 3000, options = {}) {
1679
1681
  // 跨预算继续: 新 Run 必须挂在同一个 Goal 下 (setGoalId), 并带上上一个 Run 的非幂等守卫
1680
1682
  agent.setGoalId?.(goal.goalId);
1681
1683
  agent.setContinuationGuards?.(r.guards || []);
1682
- const reply = await agent.prompt(r.instruction);
1684
+ const reply = await deliverThroughActor(agent, () => agent.prompt(r.instruction));
1683
1685
  // 注意: prompt 收尾会清空 currentRunId → 必须读 lastRunId (否则会拿上一条 run 做决策)
1684
1686
  const runId = agent.getLastRunId?.() || agent.getRunId?.();
1685
1687
  return { runId, status: 'done', reply: typeof reply === 'string' ? reply.slice(0, 500) : undefined };
@@ -2489,7 +2491,7 @@ export async function createWebServer(port = 3000, options = {}) {
2489
2491
  new Promise((res) => setTimeout(() => res(null), 8000)),
2490
2492
  ]);
2491
2493
  if (agent && typeof agent.promptStream === 'function') {
2492
- llm = (p) => agent.promptStream(p, () => { }, undefined, local.id);
2494
+ llm = (p) => deliverThroughActor(agent, () => agent.promptStream(p, () => { }, undefined, local.id));
2493
2495
  }
2494
2496
  }
2495
2497
  }
@@ -2541,7 +2543,7 @@ export async function createWebServer(port = 3000, options = {}) {
2541
2543
  throw new Error('无可用 agent, 跳过定时任务');
2542
2544
  }
2543
2545
  // 前缀 [cron] 标记后台执行来源 (供审计/日志区分人类消息与定时任务)
2544
- await agent.promptStream(`[cron] ${job.name}: ${job.prompt}`, () => { }, undefined, target.id);
2546
+ await deliverThroughActor(agent, () => agent.promptStream(`[cron] ${job.name}: ${job.prompt}`, () => { }, undefined, target.id));
2545
2547
  },
2546
2548
  });
2547
2549
  global.cronScheduler = cronHandle;
@@ -2593,7 +2595,7 @@ ${goalDesc}
2593
2595
  现在是否要主动联系其中某个智能体? 只输出一个 JSON 对象, 不要任何其他文字:
2594
2596
  {"initiate": true 或 false, "goalAchieved": true 或 false, "targetPeerPublicKey": "对方 pk", "targetChannelId": "对方渠道 id", "message": "你要说的话"}
2595
2597
  若不想发起, 输出 {"initiate": false}。`;
2596
- const raw = await agent.promptStream(prompt, () => { }, undefined, local.id);
2598
+ const raw = await deliverThroughActor(agent, () => agent.promptStream(prompt, () => { }, undefined, local.id));
2597
2599
  const m = raw.match(/\{[\s\S]*\}/);
2598
2600
  if (!m)
2599
2601
  return { initiate: false };
@@ -3406,7 +3408,16 @@ fetchState();
3406
3408
  if (!woke) {
3407
3409
  // 没在等外部事件也要如实回答 (可能它其实在等人/在跑), 但允许显式加急
3408
3410
  if (req.body?.force) {
3409
- await setContinuation(goalId, { wakeReason: 'active', autoContinue: true, wakeAt: undefined, needsExternal: undefined });
3411
+ // **K4**: channel 不直接写 Goal —— 提交控制请求, 由内核控制面执行
3412
+ const { submitRunControl } = await import('../kernel/control.js');
3413
+ const outcome = await submitRunControl({
3414
+ kind: 'wake-goal', origin: 'web', goalId, reason: 'force 加急唤醒',
3415
+ payload: { wakeReason: 'active', autoContinue: true, wakeAt: undefined, needsExternal: undefined },
3416
+ }, { setContinuation });
3417
+ if (!outcome.ok) {
3418
+ res.status(500).json({ error: `控制面拒绝: ${outcome.detail}` });
3419
+ return;
3420
+ }
3410
3421
  res.json({ ok: true, goalId, woke: false, forced: true, note: `原状态 ${g.status} (不在等外部事件), 已按 force 加急` });
3411
3422
  return;
3412
3423
  }
@@ -3467,12 +3478,17 @@ fetchState();
3467
3478
  let boundary = view.runBoundary;
3468
3479
  if (boundary.action === 'stop_running_run' && !boundary.stopped) {
3469
3480
  const { setRunStatus } = await import('../agents/run-store.js');
3481
+ const { submitRunControl } = await import('../kernel/control.js');
3470
3482
  boundary = await seam.applyRunBoundary({
3471
3483
  plan: view.plan,
3472
3484
  now,
3473
3485
  stop: async (i) => {
3474
- const r = await setRunStatus(i.runId, i.runStatus, { error: `变更注入 (${i.goalId}): ${String(i.reason).slice(0, 120)}` });
3475
- return { ok: !!r.ok, reason: r.reason || '' };
3486
+ // **K4**: 提交控制请求 (端口返回值里的"拒绝"会被控制面归一化成 ok=false + detail)
3487
+ const outcome = await submitRunControl({
3488
+ kind: 'set-run-status', origin: 'web', runId: i.runId, reason: `变更注入 (${i.goalId})`,
3489
+ payload: { status: i.runStatus, error: `变更注入 (${i.goalId}): ${String(i.reason).slice(0, 120)}` },
3490
+ }, { setRunStatus });
3491
+ return { ok: outcome.ok, reason: outcome.ok ? '' : String(outcome.detail ?? '') };
3476
3492
  },
3477
3493
  });
3478
3494
  }
@@ -3606,7 +3622,19 @@ fetchState();
3606
3622
  res.status(409).json({ error: `只有 needs_human 的运行需要批准 (当前 ${run.status})` });
3607
3623
  return;
3608
3624
  }
3609
- await recordRecovery(runId, { errorClass: run.errorClass || 'unknown', message: '人工批准后继续', action: 'resume' });
3625
+ // **K4**: channel 不直接写 Run —— 提交控制请求, 由内核控制面执行 (这里只做 ports wiring)
3626
+ const { submitRunControl } = await import('../kernel/control.js');
3627
+ const outcome = await submitRunControl({
3628
+ kind: 'record-recovery',
3629
+ origin: 'web',
3630
+ runId,
3631
+ reason: '人工批准后继续',
3632
+ payload: { errorClass: run.errorClass || 'unknown', message: '人工批准后继续', action: 'resume' },
3633
+ }, { recordRecovery });
3634
+ if (!outcome.ok) {
3635
+ res.status(500).json({ error: `控制面拒绝: ${outcome.detail}` });
3636
+ return;
3637
+ }
3610
3638
  if (!run.channelId) {
3611
3639
  res.status(400).json({ error: '该 run 没有 channelId, 请在 CLI 用 /approve' });
3612
3640
  return;
@@ -3630,9 +3658,11 @@ fetchState();
3630
3658
  const runId = String(req.params.runId);
3631
3659
  try {
3632
3660
  const { setRunStatus } = await import('../agents/run-store.js');
3633
- const r = await setRunStatus(runId, to, { error: `外部请求 (${suffix})` });
3634
- if (!r.ok) {
3635
- res.status(409).json({ error: r.reason || '状态迁移被拒绝' });
3661
+ const { submitRunControl } = await import('../kernel/control.js');
3662
+ const outcome = await submitRunControl({ kind: 'set-run-status', origin: 'web', runId, reason: `外部请求 (${suffix})`, payload: { status: to, error: `外部请求 (${suffix})` } }, { setRunStatus });
3663
+ // 语义不变: 端口/store 拒绝 ⇒ 409 (原因由控制面归一化进 detail)
3664
+ if (!outcome.ok) {
3665
+ res.status(409).json({ error: String(outcome.detail ?? '状态迁移被拒绝').replace(/^端口拒绝: /, '') });
3636
3666
  return;
3637
3667
  }
3638
3668
  res.json({ ok: true, runId, status: to, note: '运行中的 agent 会在下一轮循环检查时停下 (轮内不打断)' });
@@ -3911,7 +3941,7 @@ fetchState();
3911
3941
  req.docContent ? `\n资料内容:\n${req.docContent}` : '',
3912
3942
  `\n(来自 agent ${req.fromAgentId || 'unknown'}),请直接给出可交付结果。`,
3913
3943
  ].join('');
3914
- const out = await agent.prompt(task);
3944
+ const out = await deliverThroughActor(agent, () => agent.prompt(task));
3915
3945
  const text = String(out || '').trim();
3916
3946
  if (!text || text.startsWith('❌') || text.startsWith('[AI 服务调用失败]')) {
3917
3947
  return { ok: false, summary: text.slice(0, 800) || '(空结果)', error: 'execution-failed' };
@@ -4034,6 +4064,8 @@ fetchState();
4034
4064
  const r = await createGroup(String(req.body?.name || '').trim(), {
4035
4065
  from: String(req.body?.from || '').trim() || undefined,
4036
4066
  hello: req.body?.hello ? String(req.body.hello) : undefined,
4067
+ // 微信式群聊: 拿到邀请链接的人都能发言 ⇒ 显式声明 (2026-10-01 起不再是隐式默认)
4068
+ acl: 'open',
4037
4069
  });
4038
4070
  if (!r.ok)
4039
4071
  return res.status(400).json({ error: r.error });
@@ -5359,7 +5391,16 @@ fetchState();
5359
5391
  }
5360
5392
  // 2026-07-15 修 Bug 3: 拖拽附件 — attachmentContext 提到 contextHint 最前, LLM 第一眼看到文件清单
5361
5393
  const markedPrompt = `${extraHint}【本轮用户请求】\n${text}\n【请求结束】\n\n${attachmentContext}${contextHint}`;
5362
- fullResponse = await agent.promptStream(markedPrompt, streamCallback, runState.abortController?.signal, channelId);
5394
+ // 闭包里 TS 不再保留 null 收窄 ⇒ 先收成局部 const
5395
+ const agentForRun = agent;
5396
+ // **K5 步骤⑦**: 入口只交一个一次性 `ExecutionRequest` (绑定/流/取消都在请求里), Pi 侧 runExecution 统一派发
5397
+ const request = {
5398
+ input: markedPrompt,
5399
+ channelId,
5400
+ signal: runState.abortController?.signal,
5401
+ onStream: streamCallback,
5402
+ };
5403
+ fullResponse = await deliverThroughActor(agentForRun, () => agentForRun.runExecution(request));
5363
5404
  }
5364
5405
  catch (err) {
5365
5406
  // abort 抛错: 保留已输出的部分 (fullResponse 可能是空字符串)
@@ -5730,7 +5771,8 @@ fetchState();
5730
5771
  const contextHint = attachmentContext + `[系统上下文] 队列消息 (auto-drain)\n`;
5731
5772
  // 4) promptStream
5732
5773
  const markedPrompt = `【本轮用户请求】\n${text}\n【请求结束】\n\n${contextHint}`;
5733
- const fullResponse = await agent.promptStream(markedPrompt, () => { }, runState.abortController?.signal, channelId);
5774
+ const agentForRun = agent;
5775
+ const fullResponse = await deliverThroughActor(agentForRun, () => agentForRun.promptStream(markedPrompt, () => { }, runState.abortController?.signal, channelId));
5734
5776
  if (!fullResponse.trim()) {
5735
5777
  broadcast({ type: 'error', content: '⚠️ AI 未返回内容' }, channelId);
5736
5778
  }
@@ -6932,7 +6974,7 @@ fetchState();
6932
6974
  // 2026-06-15: 同 /message 路径, 用显式 marker 包裹 userMessage, 避免 LLM 把它当背景信息
6933
6975
  const regenHint = await buildJudgmentHint(channel, channelId);
6934
6976
  const markedRegen = `${regenHint}\n\n【本轮用户请求】\n${userMessage}\n【请求结束】\n`;
6935
- fullResponse = await agent.promptStream(markedRegen, streamCallback, undefined, channelId);
6977
+ fullResponse = await deliverThroughActor(agent, () => agent.promptStream(markedRegen, streamCallback, undefined, channelId));
6936
6978
  // 2026-07-06: 防御性兜底 — 同 /message 路径
6937
6979
  if (!fullResponse.trim()) {
6938
6980
  fullResponse = '⚠️ AI 未返回内容, 请重试';
package/dist/web/sw.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * 现在: 代码类资源 (html/js/css/json) 一律 **network-first** (拿不到网才回退缓存),
7
7
  * 缓存名带版本号, activate 时清掉所有旧缓存; 图标等静态资源仍 cache-first。
8
8
  */
9
- const CACHE = 'bolloon-mobile-v0.5.4';
9
+ const CACHE = 'bolloon-mobile-v0.6.0';
10
10
  const SHELL = [
11
11
  './mobile.html',
12
12
  './mobile.css',