@bolloon/bolloon-agent 0.6.5 → 0.6.7

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 (188) hide show
  1. package/dist/agents/chain/chain-settlement.js +0 -1
  2. package/dist/agents/chat-segmenter.js +4 -16
  3. package/dist/agents/contacts/human-directory.js +155 -0
  4. package/dist/agents/contacts/tools.js +79 -0
  5. package/dist/agents/experience-review.js +0 -3
  6. package/dist/agents/goal-flywheel/goal-change.js +3 -4
  7. package/dist/agents/goal-resume.js +0 -4
  8. package/dist/agents/ha-daemon.js +85 -0
  9. package/dist/agents/ha-supervisor.js +146 -0
  10. package/dist/agents/intent-classifier.js +1 -1
  11. package/dist/agents/iteration-budget.js +12 -0
  12. package/dist/agents/knowledge-organizer.js +0 -2
  13. package/dist/agents/lesson-to-skill.js +0 -1
  14. package/dist/agents/loop-review.js +2 -2
  15. package/dist/agents/memory-store.js +384 -0
  16. package/dist/agents/opportunity-match.js +15 -3
  17. package/dist/agents/opportunity-web-search.js +39 -0
  18. package/dist/agents/p2p-chat-tools.js +1 -2
  19. package/dist/agents/parse-tool-call.js +9 -22
  20. package/dist/agents/pi-harness.js +1 -2
  21. package/dist/agents/pi-sdk-session-manager.js +8 -0
  22. package/dist/agents/pi-sdk-tools.js +180 -5
  23. package/dist/agents/pi-sdk.js +210 -503
  24. package/dist/agents/process-runner.js +28 -9
  25. package/dist/agents/react-loop.js +9 -17
  26. package/dist/agents/runner-resolver.js +4 -3
  27. package/dist/agents/skill-loader.js +22 -15
  28. package/dist/agents/skills-manager.js +1 -25
  29. package/dist/agents/workflow-pivot-loop.js +152 -61
  30. package/dist/bootstrap/context-collector.js +67 -7
  31. package/dist/bootstrap/context-hierarchy.js +13 -1
  32. package/dist/bootstrap/persona-loader.js +26 -0
  33. package/dist/cli/ink-app.js +44 -17
  34. package/dist/cli/intent-command.js +22 -12
  35. package/dist/cli/loading-tui.js +8 -2
  36. package/dist/cli/log-gate.js +40 -8
  37. package/dist/cli/mention-data.js +1 -0
  38. package/dist/cli/reply-hygiene.js +22 -0
  39. package/dist/cli/trace-line.js +61 -2
  40. package/dist/electron-build/agents/agent-gateway.js.map +1 -1
  41. package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
  42. package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
  43. package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
  44. package/dist/electron-build/agents/contacts/chain.js.map +1 -1
  45. package/dist/electron-build/agents/contacts/human-directory.js +204 -0
  46. package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
  47. package/dist/electron-build/agents/contacts/policy.js.map +1 -1
  48. package/dist/electron-build/agents/contacts/tools.js +112 -0
  49. package/dist/electron-build/agents/contacts/tools.js.map +1 -1
  50. package/dist/electron-build/agents/experience-review.js +0 -3
  51. package/dist/electron-build/agents/experience-review.js.map +1 -1
  52. package/dist/electron-build/agents/external-events.js.map +1 -1
  53. package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
  54. package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
  55. package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
  56. package/dist/electron-build/agents/goal-resume.js +0 -4
  57. package/dist/electron-build/agents/goal-resume.js.map +1 -1
  58. package/dist/electron-build/agents/intent-classifier.js +1 -1
  59. package/dist/electron-build/agents/lesson-to-skill.js +0 -1
  60. package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
  61. package/dist/electron-build/agents/loop-review.js +2 -2
  62. package/dist/electron-build/agents/loop-review.js.map +1 -1
  63. package/dist/electron-build/agents/memory-recall.js.map +1 -1
  64. package/dist/electron-build/agents/memory-store.js +430 -0
  65. package/dist/electron-build/agents/memory-store.js.map +1 -0
  66. package/dist/electron-build/agents/parse-tool-call.js +9 -22
  67. package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
  68. package/dist/electron-build/agents/pi-harness.js +1 -2
  69. package/dist/electron-build/agents/pi-harness.js.map +1 -1
  70. package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
  71. package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
  72. package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
  73. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  74. package/dist/electron-build/agents/pi-sdk.js +208 -501
  75. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  76. package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
  77. package/dist/electron-build/agents/process-runner.js +28 -9
  78. package/dist/electron-build/agents/process-runner.js.map +1 -1
  79. package/dist/electron-build/agents/runner-resolver.js +4 -3
  80. package/dist/electron-build/agents/runner-resolver.js.map +1 -1
  81. package/dist/electron-build/agents/skill-loader.js +22 -16
  82. package/dist/electron-build/agents/skill-loader.js.map +1 -1
  83. package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
  84. package/dist/electron-build/agents/skills-manager.js +0 -24
  85. package/dist/electron-build/agents/skills-manager.js.map +1 -1
  86. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  87. package/dist/electron-build/agents/workflow-engine.js.map +1 -1
  88. package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
  89. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  90. package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
  91. package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
  92. package/dist/electron-build/bootstrap/context-collector.js +68 -7
  93. package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
  94. package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
  95. package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
  96. package/dist/electron-build/bootstrap/persona-loader.js +27 -0
  97. package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
  98. package/dist/electron-build/electron-preload.js.map +1 -1
  99. package/dist/electron-build/external-engines/delegate.js.map +1 -1
  100. package/dist/electron-build/kernel/channel-actor.js +31 -2
  101. package/dist/electron-build/kernel/channel-actor.js.map +1 -1
  102. package/dist/electron-build/kernel/model-runtime.js +8 -1
  103. package/dist/electron-build/kernel/model-runtime.js.map +1 -1
  104. package/dist/electron-build/llm/config-store.js +20 -0
  105. package/dist/electron-build/llm/config-store.js.map +1 -1
  106. package/dist/electron-build/llm/embedding-adapter.js +75 -0
  107. package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
  108. package/dist/electron-build/llm/pi-ai.js +112 -38
  109. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  110. package/dist/electron-build/llm/system-prompt/registry.js +8 -6
  111. package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
  112. package/dist/electron-build/network/agent-network.js.map +1 -1
  113. package/dist/electron-build/orbitdb/memory-sync.js +76 -0
  114. package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
  115. package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
  116. package/dist/electron-build/security/tool-gate.js +2 -0
  117. package/dist/electron-build/security/tool-gate.js.map +1 -1
  118. package/dist/electron-build/setup/setup-store.js +34 -6
  119. package/dist/electron-build/setup/setup-store.js.map +1 -1
  120. package/dist/electron-build/social/heartbeat.js +18 -5
  121. package/dist/electron-build/social/heartbeat.js.map +1 -1
  122. package/dist/electron-build/utils/update-manager.js.map +1 -1
  123. package/dist/index.js +250 -12
  124. package/dist/ios/bolloon-web.json +3 -3
  125. package/dist/ios/client.js +74 -35
  126. package/dist/ios/icons/icon-1400x1400.png +0 -0
  127. package/dist/ios/icons/icon-1400x1400.webp +0 -0
  128. package/dist/ios/index.html +122 -108
  129. package/dist/ios/mobile-agent.js +572 -45
  130. package/dist/ios/mobile-contacts.js +0 -4
  131. package/dist/ios/mobile-core.js +8079 -4590
  132. package/dist/ios/mobile-harness.js +1004 -0
  133. package/dist/ios/mobile-memory.js +317 -0
  134. package/dist/ios/mobile-orbitdb.js +194 -0
  135. package/dist/ios/mobile-persona.js +80 -0
  136. package/dist/ios/mobile-privacy.js +2 -3
  137. package/dist/ios/mobile.css +286 -21
  138. package/dist/ios/mobile.html +122 -108
  139. package/dist/ios/mobile.js +434 -152
  140. package/dist/ios/routes-contacts.js +94 -1
  141. package/dist/ios/routes-world.js +13 -2
  142. package/dist/ios/server.js +54 -41
  143. package/dist/ios/style.css +13 -5
  144. package/dist/ios/sw.js +1 -1
  145. package/dist/ios/ui/message-renderer.js +4 -18
  146. package/dist/ios/ui/step-timeline.js +14 -1
  147. package/dist/kernel/channel-actor.js +30 -2
  148. package/dist/kernel/gate-scan.js +2 -2
  149. package/dist/kernel/ha/ha-arbiter.js +177 -0
  150. package/dist/kernel/ha/ha-guard.js +151 -0
  151. package/dist/kernel/index.js +36 -0
  152. package/dist/kernel/model-runtime.js +8 -1
  153. package/dist/kernel/plan.js +5 -5
  154. package/dist/kernel/roster.js +3 -3
  155. package/dist/llm/config-store.js +20 -0
  156. package/dist/llm/embedding-adapter.js +69 -0
  157. package/dist/llm/pi-ai.js +110 -37
  158. package/dist/llm/system-prompt/registry.js +8 -6
  159. package/dist/orbitdb/memory-sync.js +70 -0
  160. package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
  161. package/dist/security/tool-gate.js +2 -0
  162. package/dist/setup/setup-store.js +34 -6
  163. package/dist/social/heartbeat.js +18 -5
  164. package/dist/web/bolloon-web.json +3 -3
  165. package/dist/web/client.js +74 -35
  166. package/dist/web/icons/icon-1400x1400.png +0 -0
  167. package/dist/web/icons/icon-1400x1400.webp +0 -0
  168. package/dist/web/index.html +37 -25
  169. package/dist/web/mobile-agent.js +572 -45
  170. package/dist/web/mobile-contacts.js +0 -4
  171. package/dist/web/mobile-core.js +8079 -4590
  172. package/dist/web/mobile-harness.js +1004 -0
  173. package/dist/web/mobile-memory.js +317 -0
  174. package/dist/web/mobile-orbitdb.js +194 -0
  175. package/dist/web/mobile-persona.js +80 -0
  176. package/dist/web/mobile-privacy.js +2 -3
  177. package/dist/web/mobile.css +286 -21
  178. package/dist/web/mobile.html +122 -108
  179. package/dist/web/mobile.js +434 -152
  180. package/dist/web/routes-contacts.js +94 -1
  181. package/dist/web/routes-world.js +13 -2
  182. package/dist/web/server.js +54 -41
  183. package/dist/web/style.css +13 -5
  184. package/dist/web/sw.js +1 -1
  185. package/dist/web/ui/message-renderer.js +4 -18
  186. package/dist/web/ui/step-timeline.js +14 -1
  187. package/package.json +3 -2
  188. package/scripts/build-web.ts +16 -1
