@bolloon/bolloon-agent 0.6.6 → 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 (52) hide show
  1. package/dist/agents/ha-daemon.js +85 -0
  2. package/dist/agents/ha-supervisor.js +146 -0
  3. package/dist/agents/memory-store.js +25 -0
  4. package/dist/agents/opportunity-match.js +15 -3
  5. package/dist/agents/pi-sdk-session-manager.js +8 -0
  6. package/dist/agents/pi-sdk.js +67 -494
  7. package/dist/bootstrap/context-collector.js +65 -3
  8. package/dist/bootstrap/persona-loader.js +26 -0
  9. package/dist/electron-build/agents/memory-store.js +26 -0
  10. package/dist/electron-build/agents/memory-store.js.map +1 -1
  11. package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
  12. package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
  13. package/dist/electron-build/agents/pi-sdk.js +65 -492
  14. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  15. package/dist/electron-build/bootstrap/context-collector.js +66 -3
  16. package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
  17. package/dist/electron-build/bootstrap/persona-loader.js +27 -0
  18. package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
  19. package/dist/electron-build/llm/pi-ai.js +16 -3
  20. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  21. package/dist/electron-build/llm/system-prompt/registry.js +8 -6
  22. package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
  23. package/dist/ios/bolloon-web.json +3 -3
  24. package/dist/ios/index.html +19 -4
  25. package/dist/ios/mobile-agent.js +267 -41
  26. package/dist/ios/mobile-contacts.js +0 -4
  27. package/dist/ios/mobile-core.js +806 -30
  28. package/dist/ios/mobile-memory.js +317 -0
  29. package/dist/ios/mobile.html +19 -4
  30. package/dist/ios/mobile.js +144 -30
  31. package/dist/ios/routes-contacts.js +94 -1
  32. package/dist/ios/routes-world.js +9 -1
  33. package/dist/ios/server.js +51 -40
  34. package/dist/ios/sw.js +1 -1
  35. package/dist/kernel/gate-scan.js +1 -1
  36. package/dist/kernel/ha/ha-arbiter.js +177 -0
  37. package/dist/kernel/ha/ha-guard.js +151 -0
  38. package/dist/kernel/index.js +3 -0
  39. package/dist/kernel/plan.js +5 -5
  40. package/dist/kernel/roster.js +3 -3
  41. package/dist/llm/pi-ai.js +16 -3
  42. package/dist/llm/system-prompt/registry.js +8 -6
  43. package/dist/web/bolloon-web.json +3 -3
  44. package/dist/web/mobile-agent.js +265 -44
  45. package/dist/web/mobile-core.js +681 -32
  46. package/dist/web/mobile-memory.js +317 -0
  47. package/dist/web/mobile.html +4 -4
  48. package/dist/web/mobile.js +36 -27
  49. package/dist/web/routes-world.js +9 -1
  50. package/dist/web/server.js +50 -39
  51. package/dist/web/sw.js +1 -1
  52. package/package.json +1 -1
@@ -623,6 +623,307 @@
623
623
  }
624
624
  });
625
625
 
