@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,36 +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
- });
90208
- if (!resp.ok) throw new Error("LLM HTTP " + resp.status + ": " + (await resp.text()).slice(0, 120));
90209
- const data = await resp.json();
90210
- const content = data?.choices?.[0]?.message?.content;
90211
- if (!content) throw new Error("LLM \u7A7A\u56DE\u590D");
90212
- 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");
90213
90588
  }
90214
90589
  function extractToolCall(raw) {
90215
90590
  if (!raw) return null;
@@ -90266,29 +90641,124 @@ ${r.steps.map((s3) => `${s3.ok ? "\u2713" : "\u2717"} ${s3.step}: ${s3.note}`).j
90266
90641
  }
90267
90642
  throw new Error("unbalanced");
90268
90643
  }
90269
- 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("|"));
90270
90666
  const messages2 = [
90271
- { role: "system", content: buildAgentSystem() },
90272
- { 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 || "") }
90273
90669
  ];
90274
- 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;
90275
90674
  const steps = [];
90675
+ let lastToolSig = "";
90676
+ let noProgress = 0;
90276
90677
  for (let i = 0; i < MAX_STEPS; i++) {
90277
- 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
+ }
90278
90702
  const toolJson = extractToolCall(raw);
90279
90703
  if (toolJson && typeof toolJson.tool === "string" && WEB_AGENT_TOOLS[toolJson.tool]) {
90280
- 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 || {});
90281
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);
90282
90736
  messages2.push({ role: "assistant", content: raw });
90283
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
+ }
90284
90746
  continue;
90285
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);
90286
90753
  _lastWorklog = steps.length ? [...steps, `\u{1F4AC} ${raw.slice(0, 60)}`] : [`\u{1F4AC} ${raw.slice(0, 60)}`];
90287
90754
  if (steps.length) return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C ${steps.length} \u6B65]
90288
90755
  ` + raw;
90289
90756
  return raw;
90290
90757
  }
90291
90758
  const last = messages2[messages2.length - 1]?.content || "";
90759
+ record.status = "done";
90760
+ record.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
90761
+ saveRunRecord(did, record);
90292
90762
  _lastWorklog = [...steps, "\u26A0 \u8FBE\u5230\u6B65\u6570\u4E0A\u9650, \u5DF2\u505C\u6B62"];
90293
90763
  return `[\u624B\u673A\u81EA\u6CBB\u6267\u884C, \u8FBE\u5230 ${MAX_STEPS} \u6B65\u4E0A\u9650]
90294
90764
  ${last.slice(0, 200)}`;
@@ -90510,12 +90980,13 @@ ${last.slice(0, 200)}`;
90510
90980
  } catch {
90511
90981
  }
90512
90982
  }
