@bolloon/bolloon-agent 0.6.4 → 0.6.6

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 (173) 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/intent-classifier.js +1 -1
  9. package/dist/agents/iteration-budget.js +12 -0
  10. package/dist/agents/knowledge-organizer.js +0 -2
  11. package/dist/agents/lesson-to-skill.js +0 -1
  12. package/dist/agents/loop-review.js +2 -2
  13. package/dist/agents/memory-store.js +359 -0
  14. package/dist/agents/opportunity-inbox.js +147 -0
  15. package/dist/agents/opportunity-match.js +48 -7
  16. package/dist/agents/opportunity-web-search.js +222 -0
  17. package/dist/agents/p2p-chat-tools.js +1 -2
  18. package/dist/agents/parse-tool-call.js +9 -22
  19. package/dist/agents/pi-harness.js +1 -2
  20. package/dist/agents/pi-sdk-tools.js +180 -5
  21. package/dist/agents/pi-sdk.js +143 -9
  22. package/dist/agents/process-runner.js +28 -9
  23. package/dist/agents/react-loop.js +9 -17
  24. package/dist/agents/runner-resolver.js +4 -3
  25. package/dist/agents/skill-loader.js +22 -15
  26. package/dist/agents/skills-manager.js +1 -25
  27. package/dist/agents/workflow-pivot-loop.js +152 -61
  28. package/dist/agents/world-watcher.js +30 -1
  29. package/dist/bootstrap/context-collector.js +2 -4
  30. package/dist/bootstrap/context-hierarchy.js +13 -1
  31. package/dist/cli/ink-app.js +44 -17
  32. package/dist/cli/intent-command.js +22 -12
  33. package/dist/cli/loading-tui.js +8 -2
  34. package/dist/cli/log-gate.js +40 -8
  35. package/dist/cli/mention-data.js +1 -0
  36. package/dist/cli/reply-hygiene.js +22 -0
  37. package/dist/cli/trace-line.js +61 -2
  38. package/dist/electron-build/agents/agent-gateway.js.map +1 -1
  39. package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
  40. package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
  41. package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
  42. package/dist/electron-build/agents/contacts/chain.js.map +1 -1
  43. package/dist/electron-build/agents/contacts/human-directory.js +204 -0
  44. package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
  45. package/dist/electron-build/agents/contacts/policy.js.map +1 -1
  46. package/dist/electron-build/agents/contacts/tools.js +112 -0
  47. package/dist/electron-build/agents/contacts/tools.js.map +1 -1
  48. package/dist/electron-build/agents/experience-review.js +0 -3
  49. package/dist/electron-build/agents/experience-review.js.map +1 -1
  50. package/dist/electron-build/agents/external-events.js.map +1 -1
  51. package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
  52. package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
  53. package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
  54. package/dist/electron-build/agents/goal-resume.js +0 -4
  55. package/dist/electron-build/agents/goal-resume.js.map +1 -1
  56. package/dist/electron-build/agents/intent-classifier.js +1 -1
  57. package/dist/electron-build/agents/lesson-to-skill.js +0 -1
  58. package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
  59. package/dist/electron-build/agents/loop-review.js +2 -2
  60. package/dist/electron-build/agents/loop-review.js.map +1 -1
  61. package/dist/electron-build/agents/memory-recall.js.map +1 -1
  62. package/dist/electron-build/agents/memory-store.js +404 -0
  63. package/dist/electron-build/agents/memory-store.js.map +1 -0
  64. package/dist/electron-build/agents/parse-tool-call.js +9 -22
  65. package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
  66. package/dist/electron-build/agents/pi-harness.js +1 -2
  67. package/dist/electron-build/agents/pi-harness.js.map +1 -1
  68. package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
  69. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  70. package/dist/electron-build/agents/pi-sdk.js +143 -9
  71. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  72. package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
  73. package/dist/electron-build/agents/process-runner.js +28 -9
  74. package/dist/electron-build/agents/process-runner.js.map +1 -1
  75. package/dist/electron-build/agents/runner-resolver.js +4 -3
  76. package/dist/electron-build/agents/runner-resolver.js.map +1 -1
  77. package/dist/electron-build/agents/skill-loader.js +22 -16
  78. package/dist/electron-build/agents/skill-loader.js.map +1 -1
  79. package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
  80. package/dist/electron-build/agents/skills-manager.js +0 -24
  81. package/dist/electron-build/agents/skills-manager.js.map +1 -1
  82. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  83. package/dist/electron-build/agents/workflow-engine.js.map +1 -1
  84. package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
  85. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  86. package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
  87. package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
  88. package/dist/electron-build/bootstrap/context-collector.js +2 -4
  89. package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
  90. package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
  91. package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
  92. package/dist/electron-build/electron-preload.js.map +1 -1
  93. package/dist/electron-build/external-engines/delegate.js.map +1 -1
  94. package/dist/electron-build/kernel/channel-actor.js +31 -2
  95. package/dist/electron-build/kernel/channel-actor.js.map +1 -1
  96. package/dist/electron-build/kernel/model-runtime.js +8 -1
  97. package/dist/electron-build/kernel/model-runtime.js.map +1 -1
  98. package/dist/electron-build/llm/config-store.js +20 -0
  99. package/dist/electron-build/llm/config-store.js.map +1 -1
  100. package/dist/electron-build/llm/embedding-adapter.js +75 -0
  101. package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
  102. package/dist/electron-build/llm/pi-ai.js +96 -35
  103. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  104. package/dist/electron-build/network/agent-network.js.map +1 -1
  105. package/dist/electron-build/orbitdb/memory-sync.js +76 -0
  106. package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
  107. package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
  108. package/dist/electron-build/security/tool-gate.js +2 -0
  109. package/dist/electron-build/security/tool-gate.js.map +1 -1
  110. package/dist/electron-build/setup/setup-store.js +34 -6
  111. package/dist/electron-build/setup/setup-store.js.map +1 -1
  112. package/dist/electron-build/social/heartbeat.js +18 -5
  113. package/dist/electron-build/social/heartbeat.js.map +1 -1
  114. package/dist/electron-build/utils/update-manager.js.map +1 -1
  115. package/dist/index.js +250 -12
  116. package/dist/ios/bolloon-web.json +3 -3
  117. package/dist/ios/client.js +150 -113
  118. package/dist/ios/icons/icon-1400x1400.png +0 -0
  119. package/dist/ios/icons/icon-1400x1400.webp +0 -0
  120. package/dist/ios/index.html +106 -107
  121. package/dist/ios/mobile-agent.js +346 -45
  122. package/dist/ios/mobile-core.js +7303 -4590
  123. package/dist/ios/mobile-harness.js +1004 -0
  124. package/dist/ios/mobile-orbitdb.js +194 -0
  125. package/dist/ios/mobile-persona.js +80 -0
  126. package/dist/ios/mobile-privacy.js +2 -3
  127. package/dist/ios/mobile.css +286 -21
  128. package/dist/ios/mobile.html +106 -107
  129. package/dist/ios/mobile.js +300 -132
  130. package/dist/ios/routes-world.js +58 -1
  131. package/dist/ios/server.js +3 -1
  132. package/dist/ios/style.css +49 -2
  133. package/dist/ios/sw.js +1 -1
  134. package/dist/ios/ui/message-renderer.js +4 -18
  135. package/dist/ios/ui/step-timeline.js +14 -1
  136. package/dist/kernel/channel-actor.js +30 -2
  137. package/dist/kernel/gate-scan.js +1 -1
  138. package/dist/kernel/index.js +33 -0
  139. package/dist/kernel/model-runtime.js +8 -1
  140. package/dist/kernel/plan.js +3 -3
  141. package/dist/kernel/roster.js +3 -3
  142. package/dist/llm/config-store.js +20 -0
  143. package/dist/llm/embedding-adapter.js +69 -0
  144. package/dist/llm/pi-ai.js +94 -34
  145. package/dist/orbitdb/memory-sync.js +70 -0
  146. package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
  147. package/dist/security/tool-gate.js +2 -0
  148. package/dist/setup/setup-store.js +34 -6
  149. package/dist/social/heartbeat.js +18 -5
  150. package/dist/web/bolloon-web.json +3 -3
  151. package/dist/web/client.js +150 -113
  152. package/dist/web/icons/icon-1400x1400.png +0 -0
  153. package/dist/web/icons/icon-1400x1400.webp +0 -0
  154. package/dist/web/index.html +52 -59
  155. package/dist/web/mobile-agent.js +351 -45
  156. package/dist/web/mobile-contacts.js +0 -4
  157. package/dist/web/mobile-core.js +7430 -4590
  158. package/dist/web/mobile-harness.js +1004 -0
  159. package/dist/web/mobile-orbitdb.js +194 -0
  160. package/dist/web/mobile-persona.js +80 -0
  161. package/dist/web/mobile-privacy.js +2 -3
  162. package/dist/web/mobile.css +286 -21
  163. package/dist/web/mobile.html +121 -107
  164. package/dist/web/mobile.js +409 -136
  165. package/dist/web/routes-contacts.js +94 -1
  166. package/dist/web/routes-world.js +58 -1
  167. package/dist/web/server.js +4 -2
  168. package/dist/web/style.css +49 -2
  169. package/dist/web/sw.js +1 -1
  170. package/dist/web/ui/message-renderer.js +4 -18
  171. package/dist/web/ui/step-timeline.js +14 -1
  172. package/package.json +3 -2
  173. 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
  }