626
+ // src/web/mobile-memory.ts
627
+ var mobile_memory_exports = {};
628
+ __export(mobile_memory_exports, {
629
+ MobileCache: () => MobileCache,
630
+ MobileMemory: () => MobileMemory,
631
+ classifyRelation: () => classifyRelation,
632
+ heuristicImportance: () => heuristicImportance,
633
+ quickHash: () => quickHash,
634
+ validMemoryContent: () => validMemoryContent
635
+ });
636
+ function quickHash(text) {
637
+ let h3 = 2166136261;
638
+ for (let i = 0; i < String(text || "").length; i++) {
639
+ h3 ^= String(text || "").charCodeAt(i);
640
+ h3 = Math.imul(h3, 16777619);
641
+ }
642
+ return (h3 >>> 0).toString(36) + "_" + String(text || "").length.toString(36);
643
+ }
644
+ function validMemoryContent(content) {
645
+ return String(content || "").trim().length >= 2;
646
+ }
647
+ function heuristicImportance(content, kind = "fact") {
648
+ const t = String(content || "");
649
+ let score = 0.3;
650
+ if (t.length > 100) score += 0.2;
651
+ if (/喜欢|偏好|讨厌|禁止|必须|永远|从不|重要|注意|记住|约定|家人|朋友|同事/.test(t)) score += 0.25;
652
+ if (kind === "preference" || kind === "relationship") score += 0.2;
653
+ if (kind === "skill-feedback") score += 0.15;
654
+ return Math.min(1, score);
655
+ }
656
+ function classifyRelation(text, source = "") {
657
+ const t = String(text || "").toLowerCase();
658
+ const src4 = String(source || "").toLowerCase();
659
+ if (/家人|妈妈|爸爸|母亲|父亲|儿子|女儿|老婆|老公|妻子|丈夫|兄弟|姐妹/.test(t)) return "family";
660
+ if (/同事|客户|供应商|老板|领导|下属|同事|合作/.test(t)) return "work";
661
+ if (/朋友|好友|哥们|闺蜜|兄弟|认识的人/.test(t)) return "friend";
662
+ if (/directory|phone|手机号|通讯录/.test(src4)) return "acquaintance";
663
+ return "stranger";
664
+ }
665
+ var memKey, MobileMemory, MobileCache;
666
+ var init_mobile_memory = __esm({
667
+ "src/web/mobile-memory.ts"() {
668
+ "use strict";
669
+ memKey = (agentId) => `bolloon_mobile_memory_${agentId}`;
670
+ MobileMemory = class {
671
+ agentId;
672
+ entries = [];
673
+ loaded = false;
674
+ constructor(agentId) {
675
+ this.agentId = String(agentId || "");
676
+ }
677
+ key() {
678
+ return memKey(this.agentId);
679
+ }
680
+ readAll() {
681
+ try {
682
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem(this.key()) : null;
683
+ if (!raw) return [];
684
+ const parsed = JSON.parse(raw);
685
+ return Array.isArray(parsed) ? parsed : [];
686
+ } catch {
687
+ return [];
688
+ }
689
+ }
690
+ writeAll(list) {
691
+ try {
692
+ if (typeof localStorage !== "undefined") localStorage.setItem(this.key(), JSON.stringify(list.slice(-200)));
693
+ } catch {
694
+ }
695
+ }
696
+ ensureLoaded() {
697
+ if (!this.loaded) {
698
+ this.entries = this.readAll();
699
+ this.loaded = true;
700
+ }
701
+ }
702
+ /** 全部条目 (内部用) */
703
+ all() {
704
+ this.ensureLoaded();
705
+ return [...this.entries];
706
+ }
707
+ /** 落一条记忆 (幂等: 同 id 覆盖); 返回条目 */
708
+ remember(input) {
709
+ this.ensureLoaded();
710
+ const content = String(input.content || "").trim();
711
+ const source = input.source || "session";
712
+ if (!validMemoryContent(content)) {
713
+ const id2 = quickHash(this.agentId + "|" + content || "empty");
714
+ const stub = { id: id2, agentId: this.agentId, content: content || "\uFF08\u7A7A\uFF09", kind: input.kind || "fact", importance: 0, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), source, recallCount: 0 };
715
+ return stub;
716
+ }
717
+ const kind = input.kind || "fact";
718
+ const now = (/* @__PURE__ */ new Date()).toISOString();
719
+ const id = quickHash(this.agentId + "|" + content);
720
+ const importance = typeof input.importance === "number" ? Math.min(1, Math.max(0, input.importance)) : heuristicImportance(content, kind);
721
+ const entry = {
722
+ id,
723
+ agentId: this.agentId,
724
+ content,
725
+ kind,
726
+ importance,
727
+ createdAt: now,
728
+ updatedAt: now,
729
+ source,
730
+ ttlMs: input.ttlMs,
731
+ decayed: false,
732
+ crystallized: false,
733
+ recallCount: 0,
734
+ ...kind === "relationship" && input.relation ? { relation: input.relation } : {}
735
+ };
736
+ const idx = this.entries.findIndex((e) => e.id === id);
737
+ if (idx >= 0) {
738
+ const prev = this.entries[idx];
739
+ this.entries[idx] = { ...prev, ...entry, createdAt: prev.createdAt, recallCount: prev.recallCount, crystallized: prev.crystallized || entry.importance >= 0.8 };
740
+ if (input.relation && prev.relation) {
741
+ this.entries[idx].relation = {
742
+ did: input.relation.did || prev.relation.did,
743
+ kind: input.relation.kind || prev.relation.kind,
744
+ trust: Math.min(1, Math.max(0, (prev.relation.trust + input.relation.trust) / 2))
745
+ };
746
+ }
747
+ } else {
748
+ this.entries.push(entry);
749
+ }
750
+ this.writeAll(this.entries);
751
+ return this.entries.find((e) => e.id === id) || entry;
752
+ }
753
+ /** 召回: 关键词 + importance 加权; 命中即 +1 recallCount + 刷新 lastRecalledAt (迭代) */
754
+ recall(query, opts = {}) {
755
+ this.ensureLoaded();
756
+ this.applyTtl();
757
+ const q = String(query || "").toLowerCase();
758
+ const limit = opts.limit ?? 3;
759
+ const minScore = opts.minScore ?? 0;
760
+ const scored = [];
761
+ for (const e of this.entries) {
762
+ if (e.decayed) continue;
763
+ const c = String(e.content || "").toLowerCase();
764
+ let s2 = e.importance * 0.5;
765
+ const qTokens = q.split(/\s+/).filter((t) => t.length >= 2);
766
+ const hit = qTokens.filter((t) => c.includes(t)).length;
767
+ if (hit > 0) s2 += 0.3 * (hit / Math.max(qTokens.length, 1));
768
+ if (qTokens.length && qTokens.some((t) => c.includes(t))) s2 += 0.2;
769
+ if (e.relation && e.relation.trust >= 0.7) s2 += 0.1;
770
+ if (s2 >= minScore) scored.push({ e, s: s2 });
771
+ }
772
+ scored.sort((a, b) => b.s - a.s);
773
+ const hits = scored.slice(0, limit).map((x) => x.e);
774
+ for (const h3 of hits) {
775
+ const now = (/* @__PURE__ */ new Date()).toISOString();
776
+ h3.recallCount += 1;
777
+ h3.lastRecalledAt = now;
778
+ if (h3.recallCount >= 3) h3.crystallized = true;
779
+ }
780
+ if (hits.length) this.writeAll(this.entries);
781
+ return hits;
782
+ }
783
+ /** TTL 过期 → decayed (不删, 与桌面 applyDecay 同精神) */
784
+ applyTtl(nowIso) {
785
+ this.ensureLoaded();
786
+ const now = Date.parse(nowIso || (/* @__PURE__ */ new Date()).toISOString());
787
+ let changed = false;
788
+ for (const e of this.entries) {
789
+ if (e.decayed || e.crystallized || !e.ttlMs) continue;
790
+ const base5 = Date.parse(e.lastRecalledAt || e.createdAt) || now;
791
+ if (now - base5 > e.ttlMs) {
792
+ e.decayed = true;
793
+ changed = true;
794
+ }
795
+ }
796
+ if (changed) this.writeAll(this.entries);
797
+ }
798
+ /**
799
+ * 记忆迭代 (每轮循环后调用): 从「用户目标 + 工具结果 + 最终回复」提炼
800
+ * 新事实 / 偏好 / 关系。规则启发式 (零 LLM 成本): 信号词命中即存,
801
+ * 已经存过的同内容自动合并 (remember 幂等)。
802
+ */
803
+ autoIterate(input) {
804
+ this.ensureLoaded();
805
+ const parts = [String(input.goal || ""), ...input.toolResults || [], String(input.reply || "")];
806
+ const text = parts.filter(Boolean).join("\n");
807
+ if (!text.trim()) return 0;
808
+ let added = 0;
809
+ const pref = text.match(/(?:我|用户|本人)(?:喜欢|偏好|热爱|想要|希望).{2,40}?/g);
810
+ if (pref) {
811
+ for (const m2 of pref.slice(0, 3)) {
812
+ const content = m2.replace(/^(?:我|用户|本人)/, "").trim();
813
+ if (validMemoryContent(content)) {
814
+ this.remember({ content: "\u504F\u597D: " + content, kind: "preference", source: input.source || "iterate" });
815
+ added++;
816
+ }
817
+ }
818
+ }
819
+ const ban = text.match(/不要(?:再)?.{2,40}?|不要.{2,40}?|不能.{2,40}?|禁止.{2,40}?|记住.{2,40}?/g);
820
+ if (ban) {
821
+ for (const m2 of ban.slice(0, 2)) {
822
+ if (validMemoryContent(m2)) {
823
+ this.remember({ content: "\u7EA6\u5B9A: " + m2.trim(), kind: "insight", source: input.source || "iterate", importance: 0.6 });
824
+ added++;
825
+ }
826
+ }
827
+ }
828
+ const relMatch = text.match(/([一-龥]{2,6})是(?:我的)?(家人|妈妈|爸爸|老婆|老公|朋友|同事|客户|老板|领导|兄弟|姐妹|妻子|丈夫|合作伙伴)/);
829
+ if (relMatch) {
830
+ const name10 = relMatch[1];
831
+ const kind = classifyRelation(relMatch[0], input.source || "");
832
+ const content = `${name10} \u662F${relMatch[2]}`;
833
+ this.remember({
834
+ content,
835
+ kind: "relationship",
836
+ source: input.source || "iterate",
837
+ importance: 0.8,
838
+ relation: { did: "", kind, trust: 0.6 }
839
+ });
840
+ added++;
841
+ }
842
+ return added;
843
+ }
844
+ /** 快照 (run 结束/重启恢复用) */
845
+ snapshot() {
846
+ this.ensureLoaded();
847
+ this.applyTtl();
848
+ return { version: 1, agentId: this.agentId, entries: [...this.entries], savedAt: (/* @__PURE__ */ new Date()).toISOString() };
849
+ }
850
+ /** 从快照恢复 (bootstrap) — 合并, 不覆盖已有 */
851
+ restore(snap) {
852
+ if (!snap || !Array.isArray(snap.entries)) return;
853
+ this.ensureLoaded();
854
+ for (const e of snap.entries) {
855
+ const exists = this.entries.some((x) => x.id === e.id);
856
+ if (!exists) this.entries.push(e);
857
+ }
858
+ this.writeAll(this.entries);
859
+ }
860
+ /**
861
+ * 稳态记忆块: 固化优先 + 未淡化 + importance 降序, 始终注入 system。
862
+ * 与 recall(query) 互补: recall 是即时相关, stableBlock 是用户确认/多次命中的事实。
863
+ */
864
+ stableBlock(maxEntries = 6) {
865
+ this.ensureLoaded();
866
+ this.applyTtl();
867
+ const picked = this.entries.filter((e) => !e.decayed).sort((a, b) => {
868
+ if (a.crystallized !== b.crystallized) return a.crystallized ? -1 : 1;
869
+ return b.importance - a.importance;
870
+ }).slice(0, maxEntries);
871
+ if (!picked.length) return "";
872
+ const lines = picked.map((e) => {
873
+ const tag = `${e.kind}${e.crystallized ? "\u2605" : ""}`;
874
+ return `- (${tag}, ${e.importance.toFixed(2)}) ${e.content.slice(0, 120)}`;
875
+ });
876
+ return `<stable-memory>
877
+ ${lines.join("\n")}
878
+ </stable-memory>`;
879
+ }
880
+ /** 记忆指纹: 摘要 hash (参与上下文缓存 key; 记忆变了 → 指纹变 → 缓存 miss) */
881
+ fingerprint() {
882
+ this.ensureLoaded();
883
+ this.applyTtl();
884
+ const active = this.entries.filter((e) => !e.decayed).sort((a, b) => a.id.localeCompare(b.id));
885
+ const digest3 = active.slice(-30).map((e) => `${e.kind}:${e.content.slice(0, 60)}:${e.importance.toFixed(2)}`).join("|");
886
+ return quickHash(digest3);
887
+ }
888
+ };
889
+ MobileCache = class {
890
+ prefix;
891
+ constructor(prefix = "bolloon_mobile_cache") {
892
+ this.prefix = prefix;
893
+ }
894
+ get(key, ttlMs = 6e4) {
895
+ try {
896
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem(`${this.prefix}_${key}`) : null;
897
+ if (!raw) return null;
898
+ const parsed = JSON.parse(raw);
899
+ if (!parsed || !parsed.v || typeof parsed.at !== "number") return null;
900
+ if (Date.now() - parsed.at > ttlMs) return null;
901
+ return parsed.v;
902
+ } catch {
903
+ return null;
904
+ }
905
+ }
906
+ set(key, value2) {
907
+ try {
908
+ if (typeof localStorage !== "undefined") localStorage.setItem(`${this.prefix}_${key}`, JSON.stringify({ v: value2, at: Date.now() }));
909
+ } catch {
910
+ }
911
+ }
912
+ clear() {
913
+ try {
914
+ const keys = [];
915
+ for (let i = 0; i < localStorage.length; i++) {
916
+ const k = localStorage.key(i);
917
+ if (k && k.startsWith(this.prefix)) keys.push(k);
918
+ }
919
+ for (const k of keys) localStorage.removeItem(k);
920
+ } catch {
921
+ }
922
+ }
923
+ };
924
+ }
925
+ });
926
+
626
927
  // node_modules/@diap/sdk/dist/types/key.js
