@bolloon/bolloon-agent 0.4.23 → 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 (48) 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 +95 -18
  17. package/dist/ios/manifest.json +1 -1
  18. package/dist/ios/mobile-agent.js +244 -5
  19. package/dist/ios/mobile-chain.js +481 -0
  20. package/dist/ios/mobile-core.js +25177 -24664
  21. package/dist/ios/mobile-helia.js +683 -0
  22. package/dist/ios/mobile-ipfs.js +680 -0
  23. package/dist/ios/mobile-orbit.js +268 -0
  24. package/dist/ios/mobile-p2p.js +130 -1
  25. package/dist/ios/mobile-social.js +624 -0
  26. package/dist/ios/mobile-sync.js +193 -0
  27. package/dist/ios/mobile-trade.js +492 -0
  28. package/dist/ios/mobile-wallet.js +11 -0
  29. package/dist/ios/mobile.css +39 -0
  30. package/dist/ios/mobile.html +95 -18
  31. package/dist/ios/mobile.js +1054 -55
  32. package/dist/ios/routes-x402-info.js +194 -0
  33. package/dist/ios/server.js +350 -9
  34. package/dist/ios/sw.js +26 -2
  35. package/dist/web/agent-delegate-server.js +58 -12
  36. package/dist/web/icons/icon-1024x1024.png +0 -0
  37. package/dist/web/icons/icon-1024x1024.webp +0 -0
  38. package/dist/web/icons/icon-216x216.png +0 -0
  39. package/dist/web/icons/icon-216x216.webp +0 -0
  40. package/dist/web/manifest.json +1 -1
  41. package/dist/web/mobile-agent.js +197 -3
  42. package/dist/web/mobile-core.js +16417 -16090
  43. package/dist/web/mobile-privacy.js +185 -0
  44. package/dist/web/mobile.css +15 -0
  45. package/dist/web/mobile.html +21 -1
  46. package/dist/web/mobile.js +179 -6
  47. package/dist/web/server.js +437 -4
  48. package/package.json +2 -2
@@ -34,15 +34,16 @@
34
34
  <div id="app" class="app">
35
35
  <!-- 顶部导航 -->
36
36
  <header class="topbar">
37
+ <button class="icon-btn" id="btn-index" title="索引"><svg class="ico" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
37
38
  <div class="topbar-title" id="topbar-title">首页</div>
38
39
  <div class="topbar-actions" id="topbar-actions">
39
- <button class="icon-btn" id="btn-refresh" title="刷新"><svg class="ico" viewBox="0 0 24 24"><path d="M20 12a8 8 0 1 1-2.3-5.6"/><path d="M20 4v4.5h-4.5"/></svg></button>
40
+ <button class="icon-btn" id="btn-search" title="搜索"><svg class="ico" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6.5"/><path d="M16 16l4.5 4.5"/></svg></button>
40
41
  <button class="icon-btn" id="btn-add" title="添加会话"><svg class="ico" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></button>
41
42
  </div>
42
43
  </header>
43
44
 
44
45
  <!-- 主页面: 双频滑动卡片 -->
45
- <main class="page-container" id="page-main" data-tab="main">
46
+ <main class="page-container" id="page-main" data-tab="main" hidden>
46
47
  <!-- 水平滑动卡片轨道 -->
47
48
  <div class="card-carousel" id="card-carousel">
48
49
  <div class="card-track" id="card-track">
@@ -60,31 +61,43 @@
60
61
  <div class="detail-body" id="detail-body"></div>
61
62
  </div>
62
63
  </main>
63
-
64
- <!-- 网络 tab -->
65
- <section class="page" id="page-network" data-tab="network" hidden>
64
+ <!-- 好友 tab: 好友与 P2P (原来散在网络页, 现已归拢; 网络页不再重复这些) -->
65
+ <section class="page" id="page-friends" data-tab="friends" hidden>
66
66
  <div class="list">
