@bolloon/bolloon-agent 0.4.24 → 0.4.25

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 (36) hide show
  1. package/dist/agents/execution-supervisor.js +391 -0
  2. package/dist/agents/goal-store.js +451 -0
  3. package/dist/agents/pi-harness.js +263 -0
  4. package/dist/agents/pi-sdk.js +568 -126
  5. package/dist/agents/run-store.js +772 -0
  6. package/dist/agents/runner-resolver.js +225 -0
  7. package/dist/agents/skills-manager.js +435 -0
  8. package/dist/agents/supervisor-host.js +249 -0
  9. package/dist/cron/tick-lock.js +1 -1
  10. package/dist/index.js +428 -22
  11. package/dist/ios/agent-delegate-server.js +58 -12
  12. package/dist/ios/icons/icon-1024x1024.png +0 -0
  13. package/dist/ios/icons/icon-1024x1024.webp +0 -0
  14. package/dist/ios/icons/icon-216x216.png +0 -0
  15. package/dist/ios/icons/icon-216x216.webp +0 -0
  16. package/dist/ios/index.html +21 -1
  17. package/dist/ios/manifest.json +1 -1
  18. package/dist/ios/mobile-agent.js +195 -1
  19. package/dist/ios/mobile-core.js +24876 -24723
  20. package/dist/ios/mobile.css +15 -0
  21. package/dist/ios/mobile.html +21 -1
  22. package/dist/ios/mobile.js +143 -0
  23. package/dist/ios/server.js +51 -4
  24. package/dist/web/icons/icon-1024x1024.png +0 -0
  25. package/dist/web/icons/icon-1024x1024.webp +0 -0
  26. package/dist/web/icons/icon-216x216.png +0 -0
  27. package/dist/web/icons/icon-216x216.webp +0 -0
  28. package/dist/web/manifest.json +1 -1
  29. package/dist/web/mobile-agent.js +2 -2
  30. package/dist/web/mobile-core.js +24884 -24726
  31. package/dist/web/mobile-privacy.js +185 -0
  32. package/dist/web/mobile.css +15 -0
  33. package/dist/web/mobile.html +21 -1
  34. package/dist/web/mobile.js +179 -6
  35. package/dist/web/server.js +386 -0
  36. package/package.json +2 -2
@@ -688,3 +688,18 @@ body {
688
688
  color: var(--bg);
689
689
  font-weight: 600;
690
690
  }
691
+
692
+ /* —— 隐私政策 (2026-09-16) —— */
693
+ .privacy-link {
694
+ display: block; margin: 6px 0 2px; text-align: center;
695
+ color: var(--accent); font-size: 13px; text-decoration: none;
696
+ }
697
+ .privacy-link:active { opacity: .7; }
698
+ .policy-body {
699
+ flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
700
+ padding: 14px 16px calc(40px + env(safe-area-inset-bottom));
701
+ }
702
+ .policy-body h3 { font-size: 14px; font-weight: 600; color: var(--text); margin: 18px 0 6px; }
703
+ .policy-body p { font-size: 13px; line-height: 1.75; color: var(--text-secondary); margin: 0 0 10px; }
704
+ .policy-body a { color: var(--accent); word-break: break-all; }
705
+ .policy-meta { font-size: 12px; color: var(--text-muted); }
@@ -43,7 +43,7 @@
43
43
  </header>
44
44
 
45
45
  <!-- 主页面: 双频滑动卡片 -->
46
- <main class="page-container" id="page-main" data-tab="main">
46
+ <main class="page-container" id="page-main" data-tab="main" hidden>
47
47
  <!-- 水平滑动卡片轨道 -->
48
48
  <div class="card-carousel" id="card-carousel">
49
49
  <div class="card-track" id="card-track">
@@ -139,6 +139,26 @@
139
139
  <button class="create-session-btn" id="btn-create-session" hidden>+ 创建新会话</button>
140
140
 
141
141
  <!-- 创建智能体: 底部滑入加载 sheet -->