@@ -413,8 +413,101 @@ export function registerContactRoutes(app, opts = {}) {
413
413
  res.status(500).json({ ok: false, error: String(err?.message || err) });
414
414
  }
415
415
  });
416
+ // ── 手机号目录 (2026-10-11, phone-account-lookup-design 落点 A) ────────────
417
+ // 隐私: 请求/响应全程只过「盲化哈希」, 明文手机号只在请求体出现一次 (发布/查询),
418
+ // 永不出现在响应里 (与 contacts 既有红线一致)。
419
+ const dirHome = () => opts.home || undefined;
420
+ /** 发布手机号 → DID 指针 (opt-in; 用户显式调用) */
421
+ app.post('/api/contacts/directory/publish', async (req, res) => {
422
+ try {
423
+ const { publishEntry, isPlausiblePhone } = await import('../agents/contacts/human-directory.js');
424
+ const phone = String(req.body?.phone || '');
425
+ const did = String(req.body?.did || ownerDidFromDisk());
426
+ if (!isPlausiblePhone(phone))
427
+ return res.status(400).json({ ok: false, error: '手机号格式不正确' });
428
+ const e = await publishEntry(phone, did, {
429
+ displayName: req.body?.displayName ? String(req.body.displayName) : undefined,
430
+ publicKeyHex: req.body?.publicKeyHex ? String(req.body.publicKeyHex) : undefined,
431
+ homeDir: dirHome(),
432
+ });
433
+ res.json({ ok: true, phoneHash: e.phoneHash.slice(0, 8) + '…', did: e.did, publicKeyHex: e.publicKeyHex || null, publishedAt: e.publishedAt });
434
+ }
435
+ catch (err) {
436
+ res.status(500).json({ ok: false, error: String(err?.message || err) });
437
+ }
438
+ });
439
+ /** 撤销目录条目 (软删) */
440
+ app.post('/api/contacts/directory/retract', async (req, res) => {
441
+ try {
442
+ const { retractEntry } = await import('../agents/contacts/human-directory.js');
443
+ const did = String(req.body?.did || ownerDidFromDisk());
444
+ const ok = await retractEntry(did, dirHome());
445
+ res.json({ ok, revoked: ok });
446
+ }
447
+ catch (err) {
448
+ res.status(500).json({ ok: false, error: String(err?.message || err) });
449
+ }
450
+ });
451
+ /** 按手机号查账号 (在本机目录内找; 响应只回 DID/昵称, 绝不回手机号) */
452
+ app.post('/api/contacts/directory/lookup', async (req, res) => {
453
+ try {
454
+ const { lookupPhone, isPlausiblePhone } = await import('../agents/contacts/human-directory.js');
455
+ const phone = String(req.body?.phone || '');
456
+ if (!isPlausiblePhone(phone))
457
+ return res.status(400).json({ ok: false, error: '手机号格式不正确' });
458
+ const r = await lookupPhone(phone, dirHome());
459
+ if (!r.found)
460
+ return res.json({ ok: true, found: false, note: '未找到 (未注册或未公开)' });
461
+ res.json({ ok: true, found: true, did: r.did, publicKeyHex: r.publicKeyHex || null, displayName: r.displayName || null });
462
+ }
463
+ catch (err) {
464
+ res.status(500).json({ ok: false, error: String(err?.message || err) });
465
+ }
466
+ });
467
+ /** 按手机号直接发好友请求 (P1, 2026-10-15): directory lookup → publicKeyHex → 既有 /api/friend-request */
468
+ app.post('/api/contacts/directory/add-friend', async (req, res) => {
469
+ try {
470
+ const { lookupPhone, isPlausiblePhone } = await import('../agents/contacts/human-directory.js');
471
+ const phone = String(req.body?.phone || '');
472
+ if (!isPlausiblePhone(phone))
473
+ return res.status(400).json({ ok: false, error: '手机号格式不正确' });
474
+ const r = await lookupPhone(phone, dirHome());
475
+ if (!r.found)
476
+ return res.json({ ok: true, found: false, note: '未找到 (未注册或未公开)' });
477
+ if (!r.publicKeyHex)
478
+ return res.status(422).json({ ok: false, found: true, error: `对方已注册目录但未公开 P2P 公钥 (${r.did?.slice(0, 20)}…), 无法直接加好友` });
479
+ // 委托给既有好友请求链路 (本机端口; 与 pi-sdk accept 工具同口径)
480
+ const port = process.env.PORT || '54188';
481
+ const fr = await fetch(`http://127.0.0.1:${port}/api/friend-request`, {
482
+ method: 'POST',
483
+ headers: { 'Content-Type': 'application/json' },
484
+ body: JSON.stringify({
485
+ targetPublicKey: r.publicKeyHex,
486
+ name: req.body?.name ? String(req.body.name) : r.displayName || undefined,
487
+ message: req.body?.message ? String(req.body.message) : `来自手机号目录: 想加你为好友`,
488
+ note: `via-phone-directory`,
489
+ }),
490
+ });
491
+ const data = await fr.json();
492
+ res.status(fr.status).json({ ok: data.ok !== false, found: true, did: r.did, friend: data });
493
+ }
494
+ catch (err) {
495
+ res.status(500).json({ ok: false, error: String(err?.message || err) });
496
+ }
497
+ });
498
+ /** 列出已发布条目 (本机审计; 只回脱敏哈希) */
499
+ app.get('/api/contacts/directory/list', async (_req, res) => {
500
+ try {
501
+ const { readDirectory, summarizeEntries } = await import('../agents/contacts/human-directory.js');
502
+ const entries = await readDirectory(dirHome());
503
+ res.json({ ok: true, entries: summarizeEntries(entries) });
504
+ }
505
+ catch (err) {
506
+ res.status(500).json({ ok: false, error: String(err?.message || err) });
507
+ }
508
+ });
416
509
  // ── 隐藏守卫自检 (验收/测试直接用) ──────────────────────────────────────────