67
- <div class="list-item" id="item-join-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M3.5 9.5a13 13 0 0 1 17 0M6.5 13a8.5 8.5 0 0 1 11 0M9.6 16.3a4 4 0 0 1 4.8 0"/><circle cx="12" cy="19.5" r="1.1" fill="currentColor" stroke="none"/></svg></span><span>加入网络</span></div>
68
- <div class="list-item" id="item-scan-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M4 8V6a2 2 0 0 1 2-2h2M16 4h2a2 2 0 0 1 2 2v2M20 16v2a2 2 0 0 1-2 2h-2M8 20H6a2 2 0 0 1-2-2v-2"/><path d="M4 12h16"/></svg></span><span>扫码入网</span></div>
67
+ <div class="list-item" id="item-add-friend"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 19c.8-3 3-4.6 5.5-4.6S13.7 16 14.5 19"/><path d="M17 8v6M14 11h6"/></svg></span><span>连接好友</span><span class="list-arrow">›</span></div>
68
+ <div class="list-item" id="item-nearby"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 16 0"/><path d="M7.5 12a4.5 4.5 0 0 1 9 0"/><circle cx="12" cy="12" r="1.4"/><path d="M12 13.6V19"/></svg></span><span>附近设备</span><span class="list-arrow">›</span></div>
69
+ <div class="list-item" id="item-scan-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M4 8V6a2 2 0 0 1 2-2h2M16 4h2a2 2 0 0 1 2 2v2M20 16v2a2 2 0 0 1-2 2h-2M8 20H6a2 2 0 0 1-2-2v-2"/><path d="M4 12h16"/></svg></span><span>扫码入网 / 加好友</span></div>
69
70
  </div>
70
71
  <input type="file" id="qr-scan-input" accept="image/*" capture="environment" style="display:none">
71
- <div class="section-label">P2P 连接</div>
72
- <div class="list" id="p2p-status"></div>
73
- <div class="section-label">Agent 网络</div>
74
- <div class="list" id="net-members"></div>
75
72
  <div class="list">
76
73
  <div class="list-item" id="item-p2p"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.3 2.3 3.5 5.3 3.5 8.5s-1.2 6.2-3.5 8.5c-2.3-2.3-3.5-5.3-3.5-8.5S9.7 5.8 12 3.5z"/></svg></span><span>P2P 好友</span></div>
77
74
  <div class="list-item" id="item-p2p-id"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><rect x="3" y="5.5" width="18" height="13" rx="2.5"/><circle cx="8.5" cy="11" r="2"/><path d="M5.6 15.8a3.2 3.2 0 0 1 5.8 0M13.5 10h5M13.5 13.5h5"/></svg></span><span>我的 P2P ID</span></div>
78
75
  </div>
76
+ <div class="section-label">P2P 连接</div>
77
+ <div class="list" id="p2p-status"></div>
78
+ <div class="section-label">好友列表</div>
79
+ <div class="list" id="contacts-list"></div>
80
+ </section>
81
+
82
+ </main>
83
+
84
+ <!-- 网络 tab -->
85
+ <section class="page" id="page-network" data-tab="network" hidden>
86
+ <div class="list">
87
+ <div class="list-item" id="item-join-global"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.7 2.6 2.7 14.4 0 17M12 3.5c-2.7 2.6-2.7 14.4 0 17"/><circle cx="12" cy="12" r="1.2" fill="currentColor" stroke="none"/></svg></span><span>一键入网 · 全球智能体网络</span><span class="list-arrow">›</span></div>
88
+ <div class="list-item" id="item-join-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M3.5 9.5a13 13 0 0 1 17 0M6.5 13a8.5 8.5 0 0 1 11 0M9.6 16.3a4 4 0 0 1 4.8 0"/><circle cx="12" cy="19.5" r="1.1" fill="currentColor" stroke="none"/></svg></span><span>加入网络</span><span class="list-arrow">›</span></div>
89
+ </div>
90
+ <div class="section-label">Agent 网络</div>
91
+ <div class="list" id="net-members"></div>
79
92
  <div class="section-label">Agent 服务 (协议自动发现)</div>
80
93
  <div class="list" id="agent-services"></div>
81
94
  <div class="list">
82
95
  <div class="list-item" id="item-trade"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M4 7h16M4 12h10M4 17h7"/><path d="M17.5 14.5l2.5 2.5-2.5 2.5"/></svg></span><span style="flex:1">资源交易</span><span class="list-arrow">›</span></div>
83
96
  </div>