627
928
  var init_key = __esm({
628
929
  "node_modules/@diap/sdk/dist/types/key.js"() {
@@ -89730,8 +90031,10 @@ zoo`.split("\n");
89730
90031
  var mobile_agent_exports = {};
89731
90032
  __export(mobile_agent_exports, {
89732
90033
  MOBILE_JOIN_DOC_RE: () => MOBILE_JOIN_DOC_RE,
90034
+ adaptiveTimeoutMs: () => adaptiveTimeoutMs,
89733
90035
  callRemoteAgent: () => callRemoteAgent,
89734
90036
  cancelPhoneAgent: () => cancelPhoneAgent,
90037
+ classifyMobileError: () => classifyMobileError,
89735
90038
  default: () => mobile_agent_default,
89736
90039
  detectJoinDocUrl: () => detectJoinDocUrl,
89737
90040
  ensureIdentity: () => ensureIdentity,
@@ -89742,7 +90045,9 @@ zoo`.split("\n");
89742
90045
  handleIncomingAgentMessage: () => handleIncomingAgentMessage,
89743
90046
  handleIncomingPhoneMessage: () => handleIncomingPhoneMessage,
89744
90047
  identityStatus: () => identityStatus,
90048
+ isConfirmRequiredTool: () => isConfirmRequiredTool,
89745
90049
  joinGatewayFromDoc: () => joinGatewayFromDoc,
90050
+ listMobileRuns: () => listMobileRuns,
89746
90051
  loginIdentity: () => loginIdentity,
89747
90052
  logoutIdentity: () => logoutIdentity,
89748
90053
  notifyAgentReply: () => notifyAgentReply,
@@ -89756,6 +90061,7 @@ zoo`.split("\n");
89756
90061
  setAgentTransport: () => setAgentTransport,
89757
90062
  setLlmConfig: () => setLlmConfig,
89758
90063
  signWithIdentity: () => signWithIdentity,
90064
+ toolFingerprint: () => toolFingerprint,
89759
90065
  updateIdentityProfile: () => updateIdentityProfile,
89760
90066
  verifyIdentitySignature: () => verifyIdentitySignature
89761
90067
  });
@@ -90145,7 +90451,7 @@ ${r.steps.map((s3) => `${s3.ok ? "\u2713" : "\u2717"} ${s3.step}: ${s3.note}`).j
90145
90451
  try {
90146
90452
  const llm = getLlmConfig2();
90147
90453
  if (llm && llm.baseUrl) {
90148
- const reply = await runWebAgentLoop(goal, { baseUrl: llm.baseUrl, apiKey: llm.apiKey, model: llm.model, maxTokens: llm.maxTokens });
90454
+ const reply = await runWebAgentLoop(goal, { baseUrl: llm.baseUrl, apiKey: llm.apiKey, model: llm.model, maxTokens: llm.maxTokens }, { silentDegrade: true });
90149
90455
  if (reply) return reply;
90150
90456
  }
90151
90457
  } catch {
@@ -90180,38 +90486,105 @@ ${r.steps.map((s3) => `${s3.ok ? "\u2713" : "\u2717"} ${s3.step}: ${s3.note}`).j
90180
90486
  }
90181
90487
  return `\u5DF2\u6536\u5230: "${(goal || "").slice(0, 40)}"\u3002\u8FD9\u662F\u624B\u673A\u7AEF Agent \u529F\u80FD\u5C42\u7684\u672C\u5730\u6267\u884C (\u6570\u636E\u540C\u6B65\u4E0E agent \u529F\u80FD\u5DF2\u5206\u79BB)\u3002`;
90182
90488
  }
90183
- function buildAgentSystem() {
90489
+ function listMobileRuns(did, limit = 20) {
90490
+ try {
90491
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem(runsKey(did)) : null;
90492
+ if (!raw) return [];
90493
+ const parsed = JSON.parse(raw);
90494
+ return Array.isArray(parsed) ? parsed.slice(0, limit) : [];
90495
+ } catch {
90496
+ return [];
90497
+ }
90498
+ }
90499
+ function saveRunRecord(did, rec) {
90500
+ try {
90501
+ const prev = listMobileRuns(did, 50);
90502
+ const next = [rec, ...prev.filter((r) => r.runId !== rec.runId)].slice(0, 50);
90503
+ if (typeof localStorage !== "undefined") localStorage.setItem(runsKey(did), JSON.stringify(next));
90504
+ } catch {
90505
+ }
90506
+ }
90507
+ function toolFingerprint() {
90508
+ if (!_toolFingerprint) _toolFingerprint = quickHash("tools|" + toolNames());
90509
+ return _toolFingerprint;
90510
+ }
90511
+ function isConfirmRequiredTool(tool) {
90512
+ return CONFIRM_REQUIRED_TOOLS.includes(tool);
90513
+ }
90514
+ function buildAgentSystem(opts = {}) {
90184
90515
  const persona = loadPersona();
90185
90516
  const personaBlock = buildPersonaPrompt(persona);
90517
+ const userBlock = opts.userDoc && opts.userDoc.trim() ? `\u3010\u7528\u6237\u6863\u6848\u3011
90518
+ ${opts.userDoc.trim().slice(0, 400)}` : "";
90519
+ const memoryBlock = opts.memoryBlock && opts.memoryBlock.trim() ? `\u3010\u8BB0\u5FC6\u3011
90520
+ ${opts.memoryBlock.trim().slice(0, 600)}` : "";
90521
+ const stableBlock = opts.memory && opts.memory.stableBlock ? opts.memory.stableBlock(6) : "";
90522
+ const progressBlock = opts.progress && opts.progress.length ? `\u3010\u672C\u8F6E\u8FDB\u5EA6\u3011
90523
+ \u5DF2\u5B8C\u6210: ${opts.progress.slice(-8).map((s2, i) => `${i + 1}. ${s2.slice(0, 80)}`).join("\n")}` : "";
90524
+ const planHint = opts.plan && opts.plan.trim() ? `\u3010\u8BA1\u5212\u3011
90525
+ ${opts.plan.trim().slice(0, 300)}` : "\u4EFB\u52A1\u590D\u6742\u65F6\u5148\u5728\u5185\u5FC3\u62C6\u6B65\u9AA4, \u9010\u6B65\u6267\u884C, \u6BCF\u6B65\u7528\u4E00\u4E2A\u5DE5\u5177\u63A8\u8FDB\u3002";
90526
+ const channelBlock = opts.channel && opts.channel !== "text" ? `\u3010\u8F93\u5165\u901A\u9053\u3011
90527
+ \u672C\u6B21\u8F93\u5165\u662F${CHANNEL_LABEL[opts.channel]}\u3002\u4F60\u80FD\u7406\u89E3${CHANNEL_LABEL[opts.channel]}\u5185\u5BB9\u5E76\u636E\u6B64\u56DE\u7B54/\u6267\u884C\u3002` : "";
90186
90528
  return [
90187
90529
  "\u4F60\u662F\u624B\u673A\u7AEF Bolloon \u667A\u80FD\u4F53 (\u81EA\u6CBB\u8282\u70B9), \u7528\u4E2D\u6587\u7B80\u6D01\u56DE\u590D\u3002",
90188
90530
  personaBlock,
90531
+ userBlock,
90532
+ memoryBlock,
90533
+ stableBlock,
90189
90534
  '\u4F60\u6709\u4EE5\u4E0B\u5DE5\u5177, \u6309\u9700\u8C03\u7528 (JSON: {"tool":"\u540D\u5B57","args":{...}}), \u4E0D\u9700\u8981\u5DE5\u5177\u5C31\u76F4\u63A5\u56DE\u7B54:',
90190
- "- get_status: \u67E5\u672C\u673A\u8EAB\u4EFD/\u5165\u7F51/\u94B1\u5305/P2P \u72B6\u6001 (args: \u65E0)",
90535
+ "- get_status: \u67E5\u672C\u673A\u8EAB\u4EFD/\u5165\u7F51/\u94B1\u5305/\u8FDE\u63A5\u72B6\u6001 (args: \u65E0)",
90191
90536
  "- get_wallet: \u67E5\u94B1\u5305\u4F59\u989D (args: \u65E0)",
90192
- "- get_identity: \u67E5\u672C\u673A DID (args: \u65E0)",
90537
+ "- get_identity: \u67E5\u672C\u673A\u8EAB\u4EFD\u8BC1\u660E (args: \u65E0)",
90193
90538
  "- get_contacts: \u67E5\u8054\u7CFB\u65B9\u5F0F\u6388\u6743\u72B6\u6001 (args: \u65E0)",
90194
- "\u6BCF\u8F6E\u53EA\u80FD\u7528\u4E00\u4E2A\u5DE5\u5177; \u62FF\u5230\u7ED3\u679C\u540E\u7EE7\u7EED\u601D\u8003, \u76F4\u5230\u4EFB\u52A1\u5B8C\u6210\u7ED9\u51FA\u6700\u7EC8\u56DE\u7B54\u3002"
90195
- ].join("\n");
90539
+ "\u6BCF\u8F6E\u6700\u591A\u7528\u4E00\u4E2A\u5DE5\u5177; \u62FF\u5230\u7ED3\u679C\u540E\u7EE7\u7EED\u601D\u8003, \u76F4\u5230\u4EFB\u52A1\u5B8C\u6210\u7ED9\u51FA\u6700\u7EC8\u56DE\u7B54\u3002",
90540
+ progressBlock,
90541
+ planHint,
90542
+ channelBlock,
90543
+ "\u80FD\u52A8\u6027: \u5148\u52A8\u624B\u522B\u5148\u95EE; \u505A\u5230\u5E95 (\u4EA4\u4ED8=\u80FD\u7528\u7684\u7ED3\u679C); \u4E00\u4E2A\u5DE5\u5177\u4E0D\u591F\u5C31\u6362\u65B9\u5F0F\u7EE7\u7EED; \u88AB\u963B\u585E\u5982\u5B9E\u8BF4, \u4E0D\u7F16\u9020\u3002"
90544
+ ].filter(Boolean).join("\n");
90545
+ }
90546
+ function classifyMobileError(err2) {
90547
+ const msg = String(err2?.message || err2?.cause?.message || err2 || "");
90548
+ if (/timeout|timed out|AbortError|aborted/i.test(msg)) return { cls: "timeout", retryable: true, hint: "\u8BF7\u6C42\u8D85\u65F6, \u7A0D\u540E\u91CD\u8BD5" };
90549
+ if (/terminated|ECONNRESET|socket hang up|fetch failed|network|ETIMEDOUT|ECONNREFUSED|UND_ERR|Failed to fetch/i.test(msg)) return { cls: "network", retryable: true, hint: "\u7F51\u7EDC\u6CE2\u52A8, \u6B63\u5728\u91CD\u8BD5" };
90550
+ if (/401|403|quota|rate limit|API key|invalid api|HTTP 40\d|HTTP 429/i.test(msg)) return { cls: "api", retryable: false, hint: "\u63A5\u53E3/\u914D\u989D\u9519\u8BEF, \u65E0\u6CD5\u81EA\u52A8\u6062\u590D" };
90551
+ return { cls: "unknown", retryable: true, hint: "\u672A\u77E5\u9519\u8BEF, \u5C1D\u8BD5\u7EE7\u7EED" };
90552
+ }
90553
+ function adaptiveTimeoutMs(ctxChars, baseMs = 1e4) {
90554
+ const byLen = Math.ceil(Math.max(0, ctxChars) / 1024) * 4e3 + baseMs;
90555
+ return Math.max(baseMs, byLen);
90196
90556
  }
90197
90557
  async function webAgentLlmCall(messages2, llm) {
90198
- const resp = await fetch(llm.baseUrl.replace(/\/$/, "") + "/chat/completions", {
90199
- method: "POST",
90200
- headers: { "Content-Type": "application/json", "Authorization": "Bearer " + (llm.apiKey || "") },
90201
- body: JSON.stringify({
90202
- model: llm.model || "glm-5.3",
90203
- messages: messages2,
90204
- max_tokens: llm.maxTokens || 2048,
90205
- temperature: 0.4
90206
- }),
90207
- // eslint-disable-next-line no-undef
90208
- signal: AbortSignal.timeout(1e4)
90209
- });
90210
- if (!resp.ok) throw new Error("LLM HTTP " + resp.status + ": " + (await resp.text()).slice(0, 120));
90211
- const data = await resp.json();
90212
- const content = data?.choices?.[0]?.message?.content;
90213
- if (!content) throw new Error("LLM \u7A7A\u56DE\u590D");
90214
- return String(content).trim();
90558
+ const ctxChars = messages2.reduce((n, m2) => n + String(m2.content || "").length, 0);
90559
+ const timeoutMs = adaptiveTimeoutMs(ctxChars);
90560
+ let lastErr = null;
90561
+ for (let attempt = 0; attempt < 3; attempt++) {
90562
+ try {
90563
+ const resp = await fetch(llm.baseUrl.replace(/\/$/, "") + "/chat/completions", {
90564
+ method: "POST",
90565
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + (llm.apiKey || "") },
90566
+ body: JSON.stringify({
90567
+ model: llm.model || "glm-5.3",
90568
+ messages: messages2,
90569
+ max_tokens: llm.maxTokens || 2048,
90570
+ temperature: 0.4
90571
+ }),
90572
+ // eslint-disable-next-line no-undef
90573
+ signal: AbortSignal.timeout(timeoutMs)
90574
+ });
90575
+ if (!resp.ok) throw new Error("LLM HTTP " + resp.status + ": " + (await resp.text()).slice(0, 120));
90576
+ const data = await resp.json();
90577
+ const content = data?.choices?.[0]?.message?.content;
90578
+ if (!content) throw new Error("LLM \u7A7A\u56DE\u590D");
90579
+ return String(content).trim();
90580
+ } catch (e) {
90581
+ const cls = classifyMobileError(e);
90582
+ lastErr = e;
90583
+ if (cls.cls === "timeout" || cls.cls === "api" || attempt >= 2) throw e;
90584
+ await new Promise((r) => setTimeout(r, attempt === 0 ? 500 : 1500));
90585
+ }
90586
+ }
90587
+ throw lastErr || new Error("LLM \u8C03\u7528\u5931\u8D25");
90215
90588
  }
90216
90589
  function extractToolCall(raw) {
90217
90590
  if (!raw) return null;
@@ -90268,29 +90641,124 @@ ${r.steps.map((s3) => `${s3.ok ? "\u2713" : "\u2717"} ${s3.step}: ${s3.note}`).j
90268
90641
  }
90269
90642
  throw new Error("unbalanced");
90270
90643
  }
90271
- async function runWebAgentLoop(goal, llm) {
90644
+ async function runWebAgentLoop(goal, llm, opts = {}) {
90645
+ const id = await ensureIdentity();
90646
+ const did = id.did;
90647
+ const memory = opts.memory || new MobileMemory(did);
90648
+ memory.restore(memory.snapshot());
90649
+ const channel = opts.channel || "text";
90650
+ const runId = quickHash(did + "|" + goal + "|" + Date.now());
90651
+ const record = { runId, goal: String(goal || "").slice(0, 80), status: "running", steps: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), channel };
90652
+ saveRunRecord(did, record);
90653
+ const userDoc = opts.userDoc ?? [id.name ? `\u540D\u5B57: ${id.name}` : "", id.phone ? `\u624B\u673A\u53F7: ${id.phone}` : "", id.email ? `\u90AE\u7BB1: ${id.email}` : "", id.note ? `\u5907\u6CE8: ${id.note}` : ""].filter(Boolean).join("\n");
90654
+ const memoryBlock = memory.recall(String(goal || ""), { limit: 3 }).map((e) => e.content).join("\n");
90655
+ const sysCache = new MobileCache("bolloon_sys");
90656
+ const stableFp = memory.stableBlock(6);
90657
+ const sysFp = quickHash([
90658
+ userDoc,
90659
+ opts.soulDoc || "soul-default",
90660
+ memoryBlock,
90661
+ stableFp,
90662
+ toolFingerprint(),
90663
+ channel,
90664
+ opts.plan || ""
90665
+ ].join("|"));
90272
90666
  const messages2 = [
90273
- { role: "system", content: buildAgentSystem() },
90274
- { role: "user", content: goal }
90667
+ { role: "system", content: sysCache.get(sysFp, 10 * 6e4) || buildAgentSystem({ goal, memoryBlock, userDoc, soulDoc: opts.soulDoc, channel, plan: opts.plan, memory }) },
90668
+ { role: "user", content: String(goal || "") }
90275
90669
  ];
90276
- const MAX_STEPS = 5;
90670
+ sysCache.set(sysFp, messages2[0].content);
90671
+ const MAX_STEPS = 150;
90672
+ const NO_PROGRESS_LIMIT = 10;
90673
+ const COMPACT_AT = 30;
90277
90674
  const steps = [];
90675
+ let lastToolSig = "";
90676
+ let noProgress = 0;
90278
90677
  for (let i = 0; i < MAX_STEPS; i++) {
90279
- const raw = await webAgentLlmCall(messages2, llm);
90678
+ if (opts.signal?.aborted) {
90679
+ record.status = "cancelled";
90680
+ record.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
90681
+ saveRunRecord(did, record);
90682
+ _lastWorklog = [...steps, "\u26A0 \u5DF2\u53D6\u6D88"];
90683
+ return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C ${steps.length} \u6B65 \xB7 \u5DF2\u53D6\u6D88]`;
90684
+ }
90685
+ let raw;
90686
+ try {
90687
+ raw = await webAgentLlmCall(messages2, llm);
90688
+ } catch (e) {
90689
+ const hint = classifyMobileError(e).hint;
90690
+ record.status = "error";
90691
+ record.error = String(e?.message || e).slice(0, 120);
90692
+ record.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
90693
+ saveRunRecord(did, record);
90694
+ _lastWorklog = [...steps, `\u26A0 ${hint}`];
90695
+ if (!steps.length && opts.silentDegrade) throw e;
90696
+ if (!steps.length) return `(${hint}, \u8BF7\u7A0D\u540E\u91CD\u8BD5)`;
90697
+ return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C ${steps.length} \u6B65 \xB7 ${hint}]
90698
+ \u5DF2\u5B8C\u6210\u7684\u6B65\u9AA4\u7ED3\u679C\u5982\u4E0B:
90699
+ ${steps.join("\n")}
90700
+ (${hint}, \u8BF7\u7A0D\u540E\u91CD\u8BD5)`;
90701
+ }
90280
90702
  const toolJson = extractToolCall(raw);
90281
90703
  if (toolJson && typeof toolJson.tool === "string" && WEB_AGENT_TOOLS[toolJson.tool]) {
90282
- const out = await WEB_AGENT_TOOLS[toolJson.tool]();
90704
+ const sig = toolJson.tool + "|" + JSON.stringify(toolJson.args || {});
90705
+ noProgress = sig === lastToolSig ? noProgress + 1 : 0;
90706
+ lastToolSig = sig;
90707
+ if (noProgress >= NO_PROGRESS_LIMIT) {
90708
+ record.status = "done";
90709
+ record.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
90710
+ saveRunRecord(did, record);
90711
+ _lastWorklog = [...steps, `\u26A0 \u8FDE\u7EED ${NO_PROGRESS_LIMIT} \u8F6E\u91CD\u590D\u540C\u4E00\u5DE5\u5177\u8C03\u7528 (${toolJson.tool}), \u5224\u65AD\u6B7B\u5FAA\u73AF\u63D0\u524D\u505C\u6B62`];
90712
+ return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C ${steps.length} \u6B65 \xB7 \u68C0\u6D4B\u5230\u91CD\u590D\u52A8\u4F5C\u63D0\u524D\u505C\u6B62]
90713
+ ` + raw.slice(0, 200);
90714
+ }
90715
+ if (isConfirmRequiredTool(toolJson.tool)) {
90716
+ const allow = opts.confirm ? await opts.confirm(toolJson.tool, toolJson.args) : false;
90717
+ if (!allow) {
90718
+ const denied = `\u5DE5\u5177 ${toolJson.tool} \u9700\u8981\u786E\u8BA4, \u672C\u6B21\u672A\u6267\u884C (\u8BF7\u7528\u6237\u6279\u51C6\u540E\u91CD\u8BD5)\u3002`;
90719
+ steps.push(`\u{1F512} ${toolJson.tool} \u2192 ${denied}`);
90720
+ messages2.push({ role: "assistant", content: raw });
90721
+ messages2.push({ role: "user", content: `\u5DE5\u5177\u7ED3\u679C: ${denied}` });
90722
+ continue;
90723
+ }
90724
+ }
90725
+ const out = await WEB_AGENT_TOOLS[toolJson.tool](toolJson.args || {});
90283
90726
  steps.push(`\u{1F527} ${toolJson.tool} \u2192 ${out.slice(0, 80)}`);
90727
+ record.steps = steps.length;
90728
+ saveRunRecord(did, record);
90729
+ if (opts.onProgress) {
90730
+ try {
90731
+ opts.onProgress({ step: steps.length, tool: toolJson.tool, output: out.slice(0, 120) });
90732
+ } catch {
90733
+ }
90734
+ }
90735
+ if (steps.length > 60) steps.splice(0, steps.length - 60);
90284
90736
  messages2.push({ role: "assistant", content: raw });
90285
90737
  messages2.push({ role: "user", content: `\u5DE5\u5177\u7ED3\u679C: ${out}` });
90738
+ if (messages2.length > COMPACT_AT) {
90739
+ const keep = messages2.slice(0, 2);
90740
+ const recent = messages2.slice(-10);
90741
+ const summary = `\uFF08\u4E2D\u95F4 ${messages2.length - 12} \u8F6E\u5BF9\u8BDD\u5DF2\u538B\u7F29\uFF09\u5DF2\u5B8C\u6210\u6B65\u9AA4: ${steps.slice(-12).map((s2) => s2.slice(0, 60)).join(" | ")}`;
90742
+ keep.push({ role: "user", content: summary });
90743
+ messages2.length = 0;
90744
+ messages2.push(...keep, ...recent);
90745
+ }
90286
90746
  continue;
90287
90747
  }