142
+ <!-- 2026-09-16: 首启隐私同意门 — 上架要求: 用户同意前不读取本机数据 / 不连网 / 不申请任何权限 -->
143
+ <div class="sheet" id="privacy-gate" hidden>
144
+ <div class="sheet-inner">
145
+ <div class="sheet-title" id="privacy-gate-title">隐私政策与权限说明</div>
146
+ <div class="sheet-text" id="privacy-gate-body"></div>
147
+ <a class="privacy-link" href="#" id="privacy-gate-link">阅读完整隐私政策 ›</a>
148
+ <button class="sheet-choice" id="privacy-agree">同意并继续</button>
149
+ <button class="sheet-choice sheet-cancel" id="privacy-decline">不同意</button>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- 2026-09-16: 应用内隐私政策 (全屏, 离线可用; 权威版 = bolloon.cn/privacy.html) -->
154
+ <div class="chat-page" id="policy-page" hidden>
155
+ <div class="chat-topbar">
156
+ <button class="icon-btn" id="policy-back">←</button>
157
+ <div style="flex:1;font-weight:600">隐私政策</div>
158
+ </div>
159
+ <div class="policy-body" id="policy-body"></div>
160
+ </div>
161
+
142
162
  <div class="sheet" id="create-sheet" hidden>
143
163
  <div class="sheet-inner">
144
164
  <div class="spinner"></div>
@@ -1717,6 +1717,9 @@
1717
1717
  <div class="conv-item" id="settings-helia"><span class="list-icon">${ICONS.globe}</span><span>本机 IPFS 节点</span><span class="list-arrow">›</span></div>
1718
1718
  <div class="conv-item" id="settings-selfcard"><span class="list-icon">${ICONS.chip}</span><span id="selfcard-text">显示本机卡片: 开</span></div>
1719
1719
  <div class="conv-item" id="settings-did"><span class="list-icon">${ICONS.idcard}</span><span>DID</span></div>