84
- <div class="section-label">P2P 好友列表</div>
85
- <div class="list" id="contacts-list"></div>
86
- <div class="section-label">MCP 工具 (触控调用)</div>
87
- <div class="list" id="mcp-tools"></div>
97
+ <div class="section-label">微信息 (微支付)</div>
98
+ <div class="list" id="x402-info-list"></div>
99
+ <div class="section-label">智能体控制 (MCP / Skills)</div>
100
+ <div class="list" id="agent-control"></div>
88
101
  <div class="section-label">待人工审批支付</div>
89
102
  <div class="list" id="approval-list"></div>
90
103
  <div class="section-label">A2UI 动态面板 (智能体生成)</div>
@@ -117,6 +130,7 @@
117
130
  <!-- 底部 tab -->
118
131
  <nav class="tabbar" id="tabbar">
119
132
  <button class="tab active" data-tab="main"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="7" height="7" rx="1.6"/><rect x="13.5" y="3.5" width="7" height="7" rx="1.6"/><rect x="3.5" y="13.5" width="7" height="7" rx="1.6"/><rect x="13.5" y="13.5" width="7" height="7" rx="1.6"/></svg></span><span>首页</span></button>
133
+ <button class="tab" data-tab="friends"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 19c.8-3 3-4.6 5.5-4.6S13.7 16 14.5 19"/><path d="M17 8v6M14 11h6"/></svg></span><span>好友</span></button>
120
134
  <button class="tab" data-tab="network"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.3 2.3 3.5 5.3 3.5 8.5s-1.2 6.2-3.5 8.5c-2.3-2.3-3.5-5.3-3.5-8.5S9.7 5.8 12 3.5z"/></svg></span><span>网络</span></button>
121
135
  <button class="tab" data-tab="me"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="8.5" r="4"/><path d="M4.5 20.5a7.5 7.5 0 0 1 15 0"/></svg></span><span>我</span></button>
122
136
  </nav>
@@ -125,6 +139,26 @@
125
139
  <button class="create-session-btn" id="btn-create-session" hidden>+ 创建新会话</button>
126
140
 
127
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
+
128
162
  <div class="sheet" id="create-sheet" hidden>
129
163
  <div class="sheet-inner">
130
164
  <div class="spinner"></div>
@@ -132,15 +166,58 @@
132
166
  </div>
133
167
  </div>
134
168
 
135
- <!-- 添加好友: 底部滑出选择 sheet -->
169
+ <!-- 添加好友: 底部滑出选择 sheet (点按式: 扫码 / 附近 / 待处理申请 / 手动) -->
136
170
  <div class="sheet" id="addfriend-sheet" hidden>
137
171
  <div class="sheet-inner sheet-inner-choices">
138
- <div class="sheet-title">添加 P2P 好友</div>
172
+ <div class="sheet-title">连接好友</div>
173
+ <button class="sheet-choice" id="choice-nearby"><svg class="ico" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 16 0"/><path d="M7.5 12a4.5 4.5 0 0 1 9 0"/><circle cx="12" cy="12" r="1.4"/><path d="M12 13.6V19"/></svg> 附近的设备</button>
139
174
  <button class="sheet-choice" id="choice-scan"><svg class="ico" viewBox="0 0 24 24"><path d="M4 8V6a2 2 0 0 1 2-2h2M16 4h2a2 2 0 0 1 2 2v2M20 16v2a2 2 0 0 1-2 2h-2M8 20H6a2 2 0 0 1-2-2v-2"/><path d="M4 12h16"/></svg> 扫码添加</button>
140
- <button class="sheet-choice" id="choice-manual"><svg class="ico" viewBox="0 0 24 24"><path d="M10.5 13.5a3.5 3.5 0 0 1 0-5l2-2a3.5 3.5 0 0 1 5 5l-1 1"/><path d="M13.5 10.5a3.5 3.5 0 0 1 0 5l-2 2a3.5 3.5 0 0 1-5-5l1-1"/></svg> 手动输入地址</button>
175
+ <button class="sheet-choice" id="choice-requests"><svg class="ico" viewBox="0 0 24 24"><path d="M6 4h9l4 4v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z"/><path d="M9 12h6M9 16h4"/></svg> 待处理申请</button>
176
+ <button class="sheet-choice sheet-cancel" id="choice-manual">⌨️ 手动输入地址 (兜底)</button>
141
177
  <button class="sheet-choice sheet-cancel" id="choice-cancel">取消</button>