417
510
  app.get('/api/contacts/_selfcheck', (_req, res) => {
418
- res.json({ ok: true, plaintextGuard: looksLikePlaintextSecret({ contacts: [{ value: '+8613812345678' }] }) });
511
+ res.json({ ok: true, plaintextGuard: looksLikePlaintextSecret({ contacts: [{ value: '+861****5678' }] }) });
419
512
  });
420
513
  }
@@ -12,7 +12,10 @@ function bodyOf(req) {
12
12
  }
13
13
  /** 限制输入形状: 只收明确字段, 不整个吞前端对象 */
14
14
  function intentInputFromBody(body) {
15
- const b = bodyOf(body);
15
+ // 2026-10-06 修 bug: 之前调 bodyOf(body) —— bodyOf 期望 Request 访问 .body,
16
+ // 传 body 对象本身时 req.body = body.body = undefined ⇒ 永远返回 {} ⇒ 所有字段都丢。
17
+ // body 此刻已是 express.json 解析后的对象, 直接校验即可。
18
+ const b = body && typeof body === 'object' ? body : {};
16
19
  const out = {};
17
20
  if (typeof b.text === 'string' && b.text.trim())
18
21
  out.text = b.text.trim();
@@ -69,7 +72,15 @@ export function registerWorldRoutes(app) {
69
72
  const minScore = Number(minScoreRaw);
70
73
  const limitRaw = typeof req.query.limit === 'string' ? req.query.limit : '50';
71
74
  const limit = Math.max(1, Math.min(200, parseInt(limitRaw, 10) || 50));
72
- const r = await scanOpportunities({ minScore: Number.isFinite(minScore) ? minScore : 0.4, limit });
75
+ // 2026-10-16: 通讯录卡片 — dids=<did1>,<did2> 只返回这些 provider 投递的机会 (手机号通讯录联系人)
76
+ const providerDids = typeof req.query.dids === 'string'
77
+ ? req.query.dids.split(',').map((d) => d.trim()).filter(Boolean)
78
+ : [];
79
+ const r = await scanOpportunities({
80
+ minScore: Number.isFinite(minScore) ? minScore : 0.4,
81
+ limit,
82
+ providerDids: providerDids.length ? providerDids : undefined,
83
+ });
73
84
  if (!r.ok)
74
85
  return json(res, 500, { ok: false, error: r.error });
75
86
  json(res, 200, { ok: true, count: r.results.length, minScore, opportunities: r.results });
@@ -920,6 +920,7 @@ async function handleV3P2PMessage(parsed, conn, comm) {
920
920
  output: event.output,
921
921
  error: event.error,
922
922
  args: event.args,
923
+ durationMs: event.durationMs, // 2026-10-12: 命令执行时间读秒
923
924
  fromPublicKey: v3P2PRef?.getPublicKey() || ''
924
925
  }
925
926
  });