1720
+ <div class="conv-item" id="settings-privacy"><span class="list-icon">${ICONS.chip}</span><span>隐私政策与个人信息</span><span class="list-arrow">›</span></div>
1721
+ <div class="conv-item" id="settings-wipe"><span class="list-icon">${ICONS.trash}</span><span style="flex:1;min-width:0"><span style="display:block">清除本机数据(注销)</span><span class="conv-preview" style="display:block">删除本机身份、智能体、会话与钱包</span></span><span class="list-arrow">›</span></div>
1722
+ <div class="conv-item" id="settings-filing"><span class="list-icon">${ICONS.idcard}</span><span id="filing-text">APP 备案号:备案办理中</span></div>
1720
1723
  </div>`;
1721
1724
  document.body.appendChild(page);
1722
1725
  applyTheme(resolveThemePref(), false);
@@ -1757,6 +1760,20 @@
1757
1760
  });
1758
1761
  }
1759
1762
  $('#settings-desktop').addEventListener('click', openDesktopSync);
1763
+ // 隐私政策 (双入口之一: 设置页; 另一个是首启同意门)
1764
+ const privacyRow = $('#settings-privacy');
1765
+ if (privacyRow) privacyRow.addEventListener('click', openPrivacyPolicy);
1766
+ // 注销: 清除本机数据
1767
+ const wipeRow = $('#settings-wipe');
1768
+ if (wipeRow) wipeRow.addEventListener('click', () => { void wipeLocalDataFlow(); });
1769
+ // 备案号展示 (未备案时如实显示"备案办理中", 不伪造编号)
1770
+ const filingRow = $('#settings-filing');
1771
+ if (filingRow) {
1772
+ const ft = $('#filing-text');
1773
+ const ptxt = privacyCore() && privacyCore().filingText ? privacyCore().filingText() : '';
1774
+ if (ft && ptxt) ft.textContent = ptxt;
1775
+ filingRow.addEventListener('click', () => { const pp = privacyCore(); alert((pp && pp.filingText ? pp.filingText() : 'APP 备案号:备案办理中') + '\n\n备案信息以工信部备案系统公示为准。'); });
1776
+ }
1760
1777
  // 本机卡片显示开关 (移除后从这里恢复)
1761
1778
  const drawSelfCardToggle = () => {
1762
1779
  const on = (() => { try { return localStorage.getItem(SELF_CARD_HIDDEN_KEY) !== '1'; } catch { return true; } })();
@@ -2842,7 +2859,133 @@
2842
2859
  });
2843
2860
  }
2844
2861
 
2862
+ // ============ 隐私合规 (2026-09-16): 同意门 / 政策页 / 注销 ============
2863
+ // 上架要求: 首次启动必须先展示隐私政策; 用户同意前不得读取本机数据、不得连网、不得申请任何权限。
2864
+ // 所以 init() 只做一件事 —— 决定"先弹门"还是"进应用"; 真正的初始化在 initApp()。
2865
+
2866
+ function privacyCore() {
2867
+ return (window.BolloonCore && window.BolloonCore.privacy) || null;
2868
+ }
2869
+
2870
+ function privacyNeedsConsent() {
2871
+ const p = privacyCore();
2872
+ if (!p || typeof p.needsConsent !== 'function') return true; // 内核未就绪 → 按"需要同意"处理
2873
+ try { return !!p.needsConsent(); } catch (e) { return true; }
2874
+ }
2875
+
2876
+ function escHtml(v) {
2877
+ return String(v == null ? '' : v)
2878
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
2879
+ .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
2880
+ }
2881
+
2882
+ /** 政策摘要 HTML (要素来自内核常量, 单测锁了必填项) */
2883
+ function privacySummaryHtml() {
2884
+ const p = privacyCore();
2885
+ const items = (p && p.summary) || [];
2886
+ return items.map((it) => `<h3>${escHtml(it.title)}</h3><p>${escHtml(it.body)}</p>`).join('');
2887
+ }
2888
+
2889
+ function showPrivacyGate() {
2890
+ const p = privacyCore();
2891
+ const t = (p && p.consentText) || {};
2892
+ const gate = $('#privacy-gate');
2893
+ if (!gate) return;
2894
+ const titleEl = $('#privacy-gate-title'), bodyEl = $('#privacy-gate-body');
2895
+ if (titleEl && t.title) titleEl.textContent = t.title;
2896
+ if (bodyEl) bodyEl.textContent = t.intro || '使用前请先阅读并同意隐私政策。';
2897
+ const linkEl = $('#privacy-gate-link');
2898
+ if (linkEl && t.policyLink) linkEl.textContent = t.policyLink + ' ›';
2899
+ const agreeEl = $('#privacy-agree'), declineEl = $('#privacy-decline');
2900
+ if (agreeEl && t.agree) agreeEl.textContent = t.agree;
2901
+ if (declineEl && t.decline) declineEl.textContent = t.decline;
2902
+ if (linkEl) linkEl.onclick = (e) => { e.preventDefault(); openPrivacyPolicy(); };
2903
+ if (agreeEl) agreeEl.onclick = () => {
2904
+ const ok = p && typeof p.grant === 'function' ? p.grant() : false;
2905
+ if (!ok) { /* 本机写不进去(隐私模式/满盘): 不假装同意, 下次启动会再问 */ }
2906
+ hideSheet('#privacy-gate');
2907
+ initApp();
2908
+ };
2909
+ if (declineEl) declineEl.onclick = () => showPrivacyDeclined();
2910
+ gate.hidden = false;
2911
+ }
2912
+
2913
+ /** 不同意 → 停在说明页: 不初始化功能、不收集任何东西, 但也不强制退出应用 */
2914
+ function showPrivacyDeclined() {
2915
+ const p = privacyCore();
2916
+ const t = (p && p.consentText) || {};
2917
+ const gate = $('#privacy-gate');
2918
+ if (!gate) return;
2919
+ const titleEl = $('#privacy-gate-title'), bodyEl = $('#privacy-gate-body');
2920
+ if (titleEl) titleEl.textContent = t.declinedTitle || '未同意';
2921
+ if (bodyEl) bodyEl.textContent = t.declinedBody || '未同意隐私政策时应用不会收集任何信息。';
2922
+ const linkEl = $('#privacy-gate-link');
2923
+ if (linkEl) { linkEl.textContent = (t.declinedReread || '重新阅读') + ' ›'; linkEl.onclick = (e) => { e.preventDefault(); showPrivacyGate(); }; }
2924
+ const agreeEl = $('#privacy-agree'), declineEl = $('#privacy-decline');
2925
+ if (agreeEl) { agreeEl.textContent = t.agree || '同意并继续'; agreeEl.onclick = () => { const ok = p && p.grant ? p.grant() : false; if (!ok) {} hideSheet('#privacy-gate'); initApp(); }; }
2926
+ if (declineEl) {
2927
+ declineEl.textContent = t.declinedExit || '退出应用';
2928
+ declineEl.onclick = () => {
2929
+ const cap = window.Capacitor;
2930
+ if (cap && cap.Plugins && cap.Plugins.App && cap.Plugins.App.exitApp) { try { cap.Plugins.App.exitApp(); return; } catch (e) {} }
2931
+ alert('请手动退出应用(返回键 / 上滑关闭)。未同意隐私政策期间应用不会收集任何信息。');
2932
+ };
2933
+ }
2934
+ gate.hidden = false;
2935
+ }
2936
+
2937
+ /** 应用内完整政策 (全屏, 离线可用)。权威版永远是 bolloon.cn/privacy.html (商店表单填的就是它) */
2938
+ function openPrivacyPolicy() {
2939
+ const p = privacyCore();
2940
+ const page = $('#policy-page');
2941
+ const bodyEl = $('#policy-body');
2942
+ if (!page || !bodyEl) return;
2943
+ const t = (p && p.consentText) || {};
2944
+ const url = (p && p.policyUrl) || 'https://bolloon.cn/privacy.html';
2945
+ const contact = (p && p.contact) || '';
2946
+ const filing = p && typeof p.filingText === 'function' ? p.filingText() : '';
2947
+ bodyEl.innerHTML = `
2948
+ <p class="policy-meta">${escHtml(t.intro || '')}</p>
2949
+ ${privacySummaryHtml()}
2950
+ <h3>完整政策与备案信息</h3>
2951
+ <p>在线完整版:<a href="${escHtml(url)}" target="_blank" rel="noopener">${escHtml(url)}</a></p>
2952
+ <p>${escHtml(filing)}</p>
2953
+ <p class="policy-meta">联系方式:${escHtml(contact)}(承诺 7 个工作日内答复)</p>
2954
+ <p class="policy-meta">本页内容与在线版本一致;如两者不一致,以在线版本为准。</p>`;
2955
+ const back = $('#policy-back');
2956
+ if (back) back.onclick = () => { page.hidden = true; };
2957
+ page.hidden = false;
2958
+ }
2959
+
2960
+ /** 注销: 清除本机全部数据 (身份 DID / 智能体 / 会话消息 / 支付 / 钱包) */
2961
+ async function wipeLocalDataFlow() {
2962
+ if (!confirm('确定清除本机数据(注销)?\n\n将删除本机上的:身份标识(DID)、智能体、会话与消息、钱包账本。\n已写入公开区块链的交易记录无法删除。\n\n此操作不可撤销。')) return;
2963
+ const p = privacyCore();
2964
+ if (!p || typeof p.wipe !== 'function') { alert('清除失败:本机内核未就绪,请重启应用后重试。'); return; }
2965
+ showToast('正在清除本机数据…');
2966
+ try {
2967
+ const r = await p.wipe();
2968
+ const failed = (r && r.failed) || [];
2969
+ const deleted = (r && r.deletedDatabases) || [];
2970
+ const cleared = (r && r.clearedStorageKeys) || [];
2971
+ if (failed.length) {
2972
+ alert(`部分数据未能清除(可重试):\n${failed.join('\n')}\n\n已清除 ${deleted.length} 个数据库 / ${cleared.length} 个本机键。`);
2973
+ } else {
2974
+ alert(`${p.wipeNotice || '已清除本机数据。'}\n\n(本次删除 ${deleted.length} 个数据库、${cleared.length} 个本机键)`);
2975
+ }
2976
+ location.reload(); // 回到首启状态 (同意记录保留, 不再重复弹门)
2977
+ } catch (e) {
2978
+ alert('清除失败:' + ((e && e.message) || e));
2979
+ }
2980
+ }
2981
+
2845
2982
  function init() {
2983
+ // 首启/政策版本变更 → 先弹同意门, 不做任何初始化 (不读本机数据 / 不连网 / 不申请权限)
2984
+ if (privacyNeedsConsent()) { showPrivacyGate(); return; }
2985
+ initApp();
2986
+ }
2987
+
2988
+ function initApp() {
2846
2989
  bindMenu();
2847
2990
  applyTheme(resolveThemePref(), false);
2848
2991
  switchTab('main');
@@ -3020,14 +3020,61 @@ ${goalDesc}
3020
3020
  // 2026-09-15: agent-delegate (manifest 协议 + agent_delegate) **启动即挂载**
3021
3021
  // 文档 bolloon-gateway-join.md 第 3 节要求 POST /api/agent/register 可用;
3022
3022
  // 之前只在 iroh 懒初始化 (/api/iroh/info 首次访问) 的收尾挂上 → 没触发时恒 404。
3023
+ //
3024
+ // 2026-09-15 (二修): 同时注入**真执行器** —— 被委派的一方不再是
3025
+ // `resultCid: mock-<ts>` 的假签收: 真的跑本地 agent session 出结果,
3026
+ // 并把结果存进 OrbitDB 得到真实内容寻址 CID; 无活跃 channel / 无 LLM 时如实报错。
3027
+ const delegateExecutor = async (req) => {
3028
+ try {
3029
+ const channels = await loadChannels();
3030
+ const active = (() => {
3031
+ try {
3032
+ const raw = fsSync.existsSync(path.join(process.env.HOME || '/tmp', '.bolloon', 'active-channel.json'))
3033
+ ? fsSync.readFileSync(path.join(process.env.HOME || '/tmp', '.bolloon', 'active-channel.json'), 'utf-8') : '';
3034
+ return raw ? JSON.parse(raw) : null;
3035
+ }
3036
+ catch {
3037
+ return null;
3038
+ }
3039
+ })();
3040
+ const ch = channels.find((c) => c.id === active?.channelId) || channels.find((c) => c.id === active?.id) || channels[0];
3041
+ if (!ch)
3042
+ return { ok: false, summary: 'no local channel to execute with', error: 'no-channel' };
3043
+ const agent = await getAgentForChannel(ch.id, ch.publicKey, ch.name, ch.didDocument);
3044
+ const task = [
3045
+ `【被委派任务 · 能力 ${req.capability}】`,
3046
+ req.instruction,
3047
+ req.docPath ? `\n资料路径: ${req.docPath}` : '',
3048
+ req.docContent ? `\n资料内容:\n${req.docContent}` : '',
3049
+ `\n(来自 agent ${req.fromAgentId || 'unknown'}),请直接给出可交付结果。`,
3050
+ ].join('');
3051
+ const out = await agent.prompt(task);
3052
+ const text = String(out || '').trim();
3053
+ if (!text || text.startsWith('❌') || text.startsWith('[AI 服务调用失败]')) {
3054
+ return { ok: false, summary: text.slice(0, 800) || '(空结果)', error: 'execution-failed' };
3055
+ }
3056
+ // 真结果 → 内容寻址存储 (CID), 失败不编造 CID, 只回摘要
3057
+ let cid;
3058
+ try {
3059
+ const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
3060
+ const rec = await getCIDDatabase().save({ agentId: req.targetAgentId, type: 'context', content: text, metadata: { capability: req.capability, fromAgentId: req.fromAgentId, kind: 'delegate-result' } });
3061
+ cid = rec?.id;
3062
+ }
3063
+ catch { /* 存不上就不给 CID */ }
3064
+ return { ok: true, summary: text.slice(0, 4000), resultCid: cid };
3065
+ }
3066
+ catch (e) {
3067
+ return { ok: false, summary: `delegate executor failed: ${String(e?.message || e).slice(0, 300)}`, error: 'executor-error' };
3068
+ }
3069
+ };
3023
3070
  let agentDelegateMounted = false;
3024
3071
  try {
3025
3072
  const delegateTransport = createIrohDelegateTransport({ verbose: true });
3026
3073
  // 注意: createAgentDelegateApp 内部声明的是绝对路径 (/api/agent/...),
3027
3074
  // 所以挂载时**不能**再带 '/api/agent' 前缀 (否则变成 /api/agent/api/agent/... 恒 404)
3028
- app.use(createAgentDelegateApp(delegateTransport));
3075
+ app.use(createAgentDelegateApp(delegateTransport, { execute: delegateExecutor }));
3029
3076
  agentDelegateMounted = true;
3030
- console.log('[agent-delegate] 已挂载到 /api/agent (启动即用: local-manifest / register / pick / delegate)');
3077
+ console.log('[agent-delegate] 已挂载到 /api/agent (启动即用: local-manifest / register / pick / delegate, 真执行器已注入)');
3031
3078
  }
3032
3079
  catch (e) {
3033
3080
  console.warn('[agent-delegate] 挂载失败 (非致命):', e?.message);
@@ -6588,10 +6635,10 @@ ${goalDesc}
6588
6635
  if (!agentDelegateMounted) {
6589
6636
  try {
6590
6637
  const delegateTransport = createIrohDelegateTransport({ verbose: true });
6591
- const delegateApp = createAgentDelegateApp(delegateTransport);
6638
+ const delegateApp = createAgentDelegateApp(delegateTransport, { execute: delegateExecutor });
6592
6639
  app.use(delegateApp);
6593
6640
  agentDelegateMounted = true;
6594
- console.log('[iroh API] agent-delegate app 已挂载到 /api/agent (补挂)');
6641
+ console.log('[iroh API] agent-delegate app 已挂载到 /api/agent (补挂, 真执行器已注入)');
6595
6642
  }
6596
6643
  catch (e) {
6597
6644
  console.error('[iroh API] 挂载 agent-delegate app 失败:', e);
Binary file
Binary file
@@ -2,7 +2,7 @@
2
2
  "name": "Bolloon Agent",
3
3
  "short_name": "Bolloon",
4
4
  "description": "去中心化智能体网络 — 手机端 (P2P / DID / 数字资源)",
5
- "id": "com.bolloon.agent.mobile",
5
+ "id": "com.hibs.bolloon",
6
6
  "start_url": "./mobile.html",
7
7
  "scope": "./",
8
8
  "display": "standalone",
@@ -555,13 +555,13 @@ export async function runPhoneAgent(goal) {
555
555
  const reply = await runLocalAgent(goal);
556
556
  return {
557
557
  ok: true, goal, result: reply, did: id.did, mode: 'fallback',
558
- hint: '当前是「手机本地规则」模式:这台手机上还没接原生执行能力 (iOS 暂不支持原生操控 App; Android 需开无障碍服务),所以只能用内置规则回复。连上电脑端后,任务可以交给电脑端 Agent 真正执行。',
558
+ hint: '当前是「手机本地规则」模式:这台手机上还没接原生执行能力,所以只能用内置规则回复。连上电脑端后,任务可以交给电脑端 Agent 真正执行。',
559
559
  };
560
560
  }
561
561
  catch (e) {
562
562
  return {
563
563
  ok: false, goal, error: String(e?.message || e).slice(0, 100), did: id.did, mode: native ? 'native' : 'fallback',
564
- hint: '任务没跑起来。常见原因:① 电脑端没在运行或不同网段 (设置 → 电脑端同步 里配置/测试) ② 没配 LLM API (设置 → API 配置) ③ 这台手机没接原生执行能力 (iOS 不支持原生操控, Android 需无障碍服务)。',
564
+ hint: '任务没跑起来。常见原因:① 电脑端没在运行或不同网段 (设置 → 电脑端同步 里配置/测试) ② 没配 LLM API (设置 → API 配置) ③ 这台手机没接原生执行能力。',
565
565
  };
566
566
  }
567
567
  }