90513
- 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;
90514
90984
  var init_mobile_agent = __esm({
90515
90985
  "src/web/mobile-agent.ts"() {
90516
90986
  "use strict";
90517
90987
  init_mobile_gateway();
90518
90988
  init_mobile_persona();
90989
+ init_mobile_memory();
90519
90990
  IDENTITY_DB = "bolloon-mobile";
90520
90991
  _identity = null;
90521
90992
  _keyMgr = null;
@@ -90531,11 +91002,21 @@ ${last.slice(0, 200)}`;
90531
91002
  _lastWorklog = [];
90532
91003
  MOBILE_JOIN_DOC_RE = /read\s+(https?:\/\/\S*bolloon-gateway-join\.md)/i;
90533
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 = [];
90534
91015
  WEB_AGENT_TOOLS = {
90535
91016
  get_status: async () => {
90536
91017
  try {
90537
91018
  const id = await ensureIdentity();
90538
- return `DID: ${id.did.slice(0, 16)}... \u540D\u5B57: ${id.name}`;
91019
+ return `\u8EAB\u4EFD: ${id.did.slice(0, 16)}... \u540D\u5B57: ${id.name}`;
90539
91020
  } catch (e) {
90540
91021
  return "\u8EAB\u4EFD\u8BFB\u53D6\u5931\u8D25: " + String(e?.message || e).slice(0, 60);
90541
91022
  }
@@ -90562,7 +91043,7 @@ ${last.slice(0, 200)}`;
90562
91043
  },
90563
91044
  get_identity: async () => {
90564
91045
  const id = await ensureIdentity();
90565
- return `DID: ${id.did} \u540D\u5B57: ${id.name}`;
91046
+ return `\u8EAB\u4EFD\u8BC1\u660E: ${id.did} \u540D\u5B57: ${id.name}`;
90566
91047
  },
90567
91048
  get_contacts: async () => {
90568
91049
  try {
@@ -169488,6 +169969,26 @@ ${joined}`);
169488
169969
  await agentLayer.handleIncomingPhoneMessage(type, msgBody, fromPeer);
169489
169970
  return;
169490
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
+ }
169491
169992
  } catch {
169492
169993
  }
169493
169994
  }
@@ -169571,6 +170072,8 @@ ${joined}`);
169571
170072
  if (p.startsWith("/api/opportunities?")) return () => core.world.opportunities(p.slice("/api/opportunities?".length));
169572
170073
  if (p === "/api/intents") return () => core.world.intents();
169573
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();
169574
170077
  if (p === "/api/orbit/status") return () => core.orbit.status();
169575
170078
  if (p === "/api/orbit/replica") return () => core.orbit.replica();
169576
170079
  if (p.startsWith("/sessions/")) {
@@ -169602,6 +170105,30 @@ ${joined}`);
169602
170105
  const b = body || {};
169603
170106
  return () => core.identity.login(String(b.name || ""));
169604
170107
  }
170108
+ if (p === "/api/phone/directory/publish") {
170109
+ const b = body || {};
170110
+ return () => core.directory.publish(String(b.phone || ""), b.displayName ? String(b.displayName) : void 0);
170111
+ }
170112
+ if (p === "/api/phone/directory/lookup") {
170113
+ const b = body || {};
170114
+ return () => core.directory.lookup(String(b.phone || ""));
170115
+ }
170116
+ if (p === "/api/phone/directory/add-friend") {
170117
+ const b = body || {};
170118
+ return () => core.directory.addFriend(String(b.phone || ""), b.name ? String(b.name) : void 0, b.message ? String(b.message) : void 0);
170119
+ }
170120
+ if (p === "/api/phone/auth/bind") {
170121
+ const b = body || {};
170122
+ return () => core.phoneAuth.bind(String(b.phone || ""));
170123
+ }
170124
+ if (p === "/api/phone/auth/verify") {
170125
+ const b = body || {};
170126
+ return () => core.phoneAuth.verify(String(b.contactId || ""), String(b.challengeId || ""), String(b.code || ""));
170127
+ }
170128
+ if (p === "/api/phone/profile/phone") {
170129
+ const b = body || {};
170130
+ return () => core.identity.updateProfile({ phone: String(b.phone || "") });
170131
+ }
169605
170132
  if (p === "/api/wallet/export") {
169606
170133
  const b = body || {};
169607
170134
  return () => core.wallet.export(String(b.id || ""));
@@ -169883,6 +170410,8 @@ ${joined}`);
169883
170410
  dataLayer.requestLlmConfigFromPeer(pid).catch(() => {
169884
170411
  });
169885
170412
  });
170413
+ core.world.requestNearbyCards().catch(() => {
170414
+ });
169886
170415
  return getMobileP2PState2();
169887
170416
  } catch (e) {
169888
170417
  return { connected: false, peerCount: 0, peerIds: [], error: String(e?.message || e).slice(0, 100) };
@@ -170015,6 +170544,10 @@ ${joined}`);
170015
170544
  async logout() {
170016
170545
  const a = await Promise.resolve().then(() => (init_mobile_agent(), mobile_agent_exports));
170017
170546
  return a.logoutIdentity();
170547
+ },
170548
+ async updateProfile(partial) {
170549
+ const a = await Promise.resolve().then(() => (init_mobile_agent(), mobile_agent_exports));
170550
+ return a.updateIdentityProfile(partial || {});
170018
170551
  }
170019
170552
  },
170020
170553
  peers: {
@@ -170249,6 +170782,103 @@ ${joined}`);
170249
170782
  } catch {
170250
170783
  return { ok: false, added: 0, results: [], note: "desktop-unreachable" };
170251
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
+ }
170252
170882
  }