@@ -1668,6 +1669,30 @@ export async function createWebServer(port = 3000, options = {}) {
1668
1669
  catch (err) {
1669
1670
  console.warn('[runs] 对账失败 (非致命):', err?.message);
1670
1671
  }
1672
+ // 2026-10-16 (leo: 双内核冗余 — 独立 kernel 守护进程): HA 双活 — 主进程以 primary 写心跳,
1673
+ // spawn 独立 kernel 守护进程 (standby), 主死 → daemon 接管; daemon 死 → 自动重启。
1674
+ // 环境变量门控: BOLLOON_HA=1 启用 (默认关 — 单节点常规运行不启动额外进程)。
1675
+ if (process.env.BOLLOON_HA === '1') {
1676
+ try {
1677
+ const { createHaSupervisor } = await import('../agents/ha-supervisor.js');
1678
+ const haSup = createHaSupervisor({
1679
+ log: (level, message) => {
1680
+ if (level === 'error')
1681
+ console.warn(message);
1682
+ else if (level === 'warn')
1683
+ console.warn(message);
1684
+ else if (process.env.BOLLOON_VERBOSE === '1')
1685
+ console.log(message);
1686
+ },
1687
+ });
1688
+ await haSup.start();
1689
+ globalThis.__bolloonHaSupervisor = haSup;
1690
+ console.log('[ha] 双活守护已启动 (primary 心跳 + standby kernel 守护进程)');
1691
+ }
1692
+ catch (err) {
1693
+ console.warn('[ha] 双活守护启动失败 (非致命):', err?.message);
1694
+ }
1695
+ }
1671
1696
  // 2026-09-16 (M2-B): ExecutionSupervisor —— 长期执行层。
1672
1697
  // 页面关掉、进程重启后由它把 Goal 继续下去; 跨进程排他靠 Goal lease (不是内存状态)。
1673
1698
  // Harness 管"这一段能不能安全执行", Supervisor 管"这个目标还要不要继续执行"。
@@ -4941,7 +4966,7 @@ fetchState();
4941
4966
  // (与邮箱串行一致), web 不再自己持 abortController。取到 agent 后立刻开户 (见下面 getAgentForChannel 之后)。
4942
4967
  let abortSignal;
4943
4968
  // 2026-07-04: pivot loop safety net — 防止 LLM hang (minimax M3
4944
- // 偶尔反复 think 不输出 <final gen>, pivot 连 5 次无进展时会 hang 在
4969
+ // 偶尔反复 think 不输出结果, pivot 连 5 次无进展时会 hang 在
4945
4970
  // quality 评估). setTimeout 让 LLM 客户端收 signal 主动 break.
4946
4971
  // 2026-07-06: 30s 太短 — minimax M3 单次 LLM 约 10s, pivot 默认 moderate profile 30 iter
4947
4972
  // 留 ~300s. 之前 30s 自动 abort 时, 一次"你好"调用就被截断, 用户看到 ❌ 循环异常 + 空内容.
@@ -5166,56 +5191,38 @@ fetchState();
5166
5191
  const judgmentHint = await buildJudgmentHint(channelForJudgment, channelId);
5167
5192
  if (judgmentHint)
5168
5193
  contextHint += judgmentHint;
5169
- // 2026-06-10: 注入 skills 列表 (本机 ~/.bolloon/skills/ 下所有 skills)
5170
- // 让 LLM 知道有哪些 skill 可用, 在回复中提示用户
5194
+ // 2026-06-10: 注入 skills 存在性提示 (2026-10-16 按需化: 删逐条列表 —
5195
+ // pi-sdk buildSkillsIndex 已注入前 40 技能名+描述, 这里只保留存在提示与触发语法)
5171
5196
  try {
5172
- const { loadSkillsFromPaths, defaultSkillPaths, describeSkill } = await import('../agents/skill-loader.js');
5197
+ const { loadSkillsFromPaths, defaultSkillPaths } = await import('../agents/skill-loader.js');
5173
5198
  const paths = defaultSkillPaths();
5174
5199
  const skills = await loadSkillsFromPaths(paths);
5175
5200
  if (skills.length > 0) {
5176
- contextHint += `[系统上下文] 本机已加载的 skills (${skills.length} 个, 你可以提示用户主动调用):\n`;
5177
- for (const s of skills.slice(0, 20)) { // 上限 20 避免 prompt 过长
5178
- const desc = (s.description || '').slice(0, 80);
5179
- contextHint += ` - /${s.name}${desc ? ' — ' + desc : ''}\n`;
5180
- }
5181
- contextHint += '调用语法: 用户说 "/技能名 ..." 或 你回复时建议 "/技能名 ..." 让用户主动触发.\n\n';
5201
+ contextHint += `[系统上下文] 本机已加载 ${skills.length} 个 skills (技能名+描述见上方技能库索引; 需要详情时用 list_skills / read_skill 按需读取). 调用语法: 用户说 "/技能名 ..." 或 你回复时建议 "/技能名 ..." 让用户主动触发.\n\n`;
5182
5202
  }
5183
5203
  }
5184
5204
  catch (err) {
5185
5205
  // 静默失败 — skills 不是核心, 加载失败不阻塞
5186
5206
  }
5187
- // 2026-06-10: 注入 human values 摘要 (最常用的 judgment / 价值偏好)
5188
- // 与 judgment 不同: values 是更宏观的"用户偏好", judgment 是针对具体决策的约束
5207
+ // 2026-06-10: human values 存在性提示 (2026-10-16 按需化: 删 8 条摘要列表 —
5208
+ // buildJudgmentHint 已注入绑定判断力 + 候选, 这里只保留"库里有多少"的指针)
5189
5209
  try {
5190
5210
  const { loadAllJudgments } = await import('../pi-ecosystem-judgment/human-value-store.js');
5191
5211
  const allJudgments = await loadAllJudgments().catch(() => []);
5192
- // 把所有 judgment 视作软参考 (跟 buildJudgmentHint 的 candidates 同理)
5193
5212
  if (Array.isArray(allJudgments) && allJudgments.length > 0) {
5194
- contextHint += `[系统上下文] 用户的核心价值倾向 (来自 ${allJudgments.length} 条历史 judgment, 软参考, 体现而非复述):\n`;
5195
- for (const j of allJudgments.slice(0, 8)) {
5196
- const decision = (j.decision || '').slice(0, 80);
5197
- contextHint += ` - ${decision}\n`;
5198
- }
5199
- contextHint += '\n';
5213
+ contextHint += `[系统上下文] 判断力库存 ${allJudgments.length} 条历史 judgment (绑定判断力见上方; 需要更多时用读取判断力的工具按需取, 软参考体现而非复述).\n\n`;
5200
5214
  }
5201
5215
  }
5202
5216
  catch (err) {
5203
5217
  // 静默失败
5204
5218
  }
5205
- // 2026-06-10: 注入 documents 列表 (本机 documents/ 目录的文档元数据)
5206
- // 让 LLM 知道有文档存在, 用户可主动要求读
5219
+ // 2026-06-10: documents 存在性提示 (2026-10-16 按需化: 删逐条元数据 —
5220
+ // 只留计数, 用户提到某文档时用读文档工具按需读取)
5207
5221
  try {
5208
5222
  const { documentStore } = await import('../documents/store.js');
5209
5223
  const docs = await documentStore.getReceivedDocuments(50).catch(() => []);
5210
5224
  if (Array.isArray(docs) && docs.length > 0) {
5211
- contextHint += `[系统上下文] 本机 documents (${docs.length} 篇, 用户可让你读):\n`;
5212
- for (const d of docs.slice(0, 10)) {
5213
- const name = d.fileName || d.id || '(未命名)';
5214
- const size = d.fileSize ? ` (${Math.round(d.fileSize / 1024)}KB)` : '';
5215
- const sender = d.fromNodeId ? ` [来自 ${d.fromNodeIdShort || d.fromNodeId.substring(0, 8)}…]` : '';
5216
- contextHint += ` - ${name}${size}${sender}\n`;
5217
- }
5218
- contextHint += '用户提到某文档时, 你可以调用读文档工具读取并总结.\n\n';
5225
+ contextHint += `[系统上下文] 本机 documents ${docs.length} 篇 (用户提到某文档时, 用读文档工具按需读取并总结).\n\n`;
5219
5226
  }
5220
5227
  }
5221
5228
  catch (err) {
@@ -5281,8 +5288,10 @@ fetchState();
5281
5288
  catch { /* 无 sessions 目录 */ }
5282
5289
  }
5283
5290
  if (summariesToRead.length > 0) {
5284
- const memBlock = summariesToRead.map(s => s.trim().slice(-1500)).join('\n\n---\n\n');
5285
- contextHint += `[系统上下文] 动态状态层 · chat-worksite (上次做到哪 — 本 channel 历史记忆, 来自 memory-compressor 摘要, 引用而非复述):\n${memBlock.slice(-2500)}\n\n`;
5291
+ // 2026-10-16 按需化: 历史记忆只注尾部 800 chars 作为"上次做到哪"的锚,
5292
+ // 完整摘要按需用 read 工具取 (记忆是引用而非复述, 全量 2500 常驻浪费)
5293
+ const memBlock = summariesToRead.map(s => s.trim().slice(-800)).join('\n\n---\n\n');
5294
+ contextHint += `[系统上下文] 动态状态层 · chat-worksite (上次做到哪 — 本 channel 历史记忆尾部摘录, 来自 memory-compressor 摘要, 引用而非复述):\n${memBlock.slice(-800)}\n\n`;
5286
5295
  }
5287
5296
  else {
5288
5297
  // 2026-08-08 (DID 目录读侧): 磁盘无摘要时, 回退读 DID 目录 memory 表 (跨设备同步来的记忆)
@@ -5293,11 +5302,11 @@ fetchState();
5293
5302
  const rows = catalogMemoryRows(cat, agentId);
5294
5303
  if (rows.length > 0) {
5295
5304
  const memBlock = rows.slice(0, 2)
5296
- .map(({ row }) => String(row.data.summary || '').slice(-1500))
5305
+ .map(({ row }) => String(row.data.summary || '').slice(-800))
5297
5306
  .filter(Boolean)
5298
5307
  .join('\n\n---\n\n');
5299
5308
  if (memBlock) {
5300
- contextHint += `[系统上下文] 动态状态层 · chat-worksite (DID 目录同步记忆, 引用而非复述):\n${memBlock.slice(-2500)}\n\n`;
5309
+ contextHint += `[系统上下文] 动态状态层 · chat-worksite (DID 目录同步记忆尾部摘录, 引用而非复述):\n${memBlock.slice(-800)}\n\n`;
5301
5310
  }
5302
5311
  }
5303
5312
  }
@@ -5338,24 +5347,27 @@ fetchState();
5338
5347
  }