@@ -2,6 +2,8 @@ import { setIntent, listIntents, getIntent, removeIntent } from '../agents/inten
2
2
  import { scanOpportunities, recordFeedback, readObservations } from '../agents/opportunity-match.js';
3
3
  import { readProfile, setProfile } from '../agents/world-profile.js';
4
4
  import { worldWatcherStatus } from '../agents/world-watcher.js';
5
+ import { deliverOpportunity, listInboxOpportunities, INBOX_PROTOCOL } from '../agents/opportunity-inbox.js';
6
+ import { searchOpportunitiesForTopic, listSearchOpportunities } from '../agents/opportunity-web-search.js';
5
7
  function json(res, status, body) {
6
8
  res.status(status).json(body);
7
9
  }
@@ -10,7 +12,10 @@ function bodyOf(req) {
10
12
  }
11
13
  /** 限制输入形状: 只收明确字段, 不整个吞前端对象 */
12
14
  function intentInputFromBody(body) {
13
- 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 : {};
14
19
  const out = {};
15
20
  if (typeof b.text === 'string' && b.text.trim())
16
21
  out.text = b.text.trim();
@@ -102,4 +107,56 @@ export function registerWorldRoutes(app) {
102
107
  const observations = await readObservations(20);
103
108
  json(res, 200, { ok: true, watcher: status, recentObservations: observations });
104
109
  });