90748
+ memory.autoIterate({ goal: String(goal || ""), toolResults: steps, reply: raw, source: "session" });
90749
+ memory.snapshot();
90750
+ record.status = "done";
90751
+ record.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
90752
+ saveRunRecord(did, record);
90288
90753
  _lastWorklog = steps.length ? [...steps, `\u{1F4AC} ${raw.slice(0, 60)}`] : [`\u{1F4AC} ${raw.slice(0, 60)}`];
90289
90754
  if (steps.length) return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C ${steps.length} \u6B65]
90290
90755
  ` + raw;
90291
90756
  return raw;
90292
90757
  }
90293
90758
  const last = messages2[messages2.length - 1]?.content || "";
90759
+ record.status = "done";
90760
+ record.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
90761
+ saveRunRecord(did, record);
90294
90762
  _lastWorklog = [...steps, "\u26A0 \u8FBE\u5230\u6B65\u6570\u4E0A\u9650, \u5DF2\u505C\u6B62"];
90295
90763
  return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C, \u8FBE\u5230 ${MAX_STEPS} \u6B65\u4E0A\u9650]
90296
90764
  ${last.slice(0, 200)}`;
@@ -90512,12 +90980,13 @@ ${last.slice(0, 200)}`;
90512
90980
  } catch {
90513
90981
  }
90514
90982
  }
90515
- var IDENTITY_DB, _identity, _keyMgr, _ed25519, _identityDb, BUILTIN_LLM, _llmConfig, _lastWorklog, MOBILE_JOIN_DOC_RE, MOBILE_JOIN_STATE_KEY, WEB_AGENT_TOOLS, _send2, _ownDid, replyHandlers, inboundChatHandlers, mobile_agent_default;
90983
+ var IDENTITY_DB, _identity, _keyMgr, _ed25519, _identityDb, BUILTIN_LLM, _llmConfig, _lastWorklog, MOBILE_JOIN_DOC_RE, MOBILE_JOIN_STATE_KEY, runsKey, toolNames, _toolFingerprint, CHANNEL_LABEL, CONFIRM_REQUIRED_TOOLS, WEB_AGENT_TOOLS, _send2, _ownDid, replyHandlers, inboundChatHandlers, mobile_agent_default;
90516
90984
  var init_mobile_agent = __esm({
90517
90985
  "src/web/mobile-agent.ts"() {
90518
90986
  "use strict";
90519
90987
  init_mobile_gateway();
90520
90988
  init_mobile_persona();
90989
+ init_mobile_memory();
90521
90990
  IDENTITY_DB = "bolloon-mobile";
90522
90991
  _identity = null;
90523
90992
  _keyMgr = null;
@@ -90533,11 +91002,21 @@ ${last.slice(0, 200)}`;
90533
91002
  _lastWorklog = [];