170253
170883
  },
170254
170884
  // 手机端独立链上能力 (自己签名/发交易, 不需要电脑端; 需在设置里配 RPC)
@@ -170334,6 +170964,130 @@ ${joined}`);
170334
170964
  return { ok: true, cid: await i.computeCid(value2) };
170335
170965
  }
170336
170966
  },
170967
+ // 手机号目录 (2026-10-15, 手机原生手机号功能): 手机端不持目录数据 → 转发电脑端既有端点。
170968
+ // 电脑端不可达 → 明确 desktop-unreachable (不假装有目录)。
170969
+ // 隐私: 明文手机号只随请求发给电脑端一次, 响应只回 DID/公钥/昵称 (与桌面红线一致)。
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
+ },
170996
+ async baseUrl() {
170997
+ try {
170998
+ const g = await Promise.resolve().then(() => (init_mobile_gateway(), mobile_gateway_exports));
170999
+ return String(g.getDesktopBaseUrl() || "").replace(/\/+$/, "");
171000
+ } catch {
171001
+ return "";
171002
+ }
171003
+ },
171004
+ async publish(phone, displayName) {
171005
+ const base5 = await core.directory.baseUrl();
171006
+ if (!base5) return { ok: false, error: "\u9700\u8981\u7535\u8111\u7AEF\u5728\u7EBF (\u8BBE\u7F6E\u91CC\u586B\u684C\u9762\u5730\u5740) \u624D\u80FD\u53D1\u5E03\u624B\u673A\u53F7\u76EE\u5F55" };
171007
+ try {
171008
+ const r = await fetch(`${base5}/api/contacts/directory/publish`, {
171009
+ method: "POST",
171010
+ headers: { "Content-Type": "application/json" },
171011
+ body: JSON.stringify({ phone, displayName: displayName || void 0 })
171012
+ });
171013
+ const d2 = await r.json().catch(() => null);
171014
+ if (!r.ok) return { ok: false, error: d2 && (d2.error || d2.message) || `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
171015
+ return d2 || { ok: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
171016
+ } catch (e) {
171017
+ return { ok: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
171018
+ }
171019
+ },
171020
+ async lookup(phone) {
171021
+ const base5 = await core.directory.baseUrl();
171022
+ if (!base5) return { ok: false, found: false, error: "\u9700\u8981\u7535\u8111\u7AEF\u5728\u7EBF (\u8BBE\u7F6E\u91CC\u586B\u684C\u9762\u5730\u5740) \u624D\u80FD\u67E5\u624B\u673A\u53F7\u76EE\u5F55" };
171023
+ try {
171024
+ const r = await fetch(`${base5}/api/contacts/directory/lookup`, {
171025
+ method: "POST",
171026
+ headers: { "Content-Type": "application/json" },
171027
+ body: JSON.stringify({ phone })
171028
+ });
171029
+ const d2 = await r.json().catch(() => null);
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));
171032
+ return d2 || { ok: false, found: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
171033
+ } catch (e) {
171034
+ return { ok: false, found: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
171035
+ }
171036
+ },
171037
+ async addFriend(phone, name10, message16) {
171038
+ const base5 = await core.directory.baseUrl();
171039
+ if (!base5) return { ok: false, error: "\u9700\u8981\u7535\u8111\u7AEF\u5728\u7EBF (\u8BBE\u7F6E\u91CC\u586B\u684C\u9762\u5730\u5740) \u624D\u80FD\u53D1\u8D77\u597D\u53CB\u7533\u8BF7" };
171040
+ try {
171041
+ const r = await fetch(`${base5}/api/contacts/directory/add-friend`, {
171042
+ method: "POST",
171043
+ headers: { "Content-Type": "application/json" },
171044
+ body: JSON.stringify({ phone, name: name10 || void 0, message: message16 || void 0 })
171045
+ });
171046
+ const d2 = await r.json().catch(() => null);
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));
171049
+ return d2 || { ok: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
171050
+ } catch (e) {
171051
+ return { ok: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
171052
+ }
171053
+ }
171054
+ },
171055
+ // 手机号登录 (2026-10-15): 手机号 + OTP 验证码登录 — 手机端把 bind/verify 转发电脑端,
171056
+ // 成功后用电脑端返回的身份绑定本机 identity (手机号即登录凭证, 不再只是昵称)。
171057
+ phoneAuth: {
171058
+ async bind(phone) {
171059
+ const base5 = await core.directory.baseUrl();
171060
+ if (!base5) return { ok: false, error: "\u9700\u8981\u7535\u8111\u7AEF\u5728\u7EBF (\u8BBE\u7F6E\u91CC\u586B\u684C\u9762\u5730\u5740) \u624D\u80FD\u53D1\u9001\u9A8C\u8BC1\u7801" };
171061
+ try {
171062
+ const r = await fetch(`${base5}/api/contacts/bind`, {
171063
+ method: "POST",
171064
+ headers: { "Content-Type": "application/json" },
171065
+ body: JSON.stringify({ kind: "phone", value: phone, region: "CN", source: "user_input" })
171066
+ });
171067
+ const d2 = await r.json().catch(() => null);
171068
+ if (!r.ok) return { ok: false, error: d2 && (d2.error || d2.message) || `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
171069
+ return d2 || { ok: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
171070
+ } catch (e) {
171071
+ return { ok: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
171072
+ }
171073
+ },
171074
+ async verify(contactId, challengeId, code10) {
171075
+ const base5 = await core.directory.baseUrl();
171076
+ if (!base5) return { ok: false, error: "\u9700\u8981\u7535\u8111\u7AEF\u5728\u7EBF (\u8BBE\u7F6E\u91CC\u586B\u684C\u9762\u5730\u5740) \u624D\u80FD\u9A8C\u8BC1\u9A8C\u8BC1\u7801" };
171077
+ try {
171078
+ const r = await fetch(`${base5}/api/contacts/verify`, {
171079
+ method: "POST",
171080
+ headers: { "Content-Type": "application/json" },
171081
+ body: JSON.stringify({ contactId, challengeId, code: code10 })
171082
+ });
171083
+ const d2 = await r.json().catch(() => null);
171084
+ if (!r.ok) return { ok: false, error: d2 && (d2.error || d2.message) || `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
171085
+ return d2 || { ok: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
171086
+ } catch (e) {
171087
+ return { ok: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
171088
+ }
171089
+ }
171090
+ },
170337
171091
  // 资源交易 (E2/E3/E4): 402 → 策略 → 支付 → 结果 → 信誉
170338
171092
  trade: {
170339
171093
  async trades(limit) {
@@ -170769,6 +171523,28 @@ ${joined}`);
170769
171523
  const r = await a.runWebAgentLoop(goal, { baseUrl: llm.baseUrl, apiKey: llm.apiKey, model: llm.model, maxTokens: llm.maxTokens });
170770
171524
  return r || "[\u5FAA\u73AF\u65E0\u8FD4\u56DE]";
170771
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
+ },
170772
171548
  // 2026-10-07: 持续工作 Harness (7 项能力) — 从 kernel 派生的长循环
170773
171549
  async runHarness(goal, opts) {
170774
171550
  const h3 = await Promise.resolve().then(() => (init_mobile_harness(), mobile_harness_exports));