@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.
- package/dist/agents/chain/chain-settlement.js +0 -1
- package/dist/agents/chat-segmenter.js +4 -16
- package/dist/agents/contacts/human-directory.js +155 -0
- package/dist/agents/contacts/tools.js +79 -0
- package/dist/agents/experience-review.js +0 -3
- package/dist/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/agents/goal-resume.js +0 -4
- package/dist/agents/intent-classifier.js +1 -1
- package/dist/agents/iteration-budget.js +12 -0
- package/dist/agents/knowledge-organizer.js +0 -2
- package/dist/agents/lesson-to-skill.js +0 -1
- package/dist/agents/loop-review.js +2 -2
- package/dist/agents/memory-store.js +359 -0
- package/dist/agents/opportunity-inbox.js +147 -0
- package/dist/agents/opportunity-match.js +48 -7
- package/dist/agents/opportunity-web-search.js +222 -0
- package/dist/agents/p2p-chat-tools.js +1 -2
- package/dist/agents/parse-tool-call.js +9 -22
- package/dist/agents/pi-harness.js +1 -2
- package/dist/agents/pi-sdk-tools.js +180 -5
- package/dist/agents/pi-sdk.js +143 -9
- package/dist/agents/process-runner.js +28 -9
- package/dist/agents/react-loop.js +9 -17
- package/dist/agents/runner-resolver.js +4 -3
- package/dist/agents/skill-loader.js +22 -15
- package/dist/agents/skills-manager.js +1 -25
- package/dist/agents/workflow-pivot-loop.js +152 -61
- package/dist/agents/world-watcher.js +30 -1
- package/dist/bootstrap/context-collector.js +2 -4
- package/dist/bootstrap/context-hierarchy.js +13 -1
- package/dist/cli/ink-app.js +44 -17
- package/dist/cli/intent-command.js +22 -12
- package/dist/cli/loading-tui.js +8 -2
- package/dist/cli/log-gate.js +40 -8
- package/dist/cli/mention-data.js +1 -0
- package/dist/cli/reply-hygiene.js +22 -0
- package/dist/cli/trace-line.js +61 -2
- package/dist/electron-build/agents/agent-gateway.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
- package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/contacts/human-directory.js +204 -0
- package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
- package/dist/electron-build/agents/contacts/policy.js.map +1 -1
- package/dist/electron-build/agents/contacts/tools.js +112 -0
- package/dist/electron-build/agents/contacts/tools.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +0 -3
- package/dist/electron-build/agents/experience-review.js.map +1 -1
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
- package/dist/electron-build/agents/goal-resume.js +0 -4
- package/dist/electron-build/agents/goal-resume.js.map +1 -1
- package/dist/electron-build/agents/intent-classifier.js +1 -1
- package/dist/electron-build/agents/lesson-to-skill.js +0 -1
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
- package/dist/electron-build/agents/loop-review.js +2 -2
- package/dist/electron-build/agents/loop-review.js.map +1 -1
- package/dist/electron-build/agents/memory-recall.js.map +1 -1
- package/dist/electron-build/agents/memory-store.js +404 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +9 -22
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-harness.js +1 -2
- package/dist/electron-build/agents/pi-harness.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +143 -9
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +28 -9
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/runner-resolver.js +4 -3
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/skill-loader.js +22 -16
- package/dist/electron-build/agents/skill-loader.js.map +1 -1
- package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
- package/dist/electron-build/agents/skills-manager.js +0 -24
- package/dist/electron-build/agents/skills-manager.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-engine.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
- package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +2 -4
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
- package/dist/electron-build/electron-preload.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +31 -2
- package/dist/electron-build/kernel/channel-actor.js.map +1 -1
- package/dist/electron-build/kernel/model-runtime.js +8 -1
- package/dist/electron-build/kernel/model-runtime.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +20 -0
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/embedding-adapter.js +75 -0
- package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +96 -35
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/network/agent-network.js.map +1 -1
- package/dist/electron-build/orbitdb/memory-sync.js +76 -0
- package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
- package/dist/electron-build/security/tool-gate.js +2 -0
- package/dist/electron-build/security/tool-gate.js.map +1 -1
- package/dist/electron-build/setup/setup-store.js +34 -6
- package/dist/electron-build/setup/setup-store.js.map +1 -1
- package/dist/electron-build/social/heartbeat.js +18 -5
- package/dist/electron-build/social/heartbeat.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/index.js +250 -12
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +150 -113
- package/dist/ios/icons/icon-1400x1400.png +0 -0
- package/dist/ios/icons/icon-1400x1400.webp +0 -0
- package/dist/ios/index.html +106 -107
- package/dist/ios/mobile-agent.js +346 -45
- package/dist/ios/mobile-core.js +7303 -4590
- package/dist/ios/mobile-harness.js +1004 -0
- package/dist/ios/mobile-orbitdb.js +194 -0
- package/dist/ios/mobile-persona.js +80 -0
- package/dist/ios/mobile-privacy.js +2 -3
- package/dist/ios/mobile.css +286 -21
- package/dist/ios/mobile.html +106 -107
- package/dist/ios/mobile.js +300 -132
- package/dist/ios/routes-world.js +58 -1
- package/dist/ios/server.js +3 -1
- package/dist/ios/style.css +49 -2
- package/dist/ios/sw.js +1 -1
- package/dist/ios/ui/message-renderer.js +4 -18
- package/dist/ios/ui/step-timeline.js +14 -1
- package/dist/kernel/channel-actor.js +30 -2
- package/dist/kernel/gate-scan.js +1 -1
- package/dist/kernel/index.js +33 -0
- package/dist/kernel/model-runtime.js +8 -1
- package/dist/kernel/plan.js +3 -3
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/config-store.js +20 -0
- package/dist/llm/embedding-adapter.js +69 -0
- package/dist/llm/pi-ai.js +94 -34
- package/dist/orbitdb/memory-sync.js +70 -0
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
- package/dist/security/tool-gate.js +2 -0
- package/dist/setup/setup-store.js +34 -6
- package/dist/social/heartbeat.js +18 -5
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +150 -113
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +52 -59
- package/dist/web/mobile-agent.js +351 -45
- package/dist/web/mobile-contacts.js +0 -4
- package/dist/web/mobile-core.js +7430 -4590
- package/dist/web/mobile-harness.js +1004 -0
- package/dist/web/mobile-orbitdb.js +194 -0
- package/dist/web/mobile-persona.js +80 -0
- package/dist/web/mobile-privacy.js +2 -3
- package/dist/web/mobile.css +286 -21
- package/dist/web/mobile.html +121 -107
- package/dist/web/mobile.js +409 -136
- package/dist/web/routes-contacts.js +94 -1
- package/dist/web/routes-world.js +58 -1
- package/dist/web/server.js +4 -2
- package/dist/web/style.css +49 -2
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +4 -18
- package/dist/web/ui/step-timeline.js +14 -1
- package/package.json +3 -2
- package/scripts/build-web.ts +16 -1
package/dist/web/mobile-agent.js
CHANGED
|
@@ -10,19 +10,77 @@
|
|
|
10
10
|
* agent.info : 请求/响应 对端 DID 与能力
|
|
11
11
|
* - 本层只负责"agent 智能", 不碰存储; session 落库由 mobile-core 协调 data 层做.
|
|
12
12
|
*/
|
|
13
|
-
// ============ 身份 (
|
|
13
|
+
// ============ 身份 (DIAP KeyManager — @diap/sdk/browser, 与桌面同一套) ============
|
|
14
|
+
// 2026-10-07: 手机端身份改用 @diap/sdk 原生实现 (KeyManager 生成 Ed25519 密钥对 → did:key),
|
|
15
|
+
// 与桌面 agent-identity / x402 签名完全同构: privateKey 32B 种子 + publicKey + did:key:z...
|
|
16
|
+
// 签名走 @noble/ed25519 signAsync (与桌面 ed25519Sign 同结果)。
|
|
14
17
|
const IDENTITY_DB = 'bolloon-mobile';
|
|
15
18
|
let _identity = null;
|
|
16
19
|
// #2 手机自动入网 (browser-safe gateway, 与桌面同一协议): 检测到链接自动 join
|
|
17
20
|
import { mobileAutoJoinGateway } from './mobile-gateway.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
// @diap/sdk/browser — KeyManager (密钥生成/DID 派生), @noble/ed25519 — signAsync (签名)
|
|
22
|
+
// 两个都是纯 JS 无 Node 依赖, 浏览器可用。动态 import 避免拖慢首屏。
|
|
23
|
+
let _keyMgr = null;
|
|
24
|
+
let _ed25519 = null;
|
|
25
|
+
async function loadDiap() {
|
|
26
|
+
if (_keyMgr && _ed25519)
|
|
27
|
+
return { KeyManager: _keyMgr, ed25519: _ed25519 };
|
|
28
|
+
const km = await import('@diap/sdk/browser');
|
|
29
|
+
const ed = await import('@noble/ed25519');
|
|
30
|
+
_keyMgr = km.KeyManager;
|
|
31
|
+
_ed25519 = ed;
|
|
32
|
+
return { KeyManager: km.KeyManager, ed25519: ed };
|
|
33
|
+
}
|
|
34
|
+
function bytesOf(s) {
|
|
35
|
+
const src = new TextEncoder().encode(s);
|
|
36
|
+
const out = new Uint8Array(new ArrayBuffer(src.byteLength));
|
|
37
|
+
out.set(src);
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function bufToHex(buf) {
|
|
41
|
+
return Array.from(buf).map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
42
|
+
}
|
|
43
|
+
async function sha256Hex(text) {
|
|
44
|
+
const d = await crypto.subtle.digest('SHA-256', bytesOf(text));
|
|
45
|
+
return Array.from(new Uint8Array(d)).map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
46
|
+
}
|
|
47
|
+
function b64(buf) {
|
|
48
|
+
let out = '';
|
|
49
|
+
for (let i = 0; i < buf.length; i++)
|
|
50
|
+
out += String.fromCharCode(buf[i]);
|
|
51
|
+
return btoa(out);
|
|
52
|
+
}
|
|
53
|
+
function b64ToBytes(b64s) {
|
|
54
|
+
const bin = atob(b64s);
|
|
55
|
+
const out = new Uint8Array(bin.length);
|
|
56
|
+
for (let i = 0; i < bin.length; i++)
|
|
57
|
+
out[i] = bin.charCodeAt(i);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/** 用身份私钥 (32B hex) 签名任意文本 → base64 签名 + 公钥 + DID (与桌面 ed25519Sign 同语义) */
|
|
61
|
+
export async function signWithIdentity(payload) {
|
|
62
|
+
const id = await ensureIdentity();
|
|
63
|
+
if (!id.privateKey)
|
|
64
|
+
throw new Error('identity_signing_unavailable: 本机身份无私钥');
|
|
65
|
+
const { ed25519 } = await loadDiap();
|
|
66
|
+
const priv = Uint8Array.from(id.privateKey.match(/.{2}/g).map((h) => parseInt(h, 16)));
|
|
67
|
+
const sig = await ed25519.signAsync(bytesOf(String(payload ?? '')), priv);
|
|
68
|
+
return { did: id.did, publicKey: id.publicKey, signature: b64(new Uint8Array(sig)), alg: 'ed25519' };
|
|
69
|
+
}
|
|
70
|
+
/** 验证身份签名 (用身份公钥验) — 与桌面 ed25519Verify 同语义 */
|
|
71
|
+
export async function verifyIdentitySignature(payload, signatureB64, publicKeyHex) {
|
|
72
|
+
try {
|
|
73
|
+
const { ed25519 } = await loadDiap();
|
|
74
|
+
const pub = Uint8Array.from(publicKeyHex.match(/.{2}/g).map((h) => parseInt(h, 16)));
|
|
75
|
+
return ed25519.verifyAsync(b64ToBytes(signatureB64), bytesOf(String(payload ?? '')), pub);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
22
80
|
}
|
|
23
81
|
function openIdentityDb() {
|
|
24
82
|
return new Promise((resolve, reject) => {
|
|
25
|
-
const req = indexedDB.open(IDENTITY_DB,
|
|
83
|
+
const req = indexedDB.open(IDENTITY_DB, 2);
|
|
26
84
|
req.onupgradeneeded = () => {
|
|
27
85
|
const db = req.result;
|
|
28
86
|
if (!db.objectStoreNames.contains('kv'))
|
|
@@ -80,18 +138,37 @@ export async function loginIdentity(name) {
|
|
|
80
138
|
_identity = next;
|
|
81
139
|
return { ...next, loggedIn: true };
|
|
82
140
|
}
|
|
141
|
+
/** 更新身份附带条件 (邮箱/手机号/备注) — 只存本机 */
|
|
142
|
+
export async function updateIdentityProfile(partial) {
|
|
143
|
+
const id = await ensureIdentity();
|
|
144
|
+
const next = {
|
|
145
|
+
...id,
|
|
146
|
+
name: typeof partial.name === 'string' && partial.name.trim() ? partial.name.trim() : id.name,
|
|
147
|
+
email: typeof partial.email === 'string' ? partial.email.trim() : id.email,
|
|
148
|
+
phone: typeof partial.phone === 'string' ? partial.phone.trim() : id.phone,
|
|
149
|
+
note: typeof partial.note === 'string' ? partial.note.trim() : id.note,
|
|
150
|
+
};
|
|
151
|
+
await _kvPut('identity', next);
|
|
152
|
+
_identity = next;
|
|
153
|
+
return next;
|
|
154
|
+
}
|
|
83
155
|
/** 注销: 清除登录态 (保留设备 DID, 不影响 P2P/频道) */
|
|
84
156
|
export async function logoutIdentity() {
|
|
85
157
|
await _kvPut('loggedIn', false);
|
|
86
158
|
return { ok: true };
|
|
87
159
|
}
|
|
88
|
-
/** 身份状态 (
|
|
160
|
+
/** 身份状态 (含登录态 + 附带条件; 未登录时 name 置空) */
|
|
89
161
|
export async function identityStatus() {
|
|
90
162
|
const id = await ensureIdentity();
|
|
91
163
|
const loggedIn = (await _kvGet('loggedIn')) === true;
|
|
92
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
did: id.did, didShort: id.did ? id.did.slice(0, 12) : '',
|
|
166
|
+
name: loggedIn ? id.name : '', createdAt: id.createdAt, loggedIn,
|
|
167
|
+
publicKey: id.publicKey || '',
|
|
168
|
+
email: id.email || '', phone: id.phone || '', note: id.note || '',
|
|
169
|
+
};
|
|
93
170
|
}
|
|
94
|
-
/**
|
|
171
|
+
/** 获取本机身份 (首次用 DIAP KeyManager 生成 Ed25519 → did:key; 持久化) */
|
|
95
172
|
export async function ensureIdentity() {
|
|
96
173
|
if (_identity)
|
|
97
174
|
return _identity;
|
|
@@ -108,7 +185,27 @@ export async function ensureIdentity() {
|
|
|
108
185
|
_identity = id;
|
|
109
186
|
return id;
|
|
110
187
|
}
|
|
111
|
-
|
|
188
|
+
// 2026-10-07: 用 @diap/sdk KeyManager 生成 (与桌面同一套) — DID 即公钥指纹, 可签名
|
|
189
|
+
let kp;
|
|
190
|
+
try {
|
|
191
|
+
const { KeyManager } = await loadDiap();
|
|
192
|
+
kp = KeyManager.generate();
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
// 老 WebView 加载不了 @diap → 退化为哈希 DID (只读, 无签名)
|
|
196
|
+
const digest = await sha256Hex(String(Date.now()) + Math.random());
|
|
197
|
+
const legacy = { did: 'did:blln:' + digest.slice(0, 32), publicKey: '', privateKey: '', name: 'blln-mobile', createdAt: Date.now() };
|
|
198
|
+
await _kvPut('identity', legacy);
|
|
199
|
+
_identity = legacy;
|
|
200
|
+
return legacy;
|
|
201
|
+
}
|
|
202
|
+
const fresh = {
|
|
203
|
+
did: kp.did,
|
|
204
|
+
publicKey: bufToHex(kp.publicKey),
|
|
205
|
+
privateKey: bufToHex(kp.privateKey),
|
|
206
|
+
name: 'blln-mobile',
|
|
207
|
+
createdAt: Date.now(),
|
|
208
|
+
};
|
|
112
209
|
await new Promise((resolve, reject) => {
|
|
113
210
|
const tx = db.transaction('kv', 'readwrite');
|
|
114
211
|
tx.objectStore('kv').put(fresh, 'identity');
|
|
@@ -119,25 +216,35 @@ export async function ensureIdentity() {
|
|
|
119
216
|
return fresh;
|
|
120
217
|
}
|
|
121
218
|
catch {
|
|
122
|
-
const
|
|
219
|
+
const digest = await sha256Hex(String(Date.now()) + Math.random());
|
|
220
|
+
const fallback = { did: 'did:blln:' + digest.slice(0, 32), publicKey: '', privateKey: '', name: 'blln-mobile', createdAt: Date.now() };
|
|
123
221
|
_identity = fallback;
|
|
124
222
|
return fallback;
|
|
125
223
|
}
|
|
126
224
|
}
|
|
127
225
|
// ============ LLM 配置 (桌面同步注入 / 手机默认) ============
|
|
226
|
+
// 2026-10-07: 开箱即用 — 手机独立运行时无需任何 API 配置。
|
|
227
|
+
// 内置公共网关 (Cloudflare Worker: api.bolloon.cn) 转发到 GLM 上游;
|
|
228
|
+
// 公共通道每天 ≤500 次/人; 桌面同步注入自己的 key 后自动覆盖。
|
|
229
|
+
const BUILTIN_LLM = Object.freeze({
|
|
230
|
+
baseUrl: 'https://api.bolloon.cn/v1',
|
|
231
|
+
apiKey: 'bolloon-free',
|
|
232
|
+
model: 'glm-5.3',
|
|
233
|
+
maxTokens: 16384,
|
|
234
|
+
});
|
|
128
235
|
let _llmConfig = null;
|
|
129
236
|
/** 最近一次 runLocalAgent 的执行过程摘要 (每步 onStep 文本), 由 message.send 广播给工作记录 UI */
|
|
130
237
|
let _lastWorklog = [];
|
|
131
238
|
export function getLastWorklog() { return _lastWorklog; }
|
|
132
|
-
/** 注入 LLM 配置 (由 mobile-core 在 data.llm-config.reply 同步后调用) */
|
|
239
|
+
/** 注入 LLM 配置 (由 mobile-core 在 data.llm-config.reply 同步后调用); null = 用内置免费网关 */
|
|
133
240
|
export function setLlmConfig(cfg) {
|
|
134
241
|
_llmConfig = cfg;
|
|
135
242
|
// 立即注入 native bridge (agentConfigure), 让 AgentRuntime 马上可用, 不必等下次 runLocalAgent
|
|
136
243
|
applyLlmConfigToBridge().catch(() => { });
|
|
137
244
|
}
|
|
138
|
-
/** 当前 LLM 配置 (
|
|
245
|
+
/** 当前 LLM 配置 (未同步则返回内置免费网关 → 手机开箱即用) */
|
|
139
246
|
export function getLlmConfig() {
|
|
140
|
-
return _llmConfig;
|
|
247
|
+
return _llmConfig || BUILTIN_LLM;
|
|
141
248
|
}
|
|
142
249
|
/** 把配置注入 Capacitor RokidBridge (agentConfigure), 让 Kotlin AgentRuntime 用同步来的 LLM */
|
|
143
250
|
async function applyLlmConfigToBridge() {
|
|
@@ -346,8 +453,6 @@ export function formatMobileJoinResult(r) {
|
|
|
346
453
|
// ============ 本地执行 (Kotlin AgentRuntime / 离线兜底) ============
|
|
347
454
|
/** 手机端本地 agent 执行 (优先 Kotlin, 离线内置规则) */
|
|
348
455
|
export async function runLocalAgent(goal) {
|
|
349
|
-
const win = typeof window !== 'undefined' ? window : null;
|
|
350
|
-
const cap = win?.Capacitor;
|
|
351
456
|
// 2026-09-15: 「读入网说明 → 入网」在手机端本地自足执行 (先于 Kotlin 桥: 原生工具集里没有入网能力,
|
|
352
457
|
// 交给它只会得到空转回复)。这样浏览器 / WebView / 真机三种环境行为一致。
|
|
353
458
|
const joinDocUrl = detectJoinDocUrl(goal);
|
|
@@ -359,20 +464,20 @@ export async function runLocalAgent(goal) {
|
|
|
359
464
|
_lastWorklog = [..._lastWorklog, ...r.steps.map((s) => `${s.ok ? '✓' : '✗'} ${s.step}: ${s.note}`)];
|
|
360
465
|
return formatMobileJoinResult(r);
|
|
361
466
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
await applyLlmConfigToBridge();
|
|
366
|
-
const r = await bridge.runAgent({ goal });
|
|
367
|
-
_lastWorklog = (r && Array.isArray(r.worklog)) ? r.worklog.map((x) => String(x)) : [];
|
|
368
|
-
return r?.result || '(无返回)';
|
|
369
|
-
}
|
|
370
|
-
catch (e) {
|
|
371
|
-
throw new Error('AgentRuntime: ' + String(e?.message || e).slice(0, 60));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
467
|
+
// 普通手机 Agent 永远走 WebView 本地执行链。
|
|
468
|
+
// Android 无障碍仅适用于未来的跨 App 控制,不得阻塞手机端基础执行;iOS 与 Android 因此行为一致。
|
|
374
469
|
// 内置极简回复 (纯离线可用)
|
|
375
470
|
const t = (goal || '').trim();
|
|
471
|
+
// 2026-10-07: 手机自治 — 先用 LLM 循环 (经内置公共网关), 失败再降级纯规则
|
|
472
|
+
try {
|
|
473
|
+
const llm = getLlmConfig();
|
|
474
|
+
if (llm && llm.baseUrl) {
|
|
475
|
+
const reply = await runWebAgentLoop(goal, { baseUrl: llm.baseUrl, apiKey: llm.apiKey, model: llm.model, maxTokens: llm.maxTokens });
|
|
476
|
+
if (reply)
|
|
477
|
+
return reply;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch { /* 网络/网关不可达 → 降级内置规则 */ }
|
|
376
481
|
if (t.includes('你好') || t === 'hi' || t === 'hello')
|
|
377
482
|
return '你好! 我是炁球 (Bolloon), 已在手机本地独立运行 (Agent 功能层 + 数据同步层分离)。';
|
|
378
483
|
if (t.includes('身份') || t.includes('did')) {
|
|
@@ -407,6 +512,202 @@ export async function runLocalAgent(goal) {
|
|
|
407
512
|
}
|
|
408
513
|
return `已收到: "${(goal || '').slice(0, 40)}"。这是手机端 Agent 功能层的本地执行 (数据同步与 agent 功能已分离)。`;
|
|
409
514
|
}
|
|
515
|
+
// ============ 手机自治 agent 循环 (2026-10-07) ============
|
|
516
|
+
// 桌面端 WorkflowPivotLoop 的浏览器移植: LLM 多轮迭代 + 手机端可用工具集。
|
|
517
|
+
// 能力范围: 手机 WebView 能做的 (身份/钱包/入网/P2P/信息查询), 不假装能做 fs/shell。
|
|
518
|
+
// 保护: maxSteps=5 防死循环; 每轮把工具结果拼回对话; LLM 说完成或没有下一步即停。
|
|
519
|
+
// 2026-10-07: persona 注入 — 性格/价值观/兴趣/说话方式来自 mobile-persona (设置 → 性格)。
|
|
520
|
+
import { loadPersona, buildPersonaPrompt } from './mobile-persona.js';
|
|
521
|
+
function buildAgentSystem() {
|
|
522
|
+
const persona = loadPersona();
|
|
523
|
+
const personaBlock = buildPersonaPrompt(persona);
|
|
524
|
+
return [
|
|
525
|
+
'你是手机端 Bolloon 智能体 (自治节点), 用中文简洁回复。',
|
|
526
|
+
personaBlock,
|
|
527
|
+
'你有以下工具, 按需调用 (JSON: {"tool":"名字","args":{...}}), 不需要工具就直接回答:',
|
|
528
|
+
'- get_status: 查本机身份/入网/钱包/P2P 状态 (args: 无)',
|
|
529
|
+
'- get_wallet: 查钱包余额 (args: 无)',
|
|
530
|
+
'- get_identity: 查本机 DID (args: 无)',
|
|
531
|
+
'- get_contacts: 查联系方式授权状态 (args: 无)',
|
|
532
|
+
'每轮只能用一个工具; 拿到结果后继续思考, 直到任务完成给出最终回答。',
|
|
533
|
+
].join('\n');
|
|
534
|
+
}
|
|
535
|
+
const WEB_AGENT_TOOLS = {
|
|
536
|
+
get_status: async () => {
|
|
537
|
+
try {
|
|
538
|
+
const id = await ensureIdentity();
|
|
539
|
+
return `DID: ${id.did.slice(0, 16)}... 名字: ${id.name}`;
|
|
540
|
+
}
|
|
541
|
+
catch (e) {
|
|
542
|
+
return '身份读取失败: ' + String(e?.message || e).slice(0, 60);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
get_wallet: async () => {
|
|
546
|
+
try {
|
|
547
|
+
const id = await ensureIdentity();
|
|
548
|
+
const w = await import('./mobile-wallet.js');
|
|
549
|
+
const info = await w.walletForAgent(id.did);
|
|
550
|
+
if (!info.exists)
|
|
551
|
+
return '本机没有授权给当前智能体的钱包 (我 → 设置 → 钱包 可创建/授权)。';
|
|
552
|
+
const w0 = info.wallets[0];
|
|
553
|
+
let s = `钱包: ${w0.name || w0.id} (${w0.address.slice(0, 12)}...)`;
|
|
554
|
+
if (w0.unlocked) {
|
|
555
|
+
try {
|
|
556
|
+
s += ` 余额: ${await w.walletBalance(w0.id)}`;
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
s += ' 余额: 查询失败';
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else
|
|
563
|
+
s += ' (未解锁)';
|
|
564
|
+
return s;
|
|
565
|
+
}
|
|
566
|
+
catch (e) {
|
|
567
|
+
return '钱包查询失败: ' + String(e?.message || e).slice(0, 60);
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
get_identity: async () => {
|
|
571
|
+
const id = await ensureIdentity();
|
|
572
|
+
return `DID: ${id.did} 名字: ${id.name}`;
|
|
573
|
+
},
|
|
574
|
+
get_contacts: async () => {
|
|
575
|
+
try {
|
|
576
|
+
return '联系方式与授权模块可用 (详见「我 → 联系方式与授权」)。';
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
return '联系人模块暂不可用';
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
/** 单轮 LLM 调用 (OpenAI 兼容, 经内置公共网关) */
|
|
584
|
+
async function webAgentLlmCall(messages, llm) {
|
|
585
|
+
// 2026-10-15: 网关不可达时**快速降级**而不是挂死 — 公共网关限流/断网时 fetch 可能永久挂起,
|
|
586
|
+
// 手机端 runLocalAgent 会先走 LLM 再降级内置规则, 挂起 = 测试 20s 超时 + 真机"发消息无反应"。
|
|
587
|
+
// 10s 兜底 (多轮循环每次都有上界, MAX_STEPS=5 ⇒ 最坏 50s, 足够拿到回复又不会无限等)。
|
|
588
|
+
const resp = await fetch(llm.baseUrl.replace(/\/$/, '') + '/chat/completions', {
|
|
589
|
+
method: 'POST',
|
|
590
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (llm.apiKey || '') },
|
|
591
|
+
body: JSON.stringify({
|
|
592
|
+
model: llm.model || 'glm-5.3',
|
|
593
|
+
messages,
|
|
594
|
+
max_tokens: llm.maxTokens || 2048,
|
|
595
|
+
temperature: 0.4,
|
|
596
|
+
}),
|
|
597
|
+
// eslint-disable-next-line no-undef
|
|
598
|
+
signal: AbortSignal.timeout(10_000),
|
|
599
|
+
});
|
|
600
|
+
if (!resp.ok)
|
|
601
|
+
throw new Error('LLM HTTP ' + resp.status + ': ' + (await resp.text()).slice(0, 120));
|
|
602
|
+
const data = await resp.json();
|
|
603
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
604
|
+
if (!content)
|
|
605
|
+
throw new Error('LLM 空回复');
|
|
606
|
+
return String(content).trim();
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* 宽容提取 LLM 回复里的工具调用 JSON。
|
|
610
|
+
* 匹配模式: {"tool":"...","args":{...}} — 容忍前置文本、代码块围栏、尾部文本。
|
|
611
|
+
* 用花括号平衡计数提取完整对象 (非贪婪正则会被内层嵌套 {} 截断 — 模拟器验证踩到)。
|
|
612
|
+
* 找不到合法 tool 字段返回 null (当作普通回答)。
|
|
613
|
+
*/
|
|
614
|
+
function extractToolCall(raw) {
|
|
615
|
+
if (!raw)
|
|
616
|
+
return null;
|
|
617
|
+
// 1) 直接找第一个 '{"' 开头、括号平衡的完整 JSON 对象
|
|
618
|
+
const start = raw.indexOf('{"');
|
|
619
|
+
if (start >= 0) {
|
|
620
|
+
try {
|
|
621
|
+
const obj = parseBalancedJson(raw, start);
|
|
622
|
+
if (obj && typeof obj.tool === 'string') {
|
|
623
|
+
return { tool: obj.tool, args: (obj.args && typeof obj.args === 'object' ? obj.args : {}) };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
catch { /* 不是 JSON, 继续 */ }
|
|
627
|
+
}
|
|
628
|
+
// 2) 代码块围栏内的 JSON (```json ... ```)
|
|
629
|
+
const fm = /```(?:json)?\s*([\s\S]*?)```/.exec(raw);
|
|
630
|
+
if (fm) {
|
|
631
|
+
try {
|
|
632
|
+
const obj = JSON.parse(fm[1]);
|
|
633
|
+
if (obj && typeof obj.tool === 'string') {
|
|
634
|
+
return { tool: obj.tool, args: (obj.args && typeof obj.args === 'object' ? obj.args : {}) };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
catch { /* 忽略 */ }
|
|
638
|
+
}
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
/** 从 start 位置解析一个括号平衡的 JSON 对象 (容忍内部嵌套字符串/大括号) */
|
|
642
|
+
function parseBalancedJson(raw, start) {
|
|
643
|
+
if (raw[start] !== '{')
|
|
644
|
+
throw new Error('not object');
|
|
645
|
+
let depth = 0;
|
|
646
|
+
let inString = false;
|
|
647
|
+
let escaped = false;
|
|
648
|
+
for (let i = start; i < raw.length; i++) {
|
|
649
|
+
const ch = raw[i];
|
|
650
|
+
if (inString) {
|
|
651
|
+
if (escaped) {
|
|
652
|
+
escaped = false;
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (ch === '\\') {
|
|
656
|
+
escaped = true;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (ch === '"')
|
|
660
|
+
inString = false;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (ch === '"') {
|
|
664
|
+
inString = true;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (ch === '{')
|
|
668
|
+
depth++;
|
|
669
|
+
else if (ch === '}') {
|
|
670
|
+
depth--;
|
|
671
|
+
if (depth === 0)
|
|
672
|
+
return JSON.parse(raw.slice(start, i + 1));
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
throw new Error('unbalanced');
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* 手机端 agent 循环: goal → LLM 思考 → (工具调用 → 结果回填 → 再思考) → 最终回答。
|
|
679
|
+
* 与桌面 pivot loop 同构但浏览器安全边界内; 任何一步失败即降级。
|
|
680
|
+
*/
|
|
681
|
+
export async function runWebAgentLoop(goal, llm) {
|
|
682
|
+
const messages = [
|
|
683
|
+
{ role: 'system', content: buildAgentSystem() },
|
|
684
|
+
{ role: 'user', content: goal },
|
|
685
|
+
];
|
|
686
|
+
const MAX_STEPS = 5;
|
|
687
|
+
const steps = [];
|
|
688
|
+
for (let i = 0; i < MAX_STEPS; i++) {
|
|
689
|
+
const raw = await webAgentLlmCall(messages, llm);
|
|
690
|
+
// 2026-10-07 (模拟器验证发现): LLM 常输出前置文本+JSON ("好的,我先查询...\n{\"tool\":...}")
|
|
691
|
+
// 旧正则要求整段是 JSON → 工具调用被当普通回答, 闭环断。宽容提取第一个 JSON 对象。
|
|
692
|
+
const toolJson = extractToolCall(raw);
|
|
693
|
+
if (toolJson && typeof toolJson.tool === 'string' && WEB_AGENT_TOOLS[toolJson.tool]) {
|
|
694
|
+
const out = await WEB_AGENT_TOOLS[toolJson.tool]();
|
|
695
|
+
steps.push(`🔧 ${toolJson.tool} → ${out.slice(0, 80)}`);
|
|
696
|
+
messages.push({ role: 'assistant', content: raw });
|
|
697
|
+
messages.push({ role: 'user', content: `工具结果: ${out}` });
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
// 无工具调用 = 最终回答
|
|
701
|
+
_lastWorklog = steps.length ? [...steps, `💬 ${raw.slice(0, 60)}`] : [`💬 ${raw.slice(0, 60)}`];
|
|
702
|
+
if (steps.length)
|
|
703
|
+
return `[手机自治执行 ${steps.length} 步]\n` + raw;
|
|
704
|
+
return raw;
|
|
705
|
+
}
|
|
706
|
+
// 循环保护: 没收敛就给最后一条
|
|
707
|
+
const last = messages[messages.length - 1]?.content || '';
|
|
708
|
+
_lastWorklog = [...steps, '⚠ 达到步数上限, 已停止'];
|
|
709
|
+
return `[手机自治执行, 达到 ${MAX_STEPS} 步上限]\n${last.slice(0, 200)}`;
|
|
710
|
+
}
|
|
410
711
|
let _send = null;
|
|
411
712
|
let _ownDid = '';
|
|
412
713
|
const replyHandlers = new Set();
|
|
@@ -537,31 +838,33 @@ export async function runPhoneAgent(goal) {
|
|
|
537
838
|
const id = await ensureIdentity();
|
|
538
839
|
try {
|
|
539
840
|
if (native) {
|
|
540
|
-
await
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
841
|
+
const st = await bridge.agentStatus?.();
|
|
842
|
+
if (st?.accessibilityReady !== false) {
|
|
843
|
+
await applyLlmConfigToBridge();
|
|
844
|
+
const r = await bridge.runAgent({ goal });
|
|
845
|
+
const result = r?.result || '(无返回)';
|
|
846
|
+
return {
|
|
847
|
+
ok: !/^\[Agent 异常|^STOPPED|^MAX_STEPS/.test(result),
|
|
848
|
+
goal,
|
|
849
|
+
result,
|
|
850
|
+
did: id.did,
|
|
851
|
+
mode: 'native',
|
|
852
|
+
agentId: r?.agentId,
|
|
853
|
+
stepCount: r?.stepCount,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
553
856
|
}
|
|
554
857
|
// 离线 fallback: 内置规则 (不依赖 LLM/原生执行能力, 手机仍自治可用)
|
|
555
858
|
const reply = await runLocalAgent(goal);
|
|
556
859
|
return {
|
|
557
860
|
ok: true, goal, result: reply, did: id.did, mode: 'fallback',
|
|
558
|
-
hint: '
|
|
861
|
+
hint: '当前使用手机本地 Agent;Android/iOS 均可直接执行手机端任务。',
|
|
559
862
|
};
|
|
560
863
|
}
|
|
561
864
|
catch (e) {
|
|
562
865
|
return {
|
|
563
866
|
ok: false, goal, error: String(e?.message || e).slice(0, 100), did: id.did, mode: native ? 'native' : 'fallback',
|
|
564
|
-
hint: '
|
|
867
|
+
hint: '任务没跑起来。请先检查「我 → 设置 → API 配置」中的 baseUrl、apiKey 和 model。',
|
|
565
868
|
};
|
|
566
869
|
}
|
|
567
870
|
}
|
|
@@ -583,12 +886,15 @@ export async function phoneStatus() {
|
|
|
583
886
|
}
|
|
584
887
|
catch { /* 忽略 */ }
|
|
585
888
|
}
|
|
889
|
+
const capabilities = ['chat', 'local-agent'];
|
|
890
|
+
if (!native || accReady)
|
|
891
|
+
capabilities.push('phone-control');
|
|
586
892
|
return {
|
|
587
893
|
ok: true,
|
|
588
894
|
did: id.did,
|
|
589
895
|
mode: native ? 'native' : 'fallback',
|
|
590
896
|
llm,
|
|
591
|
-
capabilities
|
|
897
|
+
capabilities,
|
|
592
898
|
};
|
|
593
899
|
}
|
|
594
900
|
/** 取消当前手机 Agent 任务 (phone.agent.cancel) */
|
|
@@ -639,4 +945,4 @@ export async function handleIncomingPhoneMessage(type, payload, fromPeer) {
|
|
|
639
945
|
}
|
|
640
946
|
catch { /* 控制消息处理失败静默 */ }
|
|
641
947
|
}
|
|
642
|
-
export default { ensureIdentity, loginIdentity, logoutIdentity, identityStatus, runLocalAgent, setAgentTransport, onAgentReply, callRemoteAgent, handleIncomingAgentMessage, notifyAgentReply, onInboundChat, setLlmConfig, getLlmConfig, runPhoneAgent, phoneStatus, cancelPhoneAgent, handleIncomingPhoneMessage };
|
|
948
|
+
export default { ensureIdentity, loginIdentity, logoutIdentity, identityStatus, runLocalAgent, setAgentTransport, onAgentReply, callRemoteAgent, handleIncomingAgentMessage, notifyAgentReply, onInboundChat, setLlmConfig, getLlmConfig, runPhoneAgent, phoneStatus, cancelPhoneAgent, handleIncomingPhoneMessage, runWebAgentLoop };
|
|
@@ -83,10 +83,6 @@ export async function loadOrCreateDeviceKey(storage, c) {
|
|
|
83
83
|
storage.setItem(K_DEVICE, JSON.stringify(material));
|
|
84
84
|
return material;
|
|
85
85
|
}
|
|
86
|
-
async function importPrivate(storage, c) {
|
|
87
|
-
const k = await loadOrCreateDeviceKey(storage, c);
|
|
88
|
-
return c.subtle.importKey('jwk', k.privateKeyJwk, { name: 'Ed25519' }, false, ['sign']);
|
|
89
|
-
}
|
|
90
86
|
/** 用设备私钥签一条 Grant (核心: 手机确认 → 桌面才认) */
|
|
91
87
|
export async function signGrantOnDevice(grant, storage, c) {
|
|
92
88
|
return signPayloadOnDevice(canonicalGrantPayload(grant), storage, c);
|