90534
91003
  MOBILE_JOIN_DOC_RE = /read\s+(https?:\/\/\S*bolloon-gateway-join\.md)/i;
90535
91004
  MOBILE_JOIN_STATE_KEY = "bolloon_gateway_join";
91005
+ runsKey = (did) => `bolloon_mobile_runs_${did}`;
91006
+ toolNames = () => Object.keys(WEB_AGENT_TOOLS).sort().join(",");
91007
+ _toolFingerprint = "";
91008
+ CHANNEL_LABEL = {
91009
+ text: "\u6587\u5B57",
91010
+ image: "\u56FE\u7247",
91011
+ audio: "\u8BED\u97F3",
91012
+ file: "\u6587\u4EF6"
91013
+ };
91014
+ CONFIRM_REQUIRED_TOOLS = [];
90536
91015
  WEB_AGENT_TOOLS = {
90537
91016
  get_status: async () => {
90538
91017
  try {
90539
91018
  const id = await ensureIdentity();
90540
- return `DID: ${id.did.slice(0, 16)}... \u540D\u5B57: ${id.name}`;
91019
+ return `\u8EAB\u4EFD: ${id.did.slice(0, 16)}... \u540D\u5B57: ${id.name}`;
90541
91020
  } catch (e) {
90542
91021
  return "\u8EAB\u4EFD\u8BFB\u53D6\u5931\u8D25: " + String(e?.message || e).slice(0, 60);
90543
91022
  }
@@ -90564,7 +91043,7 @@ ${last.slice(0, 200)}`;
90564
91043
  },
90565
91044
  get_identity: async () => {
90566
91045
  const id = await ensureIdentity();
90567
- return `DID: ${id.did} \u540D\u5B57: ${id.name}`;
91046
+ return `\u8EAB\u4EFD\u8BC1\u660E: ${id.did} \u540D\u5B57: ${id.name}`;
90568
91047
  },
90569
91048
  get_contacts: async () => {
90570
91049
  try {
@@ -169490,6 +169969,26 @@ ${joined}`);
169490
169969
  await agentLayer.handleIncomingPhoneMessage(type, msgBody, fromPeer);
169491
169970
  return;
169492
169971
  }