110
+ // ── 机会信箱 (2026-10-05, leo: 别人主动投来的机会 — 真实可信来源) ───────────
111
+ // 外部 agent 用 DIAP Ed25519 身份签名投递机会声明; 验签通过才入库并进世界流。
112
+ // 协议: POST { protocol, title, summary, refs[], provider:{did}, issuedAt } + header X-Signature
113
+ // signature = ed25519Sign(provider 私钥, canonicalize(上述 payload)) — 与 x402 同源 canonicalize
114
+ app.get('/api/world/inbox', async (_req, res) => {
115
+ const list = await listInboxOpportunities();
116
+ json(res, 200, { ok: true, count: list.length, opportunities: list });
117
+ });
118
+ app.post('/api/world/inbox', async (req, res) => {
119
+ const b = bodyOf(req);
120
+ const sig = typeof req.headers['x-signature'] === 'string' ? req.headers['x-signature'] : '';
121
+ const payload = {
122
+ protocol: typeof b.protocol === 'string' ? b.protocol : '',
123
+ title: typeof b.title === 'string' ? b.title : '',
124
+ summary: typeof b.summary === 'string' ? b.summary : '',
125
+ refs: Array.isArray(b.refs) ? b.refs.filter((r) => typeof r === 'string') : [],
126
+ provider: {
127
+ did: typeof b.provider?.did === 'string' ? b.provider.did : '',
128
+ name: typeof b.provider?.name === 'string' ? b.provider.name : undefined,
129
+ },
130
+ issuedAt: typeof b.issuedAt === 'string' ? b.issuedAt : new Date().toISOString(),
131
+ };
132
+ const r = await deliverOpportunity(payload, sig);
133
+ json(res, r.ok ? 200 : 400, { ok: r.ok, opportunity: r.opportunity, error: r.error });
134
+ });
135
+ // 协议说明 (外部 agent 对接用 — 怎么构造 payload / 怎么签名)
136
+ app.get('/api/world/inbox/protocol', async (_req, res) => {
137
+ json(res, 200, {
138
+ ok: true,
139
+ protocol: INBOX_PROTOCOL,
140
+ description: '外部 agent 主动投递机会到信箱: POST /api/world/inbox, header X-Signature = Ed25519(provider 私钥, canonicalize(payload))',
141
+ payloadFields: ['protocol', 'title', 'summary', 'refs[]', 'provider.did', 'provider.name?', 'issuedAt'],
142
+ signature: 'ed25519Sign(providerPrivateKey, canonicalize({protocol,title,summary,refs,provider,issuedAt}))',
143
+ verification: '本机解析 provider.did → Ed25519 公钥 → 验签; 通过才入库 (verified) 并进世界流, 否则拒绝',
144
+ });
145
+ });
146
+ // ── AI 主动搜索 (2026-10-05, leo: 系统要主动上网搜机会, 非只被动响应) ─────────
147
+ // POST /api/world/search [{topic}] → 按 topic 搜机会落 world/search/ 并进世界流
148
+ app.post('/api/world/search', async (req, res) => {
149
+ const b = bodyOf(req);
150
+ const topic = typeof b.topic === 'string' && b.topic.trim() ? b.topic.trim()
151
+ : (typeof b.text === 'string' && b.text.trim() ? b.text.trim() : '');
152
+ if (!topic)
153
+ return json(res, 400, { ok: false, error: '缺 topic (要搜什么机会)' });
154
+ const r = await searchOpportunitiesForTopic(topic, { limit: 6 });
155
+ json(res, r.ok ? 200 : 400, { ok: r.ok, topic, added: r.added, total: r.total, opportunities: r.results, error: r.error });
156
+ });
157
+ // GET /api/world/search → 已搜到的机会 (最近在前)
158
+ app.get('/api/world/search', async (_req, res) => {
159
+ const list = await listSearchOpportunities();
160
+ json(res, 200, { ok: true, count: list.length, opportunities: list });
161
+ });
105
162
  }