142
178
  </div>
143
179
  </div>
180
+
181
+ <!-- 加入网络: 点按式 sheet (扫一扫 / 附近设备 / 手动粘贴兜底) -->
182
+ <div class="sheet" id="network-sheet" hidden>
183
+ <div class="sheet-inner sheet-inner-choices">
184
+ <div class="sheet-title">加入网络</div>
185
+ <button class="sheet-choice" id="choice-join-nearby"><svg class="ico" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 16 0"/><path d="M7.5 12a4.5 4.5 0 0 1 9 0"/><circle cx="12" cy="12" r="1.4"/><path d="M12 13.6V19"/></svg> 附近的电脑 / 设备</button>
186
+ <button class="sheet-choice" id="choice-join-scan"><svg class="ico" viewBox="0 0 24 24"><path d="M4 8V6a2 2 0 0 1 2-2h2M16 4h2a2 2 0 0 1 2 2v2M20 16v2a2 2 0 0 1-2 2h-2M8 20H6a2 2 0 0 1-2-2v-2"/><path d="M4 12h16"/></svg> 扫电脑上的二维码</button>
187
+ <button class="sheet-choice sheet-cancel" id="choice-join-manual">🔗 粘贴入网链接 (兜底)</button>
188
+ <button class="sheet-choice sheet-cancel" id="choice-join-cancel">取消</button>
189
+ </div>
190
+ </div>
191
+
192
+ <!-- 附近设备: 点按列表 (点一条即连接 / 建立好友) -->
193
+ <div class="sheet" id="nearby-sheet" hidden>
194
+ <div class="sheet-inner">
195
+ <div class="sheet-title" id="nearby-title">附近设备</div>
196
+ <div class="sheet-text" id="nearby-hint">正在查找...</div>
197
+ <div class="list" id="nearby-list" style="width:100%;max-height:46vh;overflow-y:auto"></div>
198
+ <button class="sheet-choice" id="nearby-refresh">↻ 刷新</button>
199
+ <button class="sheet-choice sheet-cancel" id="nearby-close">关闭</button>
200
+ </div>
201
+ </div>
202
+ <!-- 微信息 (x402 付费信息): 点一条看详情 → 购买并验真 / 只看元数据 -->
203
+ <div class="sheet" id="x402-sheet" hidden>
204
+ <div class="sheet-inner">
205
+ <div class="sheet-title" id="x402-title">微信息</div>
206
+ <div class="sheet-text" id="x402-body" style="width:100%;max-height:46vh;overflow-y:auto;text-align:left;white-space:pre-wrap;word-break:break-word"></div>
207
+ <button class="sheet-choice" id="x402-buy">购买并验真</button>
208
+ <button class="sheet-choice" id="x402-verify">只看元数据 (离线验真)</button>
209
+ <button class="sheet-choice sheet-cancel" id="x402-close">关闭</button>
210
+ </div>
211
+ </div>
212
+
213
+ <!-- 微信息结果: 内容 + 验真分档 (verified / self-attested / content-only / unverified) -->
214
+ <div class="sheet" id="x402-result-sheet" hidden>
215
+ <div class="sheet-inner">
216
+ <div class="sheet-title" id="x402-result-title">验真结果</div>
217
+ <div class="sheet-text" id="x402-result-body" style="width:100%;max-height:56vh;overflow-y:auto;text-align:left;white-space:pre-wrap;word-break:break-word"></div>
218
+ <button class="sheet-choice sheet-cancel" id="x402-result-close">关闭</button>
219
+ </div>
220
+ </div>
144
221
  </div>
145
222
 
146
223
  <!-- 2026-08-14: 手机端内化内核 (IndexedDB 数据 + 本地身份 + 支付审批 + Agent). 必须最先加载 -->
@@ -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",
@@ -52,6 +52,45 @@ export async function resetAgentDb() {
52
52
  }
53
53
  });
54
54
  }