169972
+ if (type === "world.card") {
169973
+ try {
169974
+ const card = JSON.parse(msgBody);
169975
+ await core.world.pushNearbyCard(card, fromPeer);
169976
+ } catch {
169977
+ }
169978
+ return;
169979
+ }
169980
+ if (type === "world.card.request") {
169981
+ try {
169982
+ const feed = await core.world.cardFeed();
169983
+ const first2 = feed?.opportunities?.[0];
169984
+ if (first2) {
169985
+ const { sendMobileP2PMessage: sendMobileP2PMessage2 } = await Promise.resolve().then(() => (init_mobile_p2p(), mobile_p2p_exports));
169986
+ await sendMobileP2PMessage2(fromPeer, "world.card", JSON.stringify({ ...first2, fromOwn: true }), "");
169987
+ }
169988
+ } catch {
169989
+ }
169990
+ return;
169991
+ }
169493
169992
  } catch {
169494
169993
  }
169495
169994
  }
@@ -169573,6 +170072,8 @@ ${joined}`);
169573
170072
  if (p.startsWith("/api/opportunities?")) return () => core.world.opportunities(p.slice("/api/opportunities?".length));
169574
170073
  if (p === "/api/intents") return () => core.world.intents();
169575
170074
  if (p === "/api/world/search" || p.startsWith("/api/world/search?")) return () => core.world.search("");
170075
+ if (p === "/api/world/cards") return () => core.world.cardFeed();
170076
+ if (p === "/api/world/cards/request") return () => core.world.requestNearbyCards();
169576
170077
  if (p === "/api/orbit/status") return () => core.orbit.status();
169577
170078
  if (p === "/api/orbit/replica") return () => core.orbit.replica();
169578
170079
  if (p.startsWith("/sessions/")) {
@@ -169909,6 +170410,8 @@ ${joined}`);
169909
170410
  dataLayer.requestLlmConfigFromPeer(pid).catch(() => {
169910
170411
  });
