@bolloon/bolloon-agent 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/agents/chain/chain-settlement.js +0 -1
  2. package/dist/agents/chat-segmenter.js +4 -16
  3. package/dist/agents/contacts/human-directory.js +155 -0
  4. package/dist/agents/contacts/tools.js +79 -0
  5. package/dist/agents/experience-review.js +0 -3
  6. package/dist/agents/goal-flywheel/goal-change.js +3 -4
  7. package/dist/agents/goal-resume.js +0 -4
  8. package/dist/agents/ha-daemon.js +85 -0
  9. package/dist/agents/ha-supervisor.js +146 -0
  10. package/dist/agents/intent-classifier.js +1 -1
  11. package/dist/agents/iteration-budget.js +12 -0
  12. package/dist/agents/knowledge-organizer.js +0 -2
  13. package/dist/agents/lesson-to-skill.js +0 -1
  14. package/dist/agents/loop-review.js +2 -2
  15. package/dist/agents/memory-store.js +384 -0
  16. package/dist/agents/opportunity-match.js +15 -3
  17. package/dist/agents/opportunity-web-search.js +39 -0
  18. package/dist/agents/p2p-chat-tools.js +1 -2
  19. package/dist/agents/parse-tool-call.js +9 -22
  20. package/dist/agents/pi-harness.js +1 -2
  21. package/dist/agents/pi-sdk-session-manager.js +8 -0
  22. package/dist/agents/pi-sdk-tools.js +180 -5
  23. package/dist/agents/pi-sdk.js +210 -503
  24. package/dist/agents/process-runner.js +28 -9
  25. package/dist/agents/react-loop.js +9 -17
  26. package/dist/agents/runner-resolver.js +4 -3
  27. package/dist/agents/skill-loader.js +22 -15
  28. package/dist/agents/skills-manager.js +1 -25
  29. package/dist/agents/workflow-pivot-loop.js +152 -61
  30. package/dist/bootstrap/context-collector.js +67 -7
  31. package/dist/bootstrap/context-hierarchy.js +13 -1
  32. package/dist/bootstrap/persona-loader.js +26 -0
  33. package/dist/cli/ink-app.js +44 -17
  34. package/dist/cli/intent-command.js +22 -12
  35. package/dist/cli/loading-tui.js +8 -2
  36. package/dist/cli/log-gate.js +40 -8
  37. package/dist/cli/mention-data.js +1 -0
  38. package/dist/cli/reply-hygiene.js +22 -0
  39. package/dist/cli/trace-line.js +61 -2
  40. package/dist/electron-build/agents/agent-gateway.js.map +1 -1
  41. package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
  42. package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
  43. package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
  44. package/dist/electron-build/agents/contacts/chain.js.map +1 -1
  45. package/dist/electron-build/agents/contacts/human-directory.js +204 -0
  46. package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
  47. package/dist/electron-build/agents/contacts/policy.js.map +1 -1
  48. package/dist/electron-build/agents/contacts/tools.js +112 -0
  49. package/dist/electron-build/agents/contacts/tools.js.map +1 -1
  50. package/dist/electron-build/agents/experience-review.js +0 -3
  51. package/dist/electron-build/agents/experience-review.js.map +1 -1
  52. package/dist/electron-build/agents/external-events.js.map +1 -1
  53. package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
  54. package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
  55. package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
  56. package/dist/electron-build/agents/goal-resume.js +0 -4
  57. package/dist/electron-build/agents/goal-resume.js.map +1 -1
  58. package/dist/electron-build/agents/intent-classifier.js +1 -1
  59. package/dist/electron-build/agents/lesson-to-skill.js +0 -1
  60. package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
  61. package/dist/electron-build/agents/loop-review.js +2 -2
  62. package/dist/electron-build/agents/loop-review.js.map +1 -1
  63. package/dist/electron-build/agents/memory-recall.js.map +1 -1
  64. package/dist/electron-build/agents/memory-store.js +430 -0
  65. package/dist/electron-build/agents/memory-store.js.map +1 -0
  66. package/dist/electron-build/agents/parse-tool-call.js +9 -22
  67. package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
  68. package/dist/electron-build/agents/pi-harness.js +1 -2
  69. package/dist/electron-build/agents/pi-harness.js.map +1 -1
  70. package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
  71. package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
  72. package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
  73. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  74. package/dist/electron-build/agents/pi-sdk.js +208 -501
  75. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  76. package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
  77. package/dist/electron-build/agents/process-runner.js +28 -9
  78. package/dist/electron-build/agents/process-runner.js.map +1 -1
  79. package/dist/electron-build/agents/runner-resolver.js +4 -3
  80. package/dist/electron-build/agents/runner-resolver.js.map +1 -1
  81. package/dist/electron-build/agents/skill-loader.js +22 -16
  82. package/dist/electron-build/agents/skill-loader.js.map +1 -1
  83. package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
  84. package/dist/electron-build/agents/skills-manager.js +0 -24
  85. package/dist/electron-build/agents/skills-manager.js.map +1 -1
  86. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  87. package/dist/electron-build/agents/workflow-engine.js.map +1 -1
  88. package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
  89. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  90. package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
  91. package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
  92. package/dist/electron-build/bootstrap/context-collector.js +68 -7
  93. package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
  94. package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
  95. package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
  96. package/dist/electron-build/bootstrap/persona-loader.js +27 -0
  97. package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
  98. package/dist/electron-build/electron-preload.js.map +1 -1
  99. package/dist/electron-build/external-engines/delegate.js.map +1 -1
  100. package/dist/electron-build/kernel/channel-actor.js +31 -2
  101. package/dist/electron-build/kernel/channel-actor.js.map +1 -1
  102. package/dist/electron-build/kernel/model-runtime.js +8 -1
  103. package/dist/electron-build/kernel/model-runtime.js.map +1 -1
  104. package/dist/electron-build/llm/config-store.js +20 -0
  105. package/dist/electron-build/llm/config-store.js.map +1 -1
  106. package/dist/electron-build/llm/embedding-adapter.js +75 -0
  107. package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
  108. package/dist/electron-build/llm/pi-ai.js +112 -38
  109. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  110. package/dist/electron-build/llm/system-prompt/registry.js +8 -6
  111. package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
  112. package/dist/electron-build/network/agent-network.js.map +1 -1
  113. package/dist/electron-build/orbitdb/memory-sync.js +76 -0
  114. package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
  115. package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
  116. package/dist/electron-build/security/tool-gate.js +2 -0
  117. package/dist/electron-build/security/tool-gate.js.map +1 -1
  118. package/dist/electron-build/setup/setup-store.js +34 -6
  119. package/dist/electron-build/setup/setup-store.js.map +1 -1
  120. package/dist/electron-build/social/heartbeat.js +18 -5
  121. package/dist/electron-build/social/heartbeat.js.map +1 -1
  122. package/dist/electron-build/utils/update-manager.js.map +1 -1
  123. package/dist/index.js +250 -12
  124. package/dist/ios/bolloon-web.json +3 -3
  125. package/dist/ios/client.js +74 -35
  126. package/dist/ios/icons/icon-1400x1400.png +0 -0
  127. package/dist/ios/icons/icon-1400x1400.webp +0 -0
  128. package/dist/ios/index.html +122 -108
  129. package/dist/ios/mobile-agent.js +572 -45
  130. package/dist/ios/mobile-contacts.js +0 -4
  131. package/dist/ios/mobile-core.js +8079 -4590
  132. package/dist/ios/mobile-harness.js +1004 -0
  133. package/dist/ios/mobile-memory.js +317 -0
  134. package/dist/ios/mobile-orbitdb.js +194 -0
  135. package/dist/ios/mobile-persona.js +80 -0
  136. package/dist/ios/mobile-privacy.js +2 -3
  137. package/dist/ios/mobile.css +286 -21
  138. package/dist/ios/mobile.html +122 -108
  139. package/dist/ios/mobile.js +434 -152
  140. package/dist/ios/routes-contacts.js +94 -1
  141. package/dist/ios/routes-world.js +13 -2
  142. package/dist/ios/server.js +54 -41
  143. package/dist/ios/style.css +13 -5
  144. package/dist/ios/sw.js +1 -1
  145. package/dist/ios/ui/message-renderer.js +4 -18
  146. package/dist/ios/ui/step-timeline.js +14 -1
  147. package/dist/kernel/channel-actor.js +30 -2
  148. package/dist/kernel/gate-scan.js +2 -2
  149. package/dist/kernel/ha/ha-arbiter.js +177 -0
  150. package/dist/kernel/ha/ha-guard.js +151 -0
  151. package/dist/kernel/index.js +36 -0
  152. package/dist/kernel/model-runtime.js +8 -1
  153. package/dist/kernel/plan.js +5 -5
  154. package/dist/kernel/roster.js +3 -3
  155. package/dist/llm/config-store.js +20 -0
  156. package/dist/llm/embedding-adapter.js +69 -0
  157. package/dist/llm/pi-ai.js +110 -37
  158. package/dist/llm/system-prompt/registry.js +8 -6
  159. package/dist/orbitdb/memory-sync.js +70 -0
  160. package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
  161. package/dist/security/tool-gate.js +2 -0
  162. package/dist/setup/setup-store.js +34 -6
  163. package/dist/social/heartbeat.js +18 -5
  164. package/dist/web/bolloon-web.json +3 -3
  165. package/dist/web/client.js +74 -35
  166. package/dist/web/icons/icon-1400x1400.png +0 -0
  167. package/dist/web/icons/icon-1400x1400.webp +0 -0
  168. package/dist/web/index.html +37 -25
  169. package/dist/web/mobile-agent.js +572 -45
  170. package/dist/web/mobile-contacts.js +0 -4
  171. package/dist/web/mobile-core.js +8079 -4590
  172. package/dist/web/mobile-harness.js +1004 -0
  173. package/dist/web/mobile-memory.js +317 -0
  174. package/dist/web/mobile-orbitdb.js +194 -0
  175. package/dist/web/mobile-persona.js +80 -0
  176. package/dist/web/mobile-privacy.js +2 -3
  177. package/dist/web/mobile.css +286 -21
  178. package/dist/web/mobile.html +122 -108
  179. package/dist/web/mobile.js +434 -152
  180. package/dist/web/routes-contacts.js +94 -1
  181. package/dist/web/routes-world.js +13 -2
  182. package/dist/web/server.js +54 -41
  183. package/dist/web/style.css +13 -5
  184. package/dist/web/sw.js +1 -1
  185. package/dist/web/ui/message-renderer.js +4 -18
  186. package/dist/web/ui/step-timeline.js +14 -1
  187. package/package.json +3 -2
  188. package/scripts/build-web.ts +16 -1
