@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.
- package/dist/agents/ha-daemon.js +85 -0
- package/dist/agents/ha-supervisor.js +146 -0
- package/dist/agents/memory-store.js +25 -0
- package/dist/agents/opportunity-match.js +15 -3
- package/dist/agents/pi-sdk-session-manager.js +8 -0
- package/dist/agents/pi-sdk.js +67 -494
- package/dist/bootstrap/context-collector.js +65 -3
- package/dist/bootstrap/persona-loader.js +26 -0
- package/dist/electron-build/agents/memory-store.js +26 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +65 -492
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +66 -3
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-loader.js +27 -0
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/llm/pi-ai.js +16 -3
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/system-prompt/registry.js +8 -6
- package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/index.html +19 -4
- package/dist/ios/mobile-agent.js +267 -41
- package/dist/ios/mobile-contacts.js +0 -4
- package/dist/ios/mobile-core.js +806 -30
- package/dist/ios/mobile-memory.js +317 -0
- package/dist/ios/mobile.html +19 -4
- package/dist/ios/mobile.js +144 -30
- package/dist/ios/routes-contacts.js +94 -1
- package/dist/ios/routes-world.js +9 -1
- package/dist/ios/server.js +51 -40
- package/dist/ios/sw.js +1 -1
- package/dist/kernel/gate-scan.js +1 -1
- package/dist/kernel/ha/ha-arbiter.js +177 -0
- package/dist/kernel/ha/ha-guard.js +151 -0
- package/dist/kernel/index.js +3 -0
- package/dist/kernel/plan.js +5 -5
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/pi-ai.js +16 -3
- package/dist/llm/system-prompt/registry.js +8 -6
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/mobile-agent.js +265 -44
- package/dist/web/mobile-core.js +681 -32
- package/dist/web/mobile-memory.js +317 -0
- package/dist/web/mobile.html +4 -4
- package/dist/web/mobile.js +36 -27
- package/dist/web/routes-world.js +9 -1
- package/dist/web/server.js +50 -39
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
package/dist/web/mobile-core.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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\
|
|
90195
|
-
|
|
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
|
|
90199
|
-
|
|
90200
|
-
|
|
90201
|
-
|
|
90202
|
-
|
|
90203
|
-
|
|
90204
|
-
|
|
90205
|
-
|
|
90206
|
-
|
|
90207
|
-
|
|
90208
|
-
|
|
90209
|
-
|
|
90210
|
-
|
|
90211
|
-
|
|
90212
|
-
|
|
90213
|
-
|
|
90214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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));
|