@@ -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
  });
@@ -4941,7 +4942,7 @@ fetchState();
4941
4942
  // (与邮箱串行一致), web 不再自己持 abortController。取到 agent 后立刻开户 (见下面 getAgentForChannel 之后)。
4942
4943
  let abortSignal;
4943
4944
  // 2026-07-04: pivot loop safety net — 防止 LLM hang (minimax M3
4944
- // 偶尔反复 think 不输出 <final gen>, pivot 连 5 次无进展时会 hang 在
4945
+ // 偶尔反复 think 不输出结果, pivot 连 5 次无进展时会 hang 在
4945
4946
  // quality 评估). setTimeout 让 LLM 客户端收 signal 主动 break.
4946
4947
  // 2026-07-06: 30s 太短 — minimax M3 单次 LLM 约 10s, pivot 默认 moderate profile 30 iter
4947
4948
  // 留 ~300s. 之前 30s 自动 abort 时, 一次"你好"调用就被截断, 用户看到 ❌ 循环异常 + 空内容.
@@ -5169,7 +5170,7 @@ fetchState();
5169
5170
  // 2026-06-10: 注入 skills 列表 (本机 ~/.bolloon/skills/ 下所有 skills)
5170
5171
  // 让 LLM 知道有哪些 skill 可用, 在回复中提示用户
5171
5172
  try {
5172
- const { loadSkillsFromPaths, defaultSkillPaths, describeSkill } = await import('../agents/skill-loader.js');
5173
+ const { loadSkillsFromPaths, defaultSkillPaths } = await import('../agents/skill-loader.js');
5173
5174
  const paths = defaultSkillPaths();
5174
5175
  const skills = await loadSkillsFromPaths(paths);
5175
5176
  if (skills.length > 0) {
@@ -6901,6 +6902,7 @@ fetchState();
6901
6902
  output: event.output,
6902
6903
  error: event.error,
6903
6904
  args: event.args,
6905
+ durationMs: event.durationMs, // 2026-10-12: 命令执行时间读秒
6904
6906
  }, channelId);
6905
6907
  }
6906
6908
  else if (event.type === 'error') {
@@ -4,6 +4,12 @@
4
4
  box-sizing: border-box;
5
5
  }
6
6
 