169911
170412
  });
170413
+ core.world.requestNearbyCards().catch(() => {
170414
+ });
169912
170415
  return getMobileP2PState2();
169913
170416
  } catch (e) {
169914
170417
  return { connected: false, peerCount: 0, peerIds: [], error: String(e?.message || e).slice(0, 100) };
@@ -170279,6 +170782,103 @@ ${joined}`);
170279
170782
  } catch {
170280
170783
  return { ok: false, added: 0, results: [], note: "desktop-unreachable" };
170281
170784
  }
170785
+ },
170786
+ // ── 首页卡片动态传送 (2026-10-16, leo: 卡片根据「附近 + 手机号通讯录」传送) ──
170787
+ // 设计: 三源聚合, 不贴来源标签 (leo: 主动推送不贴元解释文案)。
170788
+ // ① 桌面机会流 (默认世界流, 现状)
170789
+ // ② 通讯录卡: 手机号目录里查过/加过的联系人 DID → 桌面 /api/opportunities?dids= 过滤
170790
+ // ③ 附近卡: P2P 已连接 peers 广播 world.card 投递 (动态传送, 不等轮询)
170791
+ // 不可达的源如实跳过 (不假装), 但其它源照常出卡 —— 首页永远有内容。
170792
+ async cardFeed() {
170793
+ const base5 = await core.world.baseUrl();
170794
+ let desktop = [];
170795
+ if (base5) {
170796
+ try {
170797
+ const r = await fetch(`${base5}/api/opportunities?min-score=0.2&limit=20`);
170798
+ if (r.ok) {
170799
+ const d2 = await r.json();
170800
+ if (d2 && Array.isArray(d2.opportunities)) desktop = d2.opportunities;
170801
+ }
170802
+ } catch {
170803
+ }
170804
+ }
170805
+ let directory = [];
170806
+ const contactDids = core.directory.contactDids();
170807
+ if (base5 && contactDids.length) {
170808
+ try {
170809
+ const r = await fetch(`${base5}/api/opportunities?dids=${encodeURIComponent(contactDids.join(","))}&min-score=0.2&limit=20`);
170810
+ if (r.ok) {
170811
+ const d2 = await r.json();
170812
+ if (d2 && Array.isArray(d2.opportunities)) directory = d2.opportunities;
170813
+ }
170814
+ } catch {
170815
+ }
170816
+ }
170817
+ const nearby = core.world.nearbyCards();
170818
+ const seen = /* @__PURE__ */ new Set();
170819
+ const merged = [...nearby, ...directory, ...desktop].filter((c) => {
170820
+ if (!c || !c.id) return false;
170821
+ if (seen.has(String(c.id))) return false;
170822
+ seen.add(String(c.id));
170823
+ return true;
170824
+ });
170825
+ return { ok: true, count: merged.length, opportunities: merged.slice(0, 12) };
170826
+ },
170827
+ /** 附近卡暂存 (P2P world.card 入站落这里; localStorage 持久化, 重启不丢) */
170828
+ nearbyCards() {
170829
+ try {
170830
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem("bolloon_nearby_cards") : null;
170831
+ if (!raw) return [];
170832
+ const parsed = JSON.parse(raw);
170833
+ return Array.isArray(parsed) ? parsed : [];
170834
+ } catch {
170835
+ return [];
170836
+ }
170837
+ },
170838
+ /** 收到附近卡 (world.card 入站 → 存 + 广播事件, 首页即时刷新, 不等 45s 轮询) */
170839
+ async pushNearbyCard(card, fromPeer) {
170840
+ if (!card || typeof card !== "object" || !card.id || !card.title) return;
170841
+ const entry = {
170842
+ ...card,
170843
+ id: `near_${String(fromPeer || "").slice(0, 8)}_${String(card.id).slice(0, 16)}`,
170844
+ createdAt: card.createdAt || Date.now()
170845
+ };
170846
+ const list = core.world.nearbyCards().filter((c) => c.id !== entry.id);
170847
+ list.unshift(entry);
170848
+ const keep = list.slice(0, 30);
170849
+ try {
170850
+ if (typeof localStorage !== "undefined") localStorage.setItem("bolloon_nearby_cards", JSON.stringify(keep));
170851
+ } catch {
170852
+ }
170853
+ try {
170854
+ busBroadcast({ type: "world-card", card: entry, fromPeer });
170855
+ } catch {
170856
+ }
170857
+ },
170858
+ /** 向已连接 peers 广播请求附近卡 (动态传送: 主动问一次, 对端回 world.card) */
170859
+ async requestNearbyCards() {
170860
+ try {
170861
+ const p2p = await Promise.resolve().then(() => (init_mobile_p2p(), mobile_p2p_exports));
170862
+ const conns = p2p.getMobileP2PConnections();
170863
+ if (!conns.length) return { ok: false, peers: 0 };
170864
+ let ownDid = "";
170865
+ try {
170866
+ const st = await core.identity.status();
170867
+ ownDid = st?.did || "";
170868
+ } catch {
170869
+ }
170870
+ let sent = 0;
170871
+ for (const c of conns) {
170872
+ try {
170873
+ const ok = await p2p.sendMobileP2PMessage(c.peer, "world.card.request", "1", ownDid);
170874
+ if (ok) sent++;
170875
+ } catch {
170876
+ }
170877
+ }
170878
+ return { ok: sent > 0, peers: sent };
170879
+ } catch {
170880
+ return { ok: false, peers: 0 };
170881
+ }
170282
170882
  }
170283
170883
  },
170284
170884
  // 手机端独立链上能力 (自己签名/发交易, 不需要电脑端; 需在设置里配 RPC)
@@ -170368,6 +170968,31 @@ ${joined}`);
170368
170968
  // 电脑端不可达 → 明确 desktop-unreachable (不假装有目录)。