55
+ async function _kvGet(key) {
56
+ try {
57
+ const db = _identityDb || (await openIdentityDb());
58
+ _identityDb = db;
59
+ return await new Promise((res) => { const t = db.transaction('kv', 'readonly'); const r = t.objectStore('kv').get(key); r.onsuccess = () => res(r.result ?? null); r.onerror = () => res(null); });
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function _kvPut(key, val) {
66
+ try {
67
+ const db = _identityDb || (await openIdentityDb());
68
+ _identityDb = db;
69
+ await new Promise((res) => { const t = db.transaction('kv', 'readwrite'); t.objectStore('kv').put(val, key); t.oncomplete = () => res(); t.onerror = () => res(); });
70
+ }
71
+ catch { /* IDB 不可用则仅内存态 */ }
72
+ }
73
+ /** 登录: 设置本机身份昵称 (无身份则新建) + 标记已登录 */
74
+ export async function loginIdentity(name) {
75
+ const id = await ensureIdentity();
76
+ const nm = String(name || '').trim() || 'blln-mobile';
77
+ const next = { ...id, name: nm };
78
+ await _kvPut('identity', next);
79
+ await _kvPut('loggedIn', true);
80
+ _identity = next;
81
+ return { ...next, loggedIn: true };
82
+ }
83
+ /** 注销: 清除登录态 (保留设备 DID, 不影响 P2P/频道) */
84
+ export async function logoutIdentity() {
85
+ await _kvPut('loggedIn', false);
86
+ return { ok: true };
87
+ }
88
+ /** 身份状态 (含登录态; 未登录时 name 置空) */
89
+ export async function identityStatus() {
90
+ const id = await ensureIdentity();
91
+ const loggedIn = (await _kvGet('loggedIn')) === true;
92
+ return { did: id.did, didShort: id.did ? id.did.slice(0, 12) : '', name: loggedIn ? id.name : '', createdAt: id.createdAt, loggedIn };
93
+ }
55
94
  /** 获取本机 DID (首次生成并持久化) */
56
95
  export async function ensureIdentity() {
57
96
  if (_identity)
@@ -121,11 +160,205 @@ async function applyLlmConfigToBridge() {
121
160
  }
122
161
  catch { /* 注入失败不阻塞本地执行 */ }
123
162
  }
124
- // ============ 本地执行 (Kotlin AgentRuntime / 内置规则) ============
163
+ // ============ 手机端「读入网说明 → 入网」(2026-09-15) ============
164
+ //
165
+ // 背景: 手机端「一键入网」发出的口令是 `read https://bolloon.cn/bolloon-gateway-join.md`
166
+ // (mobile.js DEFAULT_JOIN_PROMPT), 但手机侧此前只会走到「已收到: "…"」的兜底回复 ——
167
+ // 也就是说这句话在手机上是**空转**的: 既不读文档, 也不入网。
168
+ // 现在手机自己就能走完: 读说明(校验 frontmatter) → 本机 DID → 服务登记
169
+ // (桌面可达则登记进桌面的网络 registry, 否则本机登记并如实说明) → P2P 公告(尽力) → 落盘入网态。
170
+ // 桌面不可达不是失败: 手机是自治节点; 但每一步都如实报 ok/note, 不假装入网。
171
+ /** 入网口令识别 (与 mobile.js 的默认 prompt 同源) */
172
+ export const MOBILE_JOIN_DOC_RE = /read\s+(https?:\/\/\S*bolloon-gateway-join\.md)/i;
173
+ const MOBILE_JOIN_STATE_KEY = 'bolloon_gateway_join';
174
+ export function detectJoinDocUrl(text) {
175
+ const m = MOBILE_JOIN_DOC_RE.exec(String(text || ''));
176
+ return m ? m[1] : null;
177
+ }
178
+ /** 极简 SKILL.md frontmatter 解析 (只认 name/version) */
179
+ function parseFm(text) {
180
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(String(text || ''));
181
+ if (!m)
182
+ return {};
183
+ const out = {};
184
+ for (const line of m[1].split(/\r?\n/)) {
185
+ const kv = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(line.trim());
186
+ if (!kv)
187
+ continue;
188
+ const k = kv[1].toLowerCase();
189
+ if (k === 'name')
190
+ out.name = kv[2].trim().replace(/^["']|["']$/g, '');
191
+ if (k === 'version')
192
+ out.version = kv[2].trim().replace(/^["']|["']$/g, '');
193
+ }
194
+ return out;
195
+ }
196
+ export async function getMobileJoinState() {
197
+ try {
198
+ const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(MOBILE_JOIN_STATE_KEY) : null;
199
+ return raw ? JSON.parse(raw) : null;
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ }
205
+ function saveMobileJoinState(s) {
206
+ try {
207
+ if (typeof localStorage !== 'undefined')
208
+ localStorage.setItem(MOBILE_JOIN_STATE_KEY, JSON.stringify(s));
209
+ }
210
+ catch { /* 忽略 */ }
211
+ }
212
+ /**
213
+ * 按入网说明文档入网 (手机端自足执行)。
214
+ * opts.desktopBaseUrl 可注入 (测试用); 默认读 mobile-gateway 持久化的桌面基址。
215
+ */
216
+ export async function joinGatewayFromDoc(docUrl, opts = {}) {
217
+ const f = opts.fetchImpl || fetch;
218
+ const steps = [];
219
+ const url = String(docUrl || '').trim();
220
+ if (!/^https?:\/\//i.test(url)) {
221
+ return { ok: false, docUrl: url, steps, error: `入网说明地址必须是 http(s) URL (收到: ${url.slice(0, 60)})` };
222
+ }
223
+ // ① 读入网说明 + 校验
224
+ let docVersion;
225
+ try {
226
+ const r = await f(url, { signal: AbortSignal.timeout(opts.timeoutMs ?? 15000) });
227
+ if (!r.ok) {
228
+ steps.push({ step: '读入网说明', ok: false, note: `文档不可达 (HTTP ${r.status})` });
229
+ return { ok: false, docUrl: url, steps, error: `入网说明不可达 (HTTP ${r.status})` };
230
+ }
231
+ const text = await r.text();
232
+ const fm = parseFm(text);
233
+ if (fm.name !== 'bolloon-gateway-join' && !/加入网关|bolloon-gateway-join/.test(text)) {
234
+ steps.push({ step: '读入网说明', ok: false, note: `不是 Bolloon 入网说明 (name=${fm.name || '无'})` });
235
+ return { ok: false, docUrl: url, steps, error: '该文档不是 Bolloon 网关入网说明, 拒绝据此入网' };
236
+ }
237
+ docVersion = fm.version;
238
+ steps.push({ step: '读入网说明', ok: true, note: `${fm.name || 'bolloon-gateway-join'} v${fm.version || '?'} (${text.length} 字符)` });
239
+ }
240
+ catch (e) {
241
+ steps.push({ step: '读入网说明', ok: false, note: `读取失败: ${String(e?.message || e).slice(0, 120)}` });
242
+ return { ok: false, docUrl: url, steps, error: `入网说明读取失败: ${String(e?.message || e).slice(0, 120)}` };
243
+ }
244
+ // ② 本机 DID (手机端身份层; 可注入, 便于测试/无 IndexedDB 环境)
245
+ let did = String(opts.did || '');
246
+ if (did) {
247
+ steps.push({ step: 'DID 身份', ok: true, note: `${did} (注入身份)` });
248
+ }
249
+ else {
250
+ try {
251
+ const id = await ensureIdentity();
252
+ did = id.did;
253
+ steps.push({ step: 'DID 身份', ok: true, note: `${did} (手机端本机生成)` });
254
+ }
255
+ catch (e) {
256
+ steps.push({ step: 'DID 身份', ok: false, note: String(e?.message || e).slice(0, 120) });
257
+ return { ok: false, docUrl: url, docVersion, steps, error: '手机端 DID 生成失败' };
258
+ }
259
+ }
260
+ const name = String(opts.name || 'phone-agent');
261
+ const capabilities = ['chat', 'gateway-join'];
262
+ // ③ 服务登记: 桌面可达 → 登记进桌面(网络) registry; 否则本机登记并如实说明
263
+ let desktopBase = String(opts.desktopBaseUrl ?? '');
264
+ if (opts.desktopBaseUrl === undefined) {
265
+ try {
266
+ const g = await import('./mobile-gateway.js');
267
+ desktopBase = String(g.getDesktopBaseUrl() || '');
268
+ }
269
+ catch {
270
+ desktopBase = '';
271
+ }
272
+ }
273
+ desktopBase = desktopBase.replace(/\/+$/, '');
274
+ let registeredOn = 'local';
275
+ if (desktopBase) {
276
+ try {
277
+ const r = await f(`${desktopBase}/api/registry/register`, {
278
+ method: 'POST', headers: { 'content-type': 'application/json' },
279
+ body: JSON.stringify({
280
+ agentId: did, name, wallet: '',
281
+ service: { name: 'chat', description: '手机端智能体 (自足节点)', price: { amount: '0', currency: 'USDC', per: 'task' }, endpoint: '' },
282
+ capabilities,
283
+ }),
284
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 15000),
285
+ });
286
+ if (r.ok) {
287
+ registeredOn = 'desktop';
288
+ steps.push({ step: '服务登记', ok: true, note: `已登记进电脑端网络 registry (${desktopBase}) —— 网络内其他智能体可按能力发现我` });
289
+ }
290
+ else {
291
+ steps.push({ step: '服务登记', ok: false, note: `电脑端 registry 拒绝 (HTTP ${r.status}); 已改为本机登记` });
292
+ }
293
+ }
294
+ catch (e) {
295
+ steps.push({ step: '服务登记', ok: false, note: `电脑端不可达 (${String(e?.message || e).slice(0, 80)}); 已改为本机登记` });
296
+ }
297
+ }
298
+ else {
299
+ steps.push({ step: '服务登记', ok: true, note: '未配置电脑端基址 → 只在本机登记 (手机是自治节点; 设置里填电脑端地址可登记进网络 registry)' });
300
+ }
301
+ // ④ P2P 公告 (尽力): 让已连接的对端知道本机服务; 没连上不是入网失败
302
+ try {
303
+ const p2p = await import('./mobile-p2p.js');
304
+ let peers = 0;
305
+ try {
306
+ peers = (p2p.getConnectedPeers?.() || []).length;
307
+ }
308
+ catch {
309
+ peers = 0;
310
+ }
311
+ if (peers > 0 && typeof p2p.sendMobileP2PMessage === 'function') {
312
+ const okAnnounce = await p2p.sendMobileP2PMessage('*', 'registry.register', JSON.stringify({ agent_id: did, name, capabilities }), did);
313
+ steps.push({ step: 'P2P 公告', ok: !!okAnnounce, note: okAnnounce ? `已向 ${peers} 个对端广播本机声明` : `广播失败 (对端 ${peers} 个)` });
314
+ }
315
+ else {
316
+ steps.push({ step: 'P2P 公告', ok: false, note: '当前无已连接对端 (浏览器/未连电脑端时正常) — 本机声明已就绪, 连上即生效' });
317
+ }
318
+ }
319
+ catch (e) {
320
+ steps.push({ step: 'P2P 公告', ok: false, note: `P2P 层不可用: ${String(e?.message || e).slice(0, 80)}` });
321
+ }
322
+ // ⑤ 落盘入网态 (幂等: 同 url 再次入网覆盖时间戳)
323
+ saveMobileJoinState({ url, did, name, capabilities, docVersion, registeredOn, desktopBaseUrl: desktopBase || undefined, joinedAt: new Date().toISOString() });
324
+ const state = await getMobileJoinState();
325
+ steps.push({ step: '落盘入网态', ok: !!state?.did, note: `localStorage:${MOBILE_JOIN_STATE_KEY}` });
326
+ return { ok: true, docUrl: url, docVersion, did, steps };
327
+ }
328
+ /** 把入网结果渲染成给用户看的回复 (与桌面工具的 steps 汇报风格一致) */
329
+ export function formatMobileJoinResult(r) {
330
+ if (!r.ok) {
331
+ return `❌ 入网失败: ${r.error || '未知原因'}\n\n${r.steps.map((s) => `${s.ok ? '✓' : '✗'} ${s.step}: ${s.note}`).join('\n')}`;
332
+ }
333
+ const s = r.steps.find((x) => x.step === '服务登记');
334
+ return [
335
+ '✅ 已加入全球智能体网络 (手机端自足执行)',
336
+ '',
337
+ `DID: ${r.did}`,
338
+ `入网说明: v${r.docVersion || '?'} (${r.docUrl})`,
339
+ s ? `登记: ${s.note}` : '',
340
+ '',
341
+ ...r.steps.map((x) => `${x.ok ? '✓' : '✗'} ${x.step}: ${x.note}`),
342
+ '',
343
+ '用「网络 → Agent 网络」可查看成员; 设置里填电脑端地址可把本机登记进网络 registry。',
344
+ ].filter((l) => l !== '').join('\n');
345
+ }
346
+ // ============ 本地执行 (Kotlin AgentRuntime / 离线兜底) ============
125
347
  /** 手机端本地 agent 执行 (优先 Kotlin, 离线内置规则) */
126
348
  export async function runLocalAgent(goal) {
127
349
  const win = typeof window !== 'undefined' ? window : null;
128
350
  const cap = win?.Capacitor;
351
+ // 2026-09-15: 「读入网说明 → 入网」在手机端本地自足执行 (先于 Kotlin 桥: 原生工具集里没有入网能力,
352
+ // 交给它只会得到空转回复)。这样浏览器 / WebView / 真机三种环境行为一致。
353
+ const joinDocUrl = detectJoinDocUrl(goal);
354
+ if (joinDocUrl) {
355
+ _lastWorklog = [`🧩 识别为入网口令: ${joinDocUrl}`];
356
+ const r = await joinGatewayFromDoc(joinDocUrl).catch((e) => ({
357
+ ok: false, docUrl: joinDocUrl, steps: [{ step: '入网', ok: false, note: String(e?.message || e).slice(0, 120) }], error: String(e?.message || e),
358
+ }));
359
+ _lastWorklog = [..._lastWorklog, ...r.steps.map((s) => `${s.ok ? '✓' : '✗'} ${s.step}: ${s.note}`)];
360
+ return formatMobileJoinResult(r);
361
+ }
129
362
  const bridge = cap && cap.Plugins && cap.Plugins.RokidBridge;
130
363
  if (bridge && cap.isNativePlatform?.()) {
131
364
  try {
@@ -318,12 +551,18 @@ export async function runPhoneAgent(goal) {
318
551
  stepCount: r?.stepCount,
319
552
  };
320
553
  }
321
- // 离线 fallback: 内置规则 (不依赖 LLM/无障碍, 手机仍自治可用)
554
+ // 离线 fallback: 内置规则 (不依赖 LLM/原生执行能力, 手机仍自治可用)
322
555
  const reply = await runLocalAgent(goal);
323
- return { ok: true, goal, result: reply, did: id.did, mode: 'fallback' };
556
+ return {
557
+ ok: true, goal, result: reply, did: id.did, mode: 'fallback',
558
+ hint: '当前是「手机本地规则」模式:这台手机上还没接原生执行能力 (iOS 暂不支持原生操控 App; Android 需开无障碍服务),所以只能用内置规则回复。连上电脑端后,任务可以交给电脑端 Agent 真正执行。',
559
+ };
324
560
  }
325
561
  catch (e) {
326
- return { ok: false, goal, error: String(e?.message || e).slice(0, 100), did: id.did, mode: native ? 'native' : 'fallback' };
562
+ return {
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 需无障碍服务)。',
565
+ };
327
566
  }
328
567
  }
329
568
  /** 手机端 Agent 状态 (phone.agent.status) */
@@ -400,4 +639,4 @@ export async function handleIncomingPhoneMessage(type, payload, fromPeer) {
400
639
  }
401
640
  catch { /* 控制消息处理失败静默 */ }
402
641
  }
403
- export default { ensureIdentity, runLocalAgent, setAgentTransport, onAgentReply, callRemoteAgent, handleIncomingAgentMessage, notifyAgentReply, onInboundChat, setLlmConfig, getLlmConfig, runPhoneAgent, phoneStatus, cancelPhoneAgent, handleIncomingPhoneMessage };
642
+ export default { ensureIdentity, loginIdentity, logoutIdentity, identityStatus, runLocalAgent, setAgentTransport, onAgentReply, callRemoteAgent, handleIncomingAgentMessage, notifyAgentReply, onInboundChat, setLlmConfig, getLlmConfig, runPhoneAgent, phoneStatus, cancelPhoneAgent, handleIncomingPhoneMessage };