7
+ /* 2026-10-05 (leo): hidden 属性必须真隐藏 —— 之前 .messages{display:flex} 覆盖了
8
+ UA 的 [hidden]{display:none}, 导致世界栏下面一直显示聊天记录 (污染)。 */
9
+ [hidden] {
10
+ display: none !important;
11
+ }
12
+
7
13
  :root {
8
14
  --bg: #1a1a18;
9
15
  --bg-sidebar: #222220;
@@ -120,13 +126,20 @@ body {
120
126
  flex-shrink: 0;
121
127
  transition: var(--transition);
122
128
  transform: translateX(0);
123
- position: relative;
129
+ /* 2026-10-06: 改为 fixed 覆盖 — sidebar 收起时绝对定位脱离流, relative 会被挤成 0 宽,
130
+ 恢复条必须固定在屏幕左侧才能被看到/点到 */
131
+ position: fixed;
132
+ left: 0;
133
+ top: 0;
134
+ height: 100%;
124
135
  z-index: 99;
125
136
  }
126
137
 
127
138
  .sidebar-collapsed.hidden {
128
139
  transform: translateX(calc(-1 * var(--sidebar-collapsed)));
129
- position: absolute;
140
+ position: fixed;
141
+ left: 0;
142
+ top: 0;
130
143
  height: 100%;
131
144
  }
132
145
 
@@ -1858,6 +1871,40 @@ body {
1858
1871
  font-weight: normal;
1859
1872
  color: var(--text-muted);
1860
1873
  }
1874
+
1875
+ /* 2026-10-05 (leo): 世界/聊天切换放进原导航栏 (header) 中间 — 与钱包/配置同栏 */
1876
+ .header-tabs {
1877
+ display: flex;
1878
+ gap: 2px;
1879
+ margin-left: 18px;
1880
+ padding: 3px;
1881
+ background: var(--bg-hover);
1882
+ border-radius: 8px;
1883
+ flex-shrink: 0;
1884
+ }
1885
+ .header-tab {
1886
+ padding: 7px 22px;
1887
+ background: transparent;
1888
+ border: none;
1889
+ border-radius: 6px;
1890
+ color: var(--text-secondary);
1891
+ font-size: 13px;
1892
+ font-weight: 600;
1893
+ cursor: pointer;
1894
+ transition: var(--transition);
1895
+ display: flex;
1896
+ align-items: center;
1897
+ gap: 5px;
1898
+ white-space: nowrap;
1899
+ }
1900
+ .header-tab:hover {
1901
+ color: var(--text-primary);
1902
+ }
1903
+ .header-tab.active {
1904
+ background: var(--bg-main);
1905
+ color: var(--accent-hover);
1906
+ box-shadow: 0 1px 3px rgba(0,0,0,0.3);
1907
+ }
1861
1908
  .judgment-status-bar {
1862
1909
  display: flex;
1863
1910
  gap: 6px;
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.6.4';
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",
@@ -59,7 +59,8 @@
59
59
  "ios:release": "bash scripts/ios-release.sh"
60
60
  },
61
61
  "workspaces": [
62
- "src/constraint-runtime"
62
+ "src/constraint-runtime",
63
+ "src/kernel"
63
64
  ],
64
65
  "dependencies": {
65
66
  "@a2ui/react": "^0.11.0",
@@ -14,7 +14,7 @@ async function main() {
14
14
 
15
15
  // 重要: 不能 rm -rf 整个 dist/web/, 否则会删掉 build:main 编译出来的
16
16
  // dist/web/server.js. 只清理 web 静态资源, 保留 server.js.
17
- for (const f of ['index.html', 'api-config.html', 'explorer.html', 'style.css', 'client.js', 'chain-explorer.js', 'mobile.html', 'mobile.css', 'mobile.js', 'mobile-core.js', 'components']) {
17
+ for (const f of ['index.html', 'api-config.html', 'explorer.html', 'style.css', 'client.js', 'chain-explorer.js', 'mobile.html', 'mobile.css', 'mobile.js', 'mobile-core.js', 'mobile-persona.js', 'components']) {
18
18
  await fs.rm(path.join(DIST_WEB, f), { recursive: true, force: true });
19
19
  }
20
20
  await fs.mkdir(DIST_WEB, { recursive: true });
@@ -115,6 +115,21 @@ async function main() {
115
115
  bundle: true,
116
116
  });
117
117
 
118
+ // 2026-10-09: mobile.js 动态 import('./mobile-persona.js')。
119
+ // 这是手机端可直接运行 Agent 的配置依赖,必须随 dist/web 一起生成,
120
+ // 否则 WebView 会静默 404,性格页和 Web Agent 都会失效。
121
+ console.log('[build-web] 编译 mobile-persona.ts...');
122
+ await esbuild.build({
123
+ entryPoints: [path.join(ROOT, 'src/web/mobile-persona.ts')],
124
+ outfile: path.join(DIST_WEB, 'mobile-persona.js'),
125
+ format: 'esm',
126
+ target: 'es2022',
127
+ platform: 'browser',
128
+ minify: false,
129
+ bundle: true,
130
+ charset: 'utf8',
131
+ });
132
+
118
133
  // 2026-08-12: 编译 A2UI 前端渲染器 (a2ui-client.tsx → a2ui-client.js)
119
134
  // react + @a2ui/react + @a2ui/web_core 全部打包进 bundle (手机端 webview 无 CDN)
120
135
  console.log('[build-web] 编译 a2ui-client.tsx...');