170369
170969
  // 隐私: 明文手机号只随请求发给电脑端一次, 响应只回 DID/公钥/昵称 (与桌面红线一致)。
170370
170970
  directory: {
170971
+ /** 通讯录 DID 集合 (手机号目录查过/加过好友的联系人; localStorage 持久化) — 首页卡片传送用 */
170972
+ contactDids() {
170973
+ try {
170974
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem("bolloon_contact_dids") : null;
170975
+ if (!raw) return [];
170976
+ const parsed = JSON.parse(raw);
170977
+ return Array.isArray(parsed) ? parsed.filter((d2) => typeof d2 === "string" && d2) : [];
170978
+ } catch {
170979
+ return [];
170980
+ }
170981
+ },
170982
+ /** 记录一个联系 DID (查目录命中/加好友成功后调用) */
170983
+ rememberContactDid(did) {
170984
+ const d2 = String(did || "").trim();
170985
+ if (!d2) return;
170986
+ try {
170987
+ const list = core.directory.contactDids();
170988
+ if (!list.includes(d2)) {
170989
+ list.push(d2);
170990
+ const keep = list.slice(-50);
170991
+ if (typeof localStorage !== "undefined") localStorage.setItem("bolloon_contact_dids", JSON.stringify(keep));
170992
+ }
170993
+ } catch {
170994
+ }
170995
+ },
170371
170996
  async baseUrl() {
170372
170997
  try {
170373
170998
  const g = await Promise.resolve().then(() => (init_mobile_gateway(), mobile_gateway_exports));
@@ -170403,6 +171028,7 @@ ${joined}`);
170403
171028
  });
170404
171029
  const d2 = await r.json().catch(() => null);
170405
171030
  if (!r.ok) return { ok: false, found: false, error: d2 && (d2.error || d2.message) || `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
171031
+ if (d2 && d2.found && d2.did) core.directory.rememberContactDid(String(d2.did));
170406
171032
  return d2 || { ok: false, found: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
170407
171033
  } catch (e) {
170408
171034
  return { ok: false, found: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
@@ -170419,6 +171045,7 @@ ${joined}`);
170419
171045
  });
170420
171046
  const d2 = await r.json().catch(() => null);
170421
171047
  if (!r.ok) return { ok: false, status: r.status, error: d2 && (d2.error || d2.message) || `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
171048
+ if (d2 && d2.found && d2.did) core.directory.rememberContactDid(String(d2.did));
170422
171049
  return d2 || { ok: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
170423
171050
  } catch (e) {
170424
171051
  return { ok: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
@@ -170896,6 +171523,28 @@ ${joined}`);
170896
171523
  const r = await a.runWebAgentLoop(goal, { baseUrl: llm.baseUrl, apiKey: llm.apiKey, model: llm.model, maxTokens: llm.maxTokens });
170897
171524
  return r || "[\u5FAA\u73AF\u65E0\u8FD4\u56DE]";
170898
171525
  },
171526
+ // 2026-10-16 (leo: 手机原生运行内核): 记忆/台账/指纹/多模态 查询面
171527
+ async memory(goal) {
171528
+ const a = await Promise.resolve().then(() => (init_mobile_agent(), mobile_agent_exports));
171529
+ const id = await a.ensureIdentity();
171530
+ const { MobileMemory: MobileMemory2 } = await Promise.resolve().then(() => (init_mobile_memory(), mobile_memory_exports));
171531
+ const m2 = new MobileMemory2(id.did);
171532
+ m2.restore(m2.snapshot());
171533
+ if (goal) {
171534
+ const hits = m2.recall(String(goal), { limit: 5 });
171535
+ return { ok: true, hits: hits.map((e) => ({ content: e.content, kind: e.kind, importance: e.importance, relation: e.relation || null })), fingerprint: m2.fingerprint() };
171536
+ }
171537
+ return { ok: true, count: m2.all().length, fingerprint: m2.fingerprint() };
171538
+ },
171539
+ async runs() {
171540
+ const a = await Promise.resolve().then(() => (init_mobile_agent(), mobile_agent_exports));
171541
+ const id = await a.ensureIdentity();
171542
+ return { ok: true, runs: a.listMobileRuns(id.did) };
171543
+ },
171544
+ async toolFingerprint() {
171545
+ const a = await Promise.resolve().then(() => (init_mobile_agent(), mobile_agent_exports));
171546
+ return a.toolFingerprint();
171547
+ },
170899
171548
  // 2026-10-07: 持续工作 Harness (7 项能力) — 从 kernel 派生的长循环
170900
171549
  async runHarness(goal, opts) {
170901
171550
  const h3 = await Promise.resolve().then(() => (init_mobile_harness(), mobile_harness_exports));