5339
5348
  const linkedIds = channelForJudgment?.linkedDocumentIds;
5340
5349
  if (Array.isArray(linkedIds) && linkedIds.length > 0) {
5350
+ // 2026-10-16 按需化: 关联文档不再每轮全文注入 (10篇×1.5KB=15KB 常驻 prompt),
5351
+ // 只列文件名指针, 用户提到某文档时用读文档工具按需读取。
5352
+ // 关联文档本身不常在单轮对话中被用到, 全量注入是浪费。
5341
5353
  try {
5342
5354
  const { documentStore } = await import('../documents/store.js');
5343
- contextHint += `[系统上下文] 此 channel 关联了 ${linkedIds.length} 篇文档 (已自动加载内容, 你应基于它们回答):\n`;
5344
- let loaded = 0;
5355
+ const names = [];
5345
5356
  for (const docId of linkedIds.slice(0, 10)) {
5346
5357
  const doc = await documentStore.readDocument(docId).catch(() => null);
5347
5358
  if (!doc)
5348
5359
  continue;
5349
5360
  const name = doc.metadata?.fileName || docId;
5350
- const content = (doc.content || '').slice(0, 1500); // 单篇 1.5KB 上限, 总 prompt 防爆
5351
- contextHint += `\n--- 文档: ${name} ---\n${content}\n--- 文档结束 ---\n`;
5352
- loaded++;
5361
+ names.push(name);
5362
+ if (names.length >= 5)
5363
+ break; // 指针只列前 5, 其余计数
5353
5364
  }
5354
- if (loaded === 0) {
5355
- contextHint += `(但加载失败, 文档可能已被删除)\n\n`;
5365
+ if (names.length > 0) {
5366
+ const more = linkedIds.length > names.length ? ` (共 ${linkedIds.length} 篇)` : '';
5367
+ contextHint += `[系统上下文] 此 channel 关联了 ${linkedIds.length} 篇文档 — ${names.join('、')}${more}. 用户提到某篇时, 用读文档工具按需读取全文再回答.\n\n`;
5356
5368
  }
5357
5369
  else {
5358
- contextHint += '\n';
5370
+ contextHint += `[系统上下文] 此 channel 关联了 ${linkedIds.length} 篇文档 (内容读取失败, 用读文档工具按需读取).\n\n`;
5359
5371
  }
5360
5372
  }
5361
5373
  catch (err) {
@@ -6901,6 +6913,7 @@ fetchState();
6901
6913
  output: event.output,
6902
6914
  error: event.error,
6903
6915
  args: event.args,
6916
+ durationMs: event.durationMs, // 2026-10-12: 命令执行时间读秒
6904
6917
  }, channelId);
6905
6918
  }
6906
6919
  else if (event.type === 'error') {
@@ -126,13 +126,20 @@ body {
126
126
  flex-shrink: 0;
127
127
  transition: var(--transition);
128
128
  transform: translateX(0);
129
- position: relative;
129
+ /* 2026-10-06: 改为 fixed 覆盖 — sidebar 收起时绝对定位脱离流, relative 会被挤成 0 宽,
130
+ 恢复条必须固定在屏幕左侧才能被看到/点到 */
131
+ position: fixed;
132
+ left: 0;
133
+ top: 0;
134
+ height: 100%;
130
135
  z-index: 99;
131
136
  }
132
137
 
133
138
  .sidebar-collapsed.hidden {
134
139
  transform: translateX(calc(-1 * var(--sidebar-collapsed)));
135
- position: absolute;
140
+ position: fixed;
141
+ left: 0;
142
+ top: 0;
136
143
  height: 100%;
137
144
  }
138
145
 
@@ -1876,18 +1883,19 @@ body {
1876
1883
  flex-shrink: 0;
1877
1884
  }
1878
1885
  .header-tab {
1879
- padding: 5px 14px;
1886
+ padding: 7px 22px;
1880
1887
  background: transparent;
1881
1888
  border: none;
1882
1889
  border-radius: 6px;
1883
1890
  color: var(--text-secondary);
1884
- font-size: 12px;
1891
+ font-size: 13px;
1885
1892
  font-weight: 600;
1886
1893
  cursor: pointer;
1887
1894
  transition: var(--transition);
1888
1895
  display: flex;
1889
1896
  align-items: center;
1890
- gap: 4px;
1897
+ gap: 5px;
1898
+ white-space: nowrap;
1891
1899
  }
1892
1900
  .header-tab:hover {
1893
1901
  color: var(--text-primary);
package/dist/ios/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.6.5';
9
+ const CACHE = 'bolloon-mobile-v0.6.6';
10
10
  const SHELL = [
11
11
  './mobile.html',
12
12
  './mobile.css',
@@ -261,21 +261,10 @@ function segmentChatReply(reply, ctx) {
261
261
  if (remaining.trim()) {
262
262
  remaining = filterFillerText(remaining);
263
263
  }
264
- const finalIdx = remaining.indexOf("<final gen>");
265
- let beforeFinalText = remaining;
266
- if (finalIdx >= 0) {
267
- beforeFinalText = remaining.substring(0, finalIdx);
268
- }
269
- const textContent = beforeFinalText.trim();
264
+ const textContent = remaining.trim();
270
265
  if (textContent) {
271
266
  segments.push({ type: "text", content: textContent });
272
267
  }
273
- if (finalIdx >= 0) {
274
- const afterFinal = remaining.substring(finalIdx + "<final gen>".length).trim();
275
- if (afterFinal) {
276
- segments.push({ type: "final", content: afterFinal });
277
- }
278
- }
279
268
  return segments;
280
269
  }
281
270
  function extractAndPush(type, text, out, re) {
@@ -530,11 +519,7 @@ function addMessage(content, type, save = true, container, usedJudgmentIds = [],
530
519
  div.className = `message message-${type}`;
531
520
  let cleanContent = content;
532
521
  if (type === "ai") {
533
- cleanContent = cleanContent.replace(/<think>[\s\S]*?<\/think>/g, "");
534
- const finalGenIdx = cleanContent.indexOf("<final gen>");
535
- if (finalGenIdx >= 0) {
536
- cleanContent = cleanContent.substring(0, finalGenIdx).trim();
537
- }
522
+ cleanContent = cleanContent.replace(/ thinking[\s\S]*?<\/think>/g, "");
538
523
  }
539
524
  const knownToolNames = ctx && ctx.knownToolNames || /* @__PURE__ */ new Set();
540
525
  const segments = segmentChatReply(cleanContent, { knownToolNames });
@@ -858,7 +843,8 @@ function handleStepEvent(data, ctx = { messagesEl: null, messagesContainers: /*
858
843
  args: data.args,
859
844
  success: data.success,
860
845
  output: data.output,
861
- error: data.error
846
+ error: data.error,
847
+ durationMs: data.durationMs
862
848
  });
863
849
  }
864
850
  function flushStepEventBuffer(channelId, ctx) {
@@ -1,6 +1,15 @@
1
1
  // src/web/ui/step-timeline.ts
2
2
  var MAX_VISIBLE_NODES = 20;
3
3
  var STORAGE_PREFIX = "bolloon.stepTimeline.expanded.";
4
+ function formatDuration(ms) {
5
+ if (!Number.isFinite(ms) || ms < 0) return "(耗时未知)";
6
+ if (ms < 1e3) return `${ms}ms`;
7
+ const s = ms / 1e3;
8
+ if (s < 60) return `${s.toFixed(1)}s`;
9
+ const m = Math.floor(s / 60);
10
+ const rem = Math.round(s % 60);
11
+ return `${m}m${rem}s`;
12
+ }
4
13
  var stateMap = /* @__PURE__ */ new WeakMap();
5
14
  function readExpanded(channelId) {
6
15
  if (!channelId) return false;
@@ -121,7 +130,10 @@ function render(timelineEl) {
121
130
  }
122
131
  node.setAttribute("data-status", step.status);
123
132
  const label = node.querySelector(".step-timeline-label");
124
- if (label) label.textContent = step.name;
133
+ if (label) {
134
+ const dur = step.durationMs !== void 0 && step.status !== "active" ? ` · ${formatDuration(step.durationMs)}` : "";
135
+ label.textContent = step.name + dur;
136
+ }
125
137
  const argsEl = node.querySelector(".step-timeline-args");
126
138
  if (argsEl) {
127
139
  const argStr = step.args && Object.keys(step.args).length > 0 ? JSON.stringify(step.args).slice(0, 60) : "";
@@ -265,6 +277,7 @@ function pushStepToTimeline(timelineEl, eventType, data) {
265
277
  steps[targetIdx].status = data.success === false ? "error" : "done";
266
278
  if (data.output) steps[targetIdx].output = data.output;
267
279
  if (data.error) steps[targetIdx].error = data.error;
280
+ if (data.durationMs !== void 0) steps[targetIdx].durationMs = data.durationMs;
268
281
  } else {
269
282
  steps[targetIdx].status = "error";
270
283
  if (data.error) steps[targetIdx].error = data.error;
@@ -23,6 +23,17 @@ import { AsyncLocalStorage } from 'node:async_hooks';
23
23
  * ⇒ 用 ALS 记住"我在谁的上下文里", 同 actor 重入时**直跑**, 不同 actor 照常排队。
24
24
  */
25
25
  const actorCtx = new AsyncLocalStorage();
26
+ /**
27
+ * 2026-10-13: 当前 mailbox 任务被注入的**取消信号**。
28
+ * `deliverThroughActor` 进入执行时调 `actor.beginCancellation()` 并把 signal 注入本 ALS,
29
+ * 业务侧 (CLI/Web) 在 run() 里经 `currentRequestSignal()` 取它传给模型/循环调用 ⇒
30
+ * Ctrl+C / 终止按钮走 `actor.abort()` / `abortActorsOfChannel()` 能真正打断回合。
31
+ * 零业务依赖: 只用 node:async_hooks, 不 import 任何业务模块。
32
+ */
33
+ const requestSignalCtx = new AsyncLocalStorage();
34
+ export function currentRequestSignal() {
35
+ return requestSignalCtx.getStore() ?? undefined;
36
+ }
26
37
  /** 当前是否正跑在某个 Actor 的 mailbox 任务内 (诊断用) */
27
38
  export function currentActorContext() {
28
39
  return actorCtx.getStore();
@@ -245,6 +256,18 @@ export function resetActors() {
245
256
  channelQueues.clear();
246
257
  privateActorsCreated = 0;
247
258
  }
259
+ /**
260
+ * 2026-10-13: 在 mailbox 任务体内开新的取消位并把 signal 注入 ALS (见 deliverThroughActor)。
261
+ * 收尾时**只有本任务仍是取消位主人**才清掉 —— 嵌套/并行路径下防 abort 打到下一个任务。
262
+ */
263
+ function runWithCancellation(actor, run) {
264
+ const ac = new AbortController();
265
+ actor.state.cancellation = ac;
266
+ return requestSignalCtx.run(ac.signal, () => Promise.resolve(run()).finally(() => {
267
+ if (actor.state.cancellation === ac)
268
+ actor.state.cancellation = null;
269
+ }));
270
+ }
248
271
  export async function deliverThroughActor(
249
272
  // 形参故意放宽成 `unknown`: 调用点的 receiver 类型五花八门 (AgentSession / 结构子集 / any),
250
273
  // 写成 `{ actor?: ChannelActor }` 会触发 TS 的弱类型检查 ("no properties in common") 而误报。
@@ -264,10 +287,15 @@ holder, run, opts = {}) {
264
287
  const q = getChannelQueue(ch);
265
288
  if (channelCtx.getStore() === q)
266
289
  return run();
267
- return q.submit(() => channelCtx.run(q, () => actor.submit(() => actorCtx.run(actor, () => run()))));
290
+ return q.submit(() => channelCtx.run(q, () => actor.submit(() => actorCtx.run(actor, () => runWithCancellation(actor, run)))));
268
291
  }
269
292
  }
270
- return actor.submit(run);
293
+ // 2026-10-13 (leo: 「Ctrl+C 中断的落点必须是 kernel 的取消通道」):
294
+ // mailbox 任务体内开一个新的取消位 (state.cancellation) 并把 signal 注入 ALS ——
295
+ // 业务侧 run() 经 `currentRequestSignal()` 取到它 ⇒ Ctrl+C abort 能打断回合。
296
+ // **重入保护里直跑的分支不重新开位**: 嵌套的 inner run 复用同一个外层的 signal,
297
+ // 不会被内层 abort 清掉 (abort() 把 state.cancellation 置 null, 而 signal 经 ALS 走闭包引用)。
298
+ return actor.submit(() => actorCtx.run(actor, () => runWithCancellation(actor, run)));
271
299
  }
272
300
  // ─────────────────────────────────────────────────────────────────────────────
273
301
  // channel 级串行锁 (K5 步骤⑤) —— **能力先落地, 默认不启用**
@@ -108,7 +108,7 @@ export function scanProhibition(files, p) {
108
108
  const out = [];
109
109
  const targets = p.targets ?? [];
110
110
  const calls = p.writeCalls ?? [];
111
- const callRe = calls.length ? new RegExp(`\\b(${calls.join('|')})\\s*\\(`) : null;
111
+ const callRe = calls.length ? new RegExp(`(?<![\\w.])(?<!async\\s)(${calls.join('|')})\\s*\\(`) : null;
112
112
  for (const f of files) {
113
113
  if (p.mode === 'import-edge') {
114
114
  for (const { line, spec } of importSpecifiers(f.text)) {
@@ -238,7 +238,7 @@ export function validateDeletionRecord(rec) {
238
238
  return bad;
239
239
  }
240
240
  /** 第一批删除候选 = 产品码里 **0 入边引用** 且非入口形态 (机械派生, 不手写) */
241
- const ENTRY_SHAPE_RE = /(index\.ts$|cli-entry\.ts$|electron\.ts$|\.d\.ts$|server\.ts$|main\.ts$|route|command|register|setup|bootstrap|migration|types\.ts$|constants?\.ts$|config)/;
241
+ const ENTRY_SHAPE_RE = /(index\.ts$|cli-entry\.ts$|electron\.ts$|\.d\.ts$|server\.ts$|main\.ts$|daemon\.ts$|route|command|register|setup|bootstrap|migration|types\.ts$|constants?\.ts$|config)/;
242
242
  /**
243
243
  * **豁免规则 (2026-10-02)**: 冻结面自身的文件 (KERNEL_FILES 成员 —— `kernel/roster.ts` / `kernel/plan*.ts`)
244
244
  * **不算删除候选**。它们是台账/名册, 在"0 入边"口径下天然是孤岛, 每加一个台账就会让候选集变一次