@@ -0,0 +1,317 @@
1
+ const memKey = (agentId) => `bolloon_mobile_memory_${agentId}`;
2
+ /** 轻量 hash (记忆指纹/条目 id, 不涉密) */
3
+ export function quickHash(text) {
4
+ let h = 2166136261;
5
+ for (let i = 0; i < String(text || '').length; i++) {
6
+ h ^= String(text || '').charCodeAt(i);
7
+ h = Math.imul(h, 16777619);
8
+ }
9
+ return (h >>> 0).toString(36) + '_' + String(text || '').length.toString(36);
10
+ }
11
+ export function validMemoryContent(content) {
12
+ return String(content || '').trim().length >= 2;
13
+ }
14
+ /** importance 启发式 (与桌面同口径): 长度 + 强语义词 + kind 加权 */
15
+ export function heuristicImportance(content, kind = 'fact') {
16
+ const t = String(content || '');
17
+ let score = 0.3;
18
+ if (t.length > 100)
19
+ score += 0.2;
20
+ if (/喜欢|偏好|讨厌|禁止|必须|永远|从不|重要|注意|记住|约定|家人|朋友|同事/.test(t))
21
+ score += 0.25;
22
+ if (kind === 'preference' || kind === 'relationship')
23
+ score += 0.2;
24
+ if (kind === 'skill-feedback')
25
+ score += 0.15;
26
+ return Math.min(1, score);
27
+ }
28
+ /** 人际关系分类: 从文本/来源推断 relation kind (规则启发式; 未知 = stranger) */
29
+ export function classifyRelation(text, source = '') {
30
+ const t = String(text || '').toLowerCase();
31
+ const src = String(source || '').toLowerCase();
32
+ if (/家人|妈妈|爸爸|母亲|父亲|儿子|女儿|老婆|老公|妻子|丈夫|兄弟|姐妹/.test(t))
33
+ return 'family';
34
+ if (/同事|客户|供应商|老板|领导|下属|同事|合作/.test(t))
35
+ return 'work';
36
+ if (/朋友|好友|哥们|闺蜜|兄弟|认识的人/.test(t))
37
+ return 'friend';
38
+ if (/directory|phone|手机号|通讯录/.test(src))
39
+ return 'acquaintance';
40
+ return 'stranger';
41
+ }
42
+ /**
43
+ * 手机记忆库 (localStorage 持久化; 内存缓存避免每次读盘)。
44
+ * 测试注入 fake localStorage (mobile-core.test 模式) 即可用。
45
+ */
46
+ export class MobileMemory {
47
+ agentId;
48
+ entries = [];
49
+ loaded = false;
50
+ constructor(agentId) {
51
+ this.agentId = String(agentId || '');
52
+ }
53
+ key() { return memKey(this.agentId); }
54
+ readAll() {
55
+ try {
56
+ const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(this.key()) : null;
57
+ if (!raw)
58
+ return [];
59
+ const parsed = JSON.parse(raw);
60
+ return Array.isArray(parsed) ? parsed : [];
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ }
66
+ writeAll(list) {
67
+ try {
68
+ if (typeof localStorage !== 'undefined')
69
+ localStorage.setItem(this.key(), JSON.stringify(list.slice(-200)));
70
+ }
71
+ catch { /* 写失败静默 (内存态仍可用) */ }
72
+ }
73
+ ensureLoaded() {
74
+ if (!this.loaded) {
75
+ this.entries = this.readAll();
76
+ this.loaded = true;
77
+ }
78
+ }
79
+ /** 全部条目 (内部用) */
80
+ all() { this.ensureLoaded(); return [...this.entries]; }
81
+ /** 落一条记忆 (幂等: 同 id 覆盖); 返回条目 */
82
+ remember(input) {
83
+ this.ensureLoaded();
84
+ const content = String(input.content || '').trim();
85
+ const source = input.source || 'session';
86
+ if (!validMemoryContent(content)) {
87
+ // 空内容拒绝存 (与桌面 validContent 同口径)
88
+ const id = quickHash(this.agentId + '|' + content || 'empty');
89
+ const stub = { id, agentId: this.agentId, content: content || '(空)', kind: input.kind || 'fact', importance: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), source, recallCount: 0 };
90
+ return stub;
91
+ }
92
+ const kind = input.kind || 'fact';
93
+ const now = new Date().toISOString();
94
+ const id = quickHash(this.agentId + '|' + content);
95
+ const importance = typeof input.importance === 'number'
96
+ ? Math.min(1, Math.max(0, input.importance))
97
+ : heuristicImportance(content, kind);
98
+ const entry = {
99
+ id, agentId: this.agentId, content, kind,
100
+ importance, createdAt: now, updatedAt: now,
101
+ source,
102
+ ttlMs: input.ttlMs, decayed: false, crystallized: false,
103
+ recallCount: 0,
104
+ ...(kind === 'relationship' && input.relation ? { relation: input.relation } : {}),
105
+ };
106
+ const idx = this.entries.findIndex((e) => e.id === id);
107
+ if (idx >= 0) {
108
+ const prev = this.entries[idx];
109
+ this.entries[idx] = { ...prev, ...entry, createdAt: prev.createdAt, recallCount: prev.recallCount, crystallized: prev.crystallized || entry.importance >= 0.8 };
110
+ if (input.relation && prev.relation) {
111
+ // 关系记忆迭代: 信任度按新信息移动 (平均值), 分类按最新文本重判
112
+ this.entries[idx].relation = {
113
+ did: input.relation.did || prev.relation.did,
114
+ kind: input.relation.kind || prev.relation.kind,
115
+ trust: Math.min(1, Math.max(0, (prev.relation.trust + input.relation.trust) / 2)),
116
+ };
117
+ }
118
+ }
119
+ else {
120
+ this.entries.push(entry);
121
+ }
122
+ this.writeAll(this.entries);
123
+ return this.entries.find((e) => e.id === id) || entry;
124
+ }
125
+ /** 召回: 关键词 + importance 加权; 命中即 +1 recallCount + 刷新 lastRecalledAt (迭代) */
126
+ recall(query, opts = {}) {
127
+ this.ensureLoaded();
128
+ this.applyTtl();
129
+ const q = String(query || '').toLowerCase();
130
+ const limit = opts.limit ?? 3;
131
+ const minScore = opts.minScore ?? 0;
132
+ const scored = [];
133
+ for (const e of this.entries) {
134
+ if (e.decayed)
135
+ continue;
136
+ const c = String(e.content || '').toLowerCase();
137
+ let s = e.importance * 0.5;
138
+ const qTokens = q.split(/\s+/).filter((t) => t.length >= 2);
139
+ const hit = qTokens.filter((t) => c.includes(t)).length;
140
+ if (hit > 0)
141
+ s += 0.3 * (hit / Math.max(qTokens.length, 1));
142
+ if (qTokens.length && qTokens.some((t) => c.includes(t)))
143
+ s += 0.2; // 部分命中
144
+ if (e.relation && e.relation.trust >= 0.7)
145
+ s += 0.1; // 高信任关系记忆加权
146
+ if (s >= minScore)
147
+ scored.push({ e, s });
148
+ }
149
+ scored.sort((a, b) => b.s - a.s);
150
+ const hits = scored.slice(0, limit).map((x) => x.e);
151
+ for (const h of hits) {
152
+ const now = new Date().toISOString();
153
+ h.recallCount += 1;
154
+ h.lastRecalledAt = now;
155
+ if (h.recallCount >= 3)
156
+ h.crystallized = true; // 多次召回 → 固化 (不 decay)
157
+ }
158
+ if (hits.length)
159
+ this.writeAll(this.entries);
160
+ return hits;
161
+ }
162
+ /** TTL 过期 → decayed (不删, 与桌面 applyDecay 同精神) */
163
+ applyTtl(nowIso) {
164
+ this.ensureLoaded();
165
+ const now = Date.parse(nowIso || new Date().toISOString());
166
+ let changed = false;
167
+ for (const e of this.entries) {
168
+ if (e.decayed || e.crystallized || !e.ttlMs)
169
+ continue;
170
+ const base = Date.parse(e.lastRecalledAt || e.createdAt) || now;
171
+ if (now - base > e.ttlMs) {
172
+ e.decayed = true;
173
+ changed = true;
174
+ }
175
+ }
176
+ if (changed)
177
+ this.writeAll(this.entries);
178
+ }
179
+ /**
180
+ * 记忆迭代 (每轮循环后调用): 从「用户目标 + 工具结果 + 最终回复」提炼
181
+ * 新事实 / 偏好 / 关系。规则启发式 (零 LLM 成本): 信号词命中即存,
182
+ * 已经存过的同内容自动合并 (remember 幂等)。
183
+ */
184
+ autoIterate(input) {
185
+ this.ensureLoaded();
186
+ const parts = [String(input.goal || ''), ...(input.toolResults || []), String(input.reply || '')];
187
+ const text = parts.filter(Boolean).join('\n');
188
+ if (!text.trim())
189
+ return 0;
190
+ let added = 0;
191
+ // ① 偏好记忆 — 兼容「我/用户/本人」主语 (agent 转述用户原话时主语可能是「用户」)
192
+ const pref = text.match(/(?:我|用户|本人)(?:喜欢|偏好|热爱|想要|希望).{2,40}?/g);
193
+ if (pref) {
194
+ for (const m of pref.slice(0, 3)) {
195
+ const content = m.replace(/^(?:我|用户|本人)/, '').trim();
196
+ if (validMemoryContent(content)) {
197
+ this.remember({ content: '偏好: ' + content, kind: 'preference', source: input.source || 'iterate' });
198
+ added++;
199
+ }
200
+ }
201
+ }
202
+ // ② 禁忌/约定记忆 — 兼容「不要/不能/别」开头 (用户约束)
203
+ const ban = text.match(/不要(?:再)?.{2,40}?|不要.{2,40}?|不能.{2,40}?|禁止.{2,40}?|记住.{2,40}?/g);
204
+ if (ban) {
205
+ for (const m of ban.slice(0, 2)) {
206
+ if (validMemoryContent(m)) {
207
+ this.remember({ content: '约定: ' + m.trim(), kind: 'insight', source: input.source || 'iterate', importance: 0.6 });
208
+ added++;
209
+ }
210
+ }
211
+ }
212
+ // ③ 关系记忆: 出现「人名/称谓 + 关系词」→ 分类存 (含「同事/合作伙伴」等职场关系)
213
+ const relMatch = text.match(/([一-龥]{2,6})是(?:我的)?(家人|妈妈|爸爸|老婆|老公|朋友|同事|客户|老板|领导|兄弟|姐妹|妻子|丈夫|合作伙伴)/);
214
+ if (relMatch) {
215
+ const name = relMatch[1];
216
+ const kind = classifyRelation(relMatch[0], input.source || '');
217
+ const content = `${name} 是${relMatch[2]}`;
218
+ this.remember({
219
+ content, kind: 'relationship', source: input.source || 'iterate',
220
+ importance: 0.8,
221
+ relation: { did: '', kind, trust: 0.6 },
222
+ });
223
+ added++;
224
+ }
225
+ return added;
226
+ }
227
+ /** 快照 (run 结束/重启恢复用) */
228
+ snapshot() {
229
+ this.ensureLoaded();
230
+ this.applyTtl();
231
+ return { version: 1, agentId: this.agentId, entries: [...this.entries], savedAt: new Date().toISOString() };
232
+ }
233
+ /** 从快照恢复 (bootstrap) — 合并, 不覆盖已有 */
234
+ restore(snap) {
235
+ if (!snap || !Array.isArray(snap.entries))
236
+ return;
237
+ this.ensureLoaded();
238
+ for (const e of snap.entries) {
239
+ const exists = this.entries.some((x) => x.id === e.id);
240
+ if (!exists)
241
+ this.entries.push(e);
242
+ }
243
+ this.writeAll(this.entries);
244
+ }
245
+ /**
246
+ * 稳态记忆块: 固化优先 + 未淡化 + importance 降序, 始终注入 system。
247
+ * 与 recall(query) 互补: recall 是即时相关, stableBlock 是用户确认/多次命中的事实。
248
+ */
249
+ stableBlock(maxEntries = 6) {
250
+ this.ensureLoaded();
251
+ this.applyTtl();
252
+ const picked = this.entries
253
+ .filter((e) => !e.decayed)
254
+ .sort((a, b) => {
255
+ if (a.crystallized !== b.crystallized)
256
+ return a.crystallized ? -1 : 1;
257
+ return b.importance - a.importance;
258
+ })
259
+ .slice(0, maxEntries);
260
+ if (!picked.length)
261
+ return '';
262
+ const lines = picked.map((e) => {
263
+ const tag = `${e.kind}${e.crystallized ? '★' : ''}`;
264
+ return `- (${tag}, ${e.importance.toFixed(2)}) ${e.content.slice(0, 120)}`;
265
+ });
266
+ return `<stable-memory>\n${lines.join('\n')}\n</stable-memory>`;
267
+ }
268
+ /** 记忆指纹: 摘要 hash (参与上下文缓存 key; 记忆变了 → 指纹变 → 缓存 miss) */
269
+ fingerprint() {
270
+ this.ensureLoaded();
271
+ this.applyTtl();
272
+ const active = this.entries.filter((e) => !e.decayed).sort((a, b) => a.id.localeCompare(b.id));
273
+ const digest = active.slice(-30).map((e) => `${e.kind}:${e.content.slice(0, 60)}:${e.importance.toFixed(2)}`).join('|');
274
+ return quickHash(digest);
275
+ }
276
+ }
277
+ /** 独立缓存助手 (mobile-runtime 用): 指纹 → 结果, 带 TTL */
278
+ export class MobileCache {
279
+ prefix;
280
+ constructor(prefix = 'bolloon_mobile_cache') { this.prefix = prefix; }
281
+ get(key, ttlMs = 60_000) {
282
+ try {
283
+ const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(`${this.prefix}_${key}`) : null;
284
+ if (!raw)
285
+ return null;
286
+ const parsed = JSON.parse(raw);
287
+ if (!parsed || !parsed.v || typeof parsed.at !== 'number')
288
+ return null;
289
+ if (Date.now() - parsed.at > ttlMs)
290
+ return null; // TTL 过期 → miss
291
+ return parsed.v;
292
+ }
293
+ catch {
294
+ return null;
295
+ }
296
+ }
297
+ set(key, value) {
298
+ try {
299
+ if (typeof localStorage !== 'undefined')
300
+ localStorage.setItem(`${this.prefix}_${key}`, JSON.stringify({ v: value, at: Date.now() }));
301
+ }
302
+ catch { /* 缓存写失败静默 */ }
303
+ }
304
+ clear() {
305
+ try {
306
+ const keys = [];
307
+ for (let i = 0; i < localStorage.length; i++) {
308
+ const k = localStorage.key(i);
309
+ if (k && k.startsWith(this.prefix))
310
+ keys.push(k);
311
+ }
312
+ for (const k of keys)
313
+ localStorage.removeItem(k);
314
+ }
315
+ catch { /* 忽略 */ }
316
+ }
317
+ }
@@ -0,0 +1,194 @@
1
+ let _orbit = null;
2
+ let _node = null;
3
+ /** 当前 OrbitDB 实例 (null = 未创建) */
4
+ export function getMobileOrbitDB() { return _orbit; }
5
+ /** 当前绑定的 helia 节点 (null = 未创建) */
6
+ export function getMobileOrbitNode() { return _node; }
7
+ /** 测试用: 重置实例 (不 stop 节点, 由调用方管) */
8
+ export function resetMobileOrbitDB() {
9
+ _orbit = null;
10
+ _node = null;
11
+ }
12
+ /**
13
+ * 创建手机端 OrbitDB (幂等)。
14
+ * 需要带 pubsub (gossipsub) 的 helia 节点 —— OrbitDB 4.x 的同步协议靠
15
+ * ipfs.libp2p.services.pubsub.addEventListener/subscribe。
16
+ * 不带 pubsub 的节点 (SDK 默认只启 identify) → open store 会抛
17
+ * "Cannot read properties of undefined (reading 'addEventListener')"。
18
+ * @returns { ok, orbit?, error? }
19
+ */
20
+ export async function createMobileOrbitDB(node) {
21
+ if (_orbit)
22
+ return { ok: true, orbit: _orbit };
23
+ try {
24
+ const activeNode = node ?? _node ?? null;
25
+ if (!activeNode)
26
+ return { ok: false, error: '需要 helia 节点 (先 startMobileHelia)' };
27
+ _node = activeNode;
28
+ // 动态 import @orbitdb/core (browser bundle; 避免顶层拖慢首屏)
29
+ const orbitMod = await import('@orbitdb/core');
30
+ // @orbitdb/core 的 browser bundle 类型导出不稳定 → 显式收成 any (运行时仍是同一函数)
31
+ const createOrbitDB = orbitMod.default ?? orbitMod.createOrbitDB;
32
+ if (typeof createOrbitDB !== 'function')
33
+ return { ok: false, error: '@orbitdb/core 没有 createOrbitDB 导出' };
34
+ // 用 MemoryStorage (浏览器无 fs) — KeyStore/日志都在内存
35
+ const orbit = await createOrbitDB({
36
+ ipfs: activeNode,
37
+ // 浏览器安全: 全部内存, 不落盘 (真持久化靠 IPFS 网络)
38
+ directory: undefined,
39
+ });
40
+ _orbit = orbit;
41
+ return { ok: true, orbit };
42
+ }
43
+ catch (e) {
44
+ return { ok: false, error: `创建 OrbitDB 失败: ${String(e?.message || e).slice(0, 200)}` };
45
+ }
46
+ }
47
+ /**
48
+ * 用带 pubsub 的节点创建 OrbitDB。
49
+ * 内部: 若当前节点没有 services.pubsub, 则用 mobile-helia 高级路径 (libp2pOptions + gossipsub)
50
+ * 重建一个, 替换当前节点。返回重建结果。
51
+ */
52
+ export async function createMobileOrbitDBWithPubsub(heliaMod) {
53
+ try {
54
+ const mh = heliaMod || (await import('./mobile-helia.js'));
55
+ // 先试现有节点 (可能已有 pubsub)
56
+ if (_node) {
57
+ const hasPubsub = !!(_node?.libp2p?.services?.pubsub);
58
+ if (hasPubsub) {
59
+ const r = await createMobileOrbitDB(_node);
60
+ return { ok: r.ok, orbit: r.orbit, node: _node, error: r.error };
61
+ }
62
+ }
63
+ // 没有 pubsub → 用 gossipsub 重建节点 (高级路径)
64
+ // 注意: buildCustomHelia 的 services 是 identify+ping(+dht), libp2pOptions 整体展开会覆盖 services
65
+ // → 必须带上 identify/ping 一起, 否则 circuit-relay transport 报 UnmetServiceDependenciesError
66
+ const { gossipsub } = await import('@libp2p/gossipsub');
67
+ const { identify } = await import('@libp2p/identify');
68
+ const { ping } = await import('@libp2p/ping');
69
+ const nodeR = await mh.createMobileHeliaNode({
70
+ libp2pOptions: {
71
+ services: {
72
+ identify: identify(), ping: ping(),
73
+ // allowPublishToZeroPeers: 单节点/刚启动时没有对端订阅也允许发布 (OrbitDB 4.x 同步需要);
74
+ // emitSelf: 自己发布的也回放给自己 (本地 store 一致性)
75
+ pubsub: gossipsub({ emitSelf: false, allowPublishToZeroTopicPeers: true }),
76
+ },
77
+ },
78
+ });
79
+ if (!nodeR.ok || !nodeR.node)
80
+ return { ok: false, error: nodeR.error || '重建带 pubsub 的节点失败' };
81
+ const r = await createMobileOrbitDB(nodeR.node);
82
+ return { ok: r.ok, orbit: r.orbit, node: nodeR.node, error: r.error };
83
+ }
84
+ catch (e) {
85
+ return { ok: false, error: `创建带 pubsub 的 OrbitDB 失败: ${String(e?.message || e).slice(0, 200)}` };
86
+ }
87
+ }
88
+ /** 打开 store (type: 'keyvalue' | 'documents' | 'events') */
89
+ export async function openStore(name, type = 'keyvalue', options = {}) {
90
+ try {
91
+ if (!_orbit) {
92
+ const r = await createMobileOrbitDB();
93
+ if (!r.ok)
94
+ return { ok: false, error: r.error };
95
+ }
96
+ const store = await _orbit.open(String(name || ''), { type, ...options });
97
+ return { ok: true, store };
98
+ }
99
+ catch (e) {
100
+ return { ok: false, error: `打开 store 失败: ${String(e?.message || e).slice(0, 200)}` };
101
+ }
102
+ }
103
+ /** kvstore: put */
104
+ export async function kvPut(store, key, value) {
105
+ try {
106
+ await store.put(String(key), value);
107
+ return { ok: true };
108
+ }
109
+ catch (e) {
110
+ return { ok: false, error: String(e?.message || e) };
111
+ }
112
+ }
113
+ /** kvstore: get */
114
+ export async function kvGet(store, key) {
115
+ try {
116
+ return { ok: true, value: await store.get(String(key)) };
117
+ }
118
+ catch (e) {
119
+ return { ok: false, error: String(e?.message || e) };
120
+ }
121
+ }
122
+ /** kvstore: del */
123
+ export async function kvDel(store, key) {
124
+ try {
125
+ await store.del(String(key));
126
+ return { ok: true };
127
+ }
128
+ catch (e) {
129
+ return { ok: false, error: String(e?.message || e) };
130
+ }
131
+ }
132
+ /** kvstore: 全部条目 */
133
+ export async function kvAll(store) {
134
+ try {
135
+ // @orbitdb/core 4.x kvstore: all() 返回 [{key, value, hash}] 数组
136
+ let all = {};
137
+ if (typeof store.all === 'function') {
138
+ const rows = (await store.all()) || [];
139
+ if (Array.isArray(rows)) {
140
+ for (const r of rows) {
141
+ if (r && r.key !== undefined)
142
+ all[String(r.key)] = r.value;
143
+ }
144
+ }
145
+ else if (typeof rows === 'object') {
146
+ all = rows;
147
+ }
148
+ }
149
+ else if (store.iterator) {
150
+ for (const [k, v] of (await store.iterator().collect())) {
151
+ if (k !== undefined)
152
+ all[String(k)] = v;
153
+ }
154
+ }
155
+ return { ok: true, entries: all };
156
+ }
157
+ catch (e) {
158
+ return { ok: false, error: String(e?.message || e) };
159
+ }
160
+ }
161
+ /** documents: 按索引查 (query(fn) — @orbitdb/core 4.x documents 的正确 API; indexBy 在 open 时声明) */
162
+ export async function docQuery(store, query) {
163
+ try {
164
+ let r;
165
+ if (typeof query === 'function') {
166
+ r = await store.query(query);
167
+ }
168
+ else if (query && typeof query === 'object') {
169
+ // 对象查询 → 转成匹配所有键值的函数 (与 indexBy 字段匹配)
170
+ const keys = Object.keys(query);
171
+ r = await store.query((doc) => keys.every((k) => doc?.[k] === query[k]));
172
+ }
173
+ else {
174
+ r = await store.get(String(query ?? ''));
175
+ }
176
+ return { ok: true, results: Array.isArray(r) ? r : (r ? [r] : []) };
177
+ }
178
+ catch (e) {
179
+ return { ok: false, error: String(e?.message || e) };
180
+ }
181
+ }
182
+ /** 关闭实例 (保留 helia 节点) */
183
+ export async function stopMobileOrbitDB() {
184
+ try {
185
+ if (_orbit)
186
+ await _orbit.stop();
187
+ _orbit = null;
188
+ return { ok: true };
189
+ }
190
+ catch {
191
+ _orbit = null;
192
+ return { ok: true };
193
+ }
194
+ }
@@ -0,0 +1,80 @@
1
+ // src/web/mobile-persona.ts
2
+ var PERSONA_KEY = "bolloon_mobile_persona";
3
+ var DEFAULT_PERSONA = {
4
+ name: "blln-mobile",
5
+ personality: "严谨、可靠、有边界感",
6
+ values: ["本地优先", "隐私优先", "诚实透明"],
7
+ interests: ["P2P 网络", "AI 智能体", "知识系统"],
8
+ style: "简洁中文,先给结论再给细节",
9
+ boundaries: ["不假装能做做不到的事", "不编造数据"],
10
+ extra: ""
11
+ };
12
+ function safeGet() {
13
+ try {
14
+ return window.localStorage;
15
+ } catch {
16
+ return globalThis.localStorage || { getItem: () => null, setItem: () => {
17
+ } };
18
+ }
19
+ }
20
+ function loadPersona() {
21
+ try {
22
+ const raw = safeGet().getItem(PERSONA_KEY);
23
+ if (!raw) return { ...DEFAULT_PERSONA };
24
+ const p = JSON.parse(raw);
25
+ return {
26
+ name: typeof p.name === "string" && p.name ? p.name : DEFAULT_PERSONA.name,
27
+ personality: typeof p.personality === "string" ? p.personality : DEFAULT_PERSONA.personality,
28
+ values: Array.isArray(p.values) ? p.values : DEFAULT_PERSONA.values,
29
+ interests: Array.isArray(p.interests) ? p.interests : DEFAULT_PERSONA.interests,
30
+ style: typeof p.style === "string" ? p.style : DEFAULT_PERSONA.style,
31
+ boundaries: Array.isArray(p.boundaries) ? p.boundaries : DEFAULT_PERSONA.boundaries,
32
+ extra: typeof p.extra === "string" ? p.extra : ""
33
+ };
34
+ } catch {
35
+ return { ...DEFAULT_PERSONA };
36
+ }
37
+ }
38
+ function savePersona(partial) {
39
+ const cur = loadPersona();
40
+ const next = {
41
+ name: typeof partial.name === "string" && partial.name.trim() ? partial.name.trim() : cur.name,
42
+ personality: typeof partial.personality === "string" ? partial.personality : cur.personality,
43
+ values: Array.isArray(partial.values) ? partial.values : cur.values,
44
+ interests: Array.isArray(partial.interests) ? partial.interests : cur.interests,
45
+ style: typeof partial.style === "string" ? partial.style : cur.style,
46
+ boundaries: Array.isArray(partial.boundaries) ? partial.boundaries : cur.boundaries,
47
+ extra: typeof partial.extra === "string" ? partial.extra : cur.extra
48
+ };
49
+ try {
50
+ safeGet().setItem(PERSONA_KEY, JSON.stringify(next));
51
+ } catch {
52
+ }
53
+ return next;
54
+ }
55
+ function resetPersona() {
56
+ try {
57
+ safeGet().removeItem(PERSONA_KEY);
58
+ } catch {
59
+ }
60
+ return { ...DEFAULT_PERSONA };
61
+ }
62
+ function buildPersonaPrompt(p = loadPersona()) {
63
+ const lines = [
64
+ `你的名字: ${p.name}`,
65
+ `性格: ${p.personality}`,
66
+ p.style ? `说话方式: ${p.style}` : "",
67
+ p.values.length ? `价值观: ${p.values.join("、")}` : "",
68
+ p.interests.length ? `兴趣: ${p.interests.join("、")}` : "",
69
+ p.boundaries.length ? `不做的事: ${p.boundaries.join("、")}` : "",
70
+ p.extra ? `补充: ${p.extra}` : ""
71
+ ].filter(Boolean);
72
+ return lines.join("\n");
73
+ }
74
+ export {
75
+ DEFAULT_PERSONA,
76
+ buildPersonaPrompt,
77
+ loadPersona,
78
+ resetPersona,
79
+ savePersona
80
+ };
@@ -53,9 +53,8 @@ export const PRIVACY_SUMMARY = [
53
53
  body: '蓝牙扫描与连接:发现并连接附近设备与好友(已声明 neverForLocation,不用于推断位置)。' +
54
54
  '位置权限:仅 Android 11 及以下系统要求蓝牙扫描需要它(已限制 maxSdkVersion=30,Android 12+ 不再申请)。' +
55
55
  '网络:P2P 连接与下载。相机:调用系统相机应用完成拍摄,本应用不申请相机权限。' +
56
- '完全访问权限(让智能体在这台手机上点按、滑动屏幕或调用系统能力)与 Shizuku:仅官网直装版本包含,默认开启' +
57
- '(首次使用会直接带你去系统开关页确认一次,之后一直有效)、随时可以关闭;' +
58
- '应用商店下载的版本不含这两项,也没有这个能力。',
56
+ '跨 App 点按、滑动屏幕或调用系统能力所需的 Android 无障碍与 Shizuku:仅官网直装版本包含,属于可选扩展能力;' +
57
+ '手机端基础 Agent 不依赖这些权限即可直接运行。应用商店下载的版本不含跨 App 控制能力。',
59
58
  },
60
59
  {
61
60
  title: '六、你的权利与注销',