@bolloon/bolloon-agent 0.4.22 → 0.4.24

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.
@@ -86151,11 +86151,16 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86151
86151
  // src/web/mobile-p2p.ts
86152
86152
  var mobile_p2p_exports = {};
86153
86153
  __export(mobile_p2p_exports, {
86154
+ RELAY_HOP_PROTOCOL: () => RELAY_HOP_PROTOCOL,
86154
86155
  addMobilePeer: () => addMobilePeer,
86156
+ getMobileCircuitAddrs: () => getMobileCircuitAddrs,
86155
86157
  getMobileP2PConnections: () => getMobileP2PConnections,
86156
86158
  getMobileP2PState: () => getMobileP2PState,
86159
+ getMobileRelayReservations: () => getMobileRelayReservations,
86160
+ getMobileRelays: () => getMobileRelays,
86157
86161
  listMobilePeerAddrs: () => listMobilePeerAddrs,
86158
86162
  onMobileP2PMessage: () => onMobileP2PMessage,
86163
+ reserveMobileRelay: () => reserveMobileRelay,
86159
86164
  sendMobileP2PMessage: () => sendMobileP2PMessage,
86160
86165
  startMobileP2P: () => startMobileP2P
86161
86166
  });
@@ -86175,7 +86180,13 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86175
86180
  async function startMobileP2P(cfg = {}) {
86176
86181
  if (node) return state;
86177
86182
  try {
86183
+ const relayListen = (cfg.relayAddrs || []).map((a) => (a || "").trim().replace(/\/p2p-circuit\/?$/, "")).filter((a) => /\/p2p\/[^/]+$/.test(a)).map((a) => `${a}/p2p-circuit`);
86178
86184
  node = await createLibp2p({
86185
+ // 2026-09-11: 手机唯一的「可被拨入」途径 = 向中继预约。
86186
+ // listen /p2p-circuit 会在传输层 reserveRelay() 一个待预约名额; 之后只要连上
86187
+ // 一个广播了 hop 协议的中继, 就会自动预约并拿到 <relay>/p2p-circuit/p2p/<本机>。
86188
+ // (WebView 不能 listen ip4/ip6 → 只有这一类 listen 项是有意义的。)
86189
+ addresses: { listen: ["/p2p-circuit", ...relayListen] },
86179
86190
  transports: [webSockets(), circuitRelayTransport()],
86180
86191
  connectionEncrypters: [noise()],
86181
86192
  streamMuxers: [yamux()],
@@ -86232,6 +86243,16 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86232
86243
  console.warn(`[mobile-p2p] failed to dial saved ${addr}:`, String(e).slice(0, 100));
86233
86244
  }
86234
86245
  }
86246
+ for (const addr of cfg.relayAddrs || []) {
86247
+ if (getMobileCircuitAddrs().length > 0) break;
86248
+ const r = await reserveMobileRelay(addr);
86249
+ console.log(
86250
+ `[mobile-p2p] relay reserve ${addr} \u2192 ok=${r.ok} circuitAddrs=${r.circuitAddrs.length}` + (r.error ? ` err=${String(r.error).slice(0, 140)}` : "")
86251
+ );
86252
+ }
86253
+ if (getMobileCircuitAddrs().length > 0) {
86254
+ console.log(`[mobile-p2p] \u53EF\u62E8\u5165\u5730\u5740: ${getMobileCircuitAddrs().join(" , ")}`);
86255
+ }
86235
86256
  refreshState();
86236
86257
  return state;
86237
86258
  } catch (e) {
@@ -86317,6 +86338,58 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86317
86338
  function listMobilePeerAddrs() {
86318
86339
  return [...peerAddrs];
86319
86340
  }
86341
+ function getMobileCircuitAddrs() {
86342
+ if (!node) return [];
86343
+ try {
86344
+ return (node.getMultiaddrs() || []).map((a) => typeof a?.toString === "function" ? a.toString() : String(a)).filter((a) => a.includes("/p2p-circuit")).sort((a, b) => wsFirstRank(a) - wsFirstRank(b));
86345
+ } catch {
86346
+ return [];
86347
+ }
86348
+ }
86349
+ function getMobileRelays() {
86350
+ const out = [];
86351
+ for (const a of getMobileCircuitAddrs()) {
86352
+ const head = a.split("/p2p-circuit")[0] || "";
86353
+ const ids = head.match(/\/p2p\/([^/]+)/g) || [];
86354
+ const last = ids[ids.length - 1];
86355
+ const relay = last ? last.slice("/p2p/".length) : "";
86356
+ if (relay && !out.includes(relay)) out.push(relay);
86357
+ }
86358
+ return out;
86359
+ }
86360
+ function getMobileRelayReservations() {
86361
+ return relayReservationResults.map((r) => ({ ...r }));
86362
+ }
86363
+ async function reserveMobileRelay(relayAddr) {
86364
+ const raw = (relayAddr || "").trim();
86365
+ if (!node) return { ok: false, circuitAddrs: [], error: "P2P \u8282\u70B9\u672A\u542F\u52A8" };
86366
+ if (!raw) return { ok: false, circuitAddrs: [], error: "\u4E2D\u7EE7\u5730\u5740\u4E3A\u7A7A" };
86367
+ const base6 = raw.replace(/\/p2p-circuit\/?$/, "");
86368
+ if (!/\/p2p\/[^/]+$/.test(base6)) {
86369
+ const err2 = "\u4E2D\u7EE7\u5730\u5740\u5FC5\u987B\u5E26 /p2p/<\u4E2D\u7EE7PeerId> (\u6CA1\u6709 PeerId \u65E0\u6CD5\u9884\u7EA6)";
86370
+ relayReservationResults.push({ addr: raw, ok: false, error: err2 });
86371
+ return { ok: false, circuitAddrs: [], error: err2 };
86372
+ }
86373
+ try {
86374
+ const listenAddr = multiaddr(`${base6}/p2p-circuit`);
86375
+ if (typeof node.listen === "function") {
86376
+ await node.listen(listenAddr);
86377
+ } else if (node.components?.transportManager?.listen) {
86378
+ await node.components.transportManager.listen([listenAddr]);
86379
+ } else {
86380
+ throw new Error("\u672C\u7248\u672C libp2p \u65E2\u65E0 node.listen() \u4E5F\u65E0 components.transportManager.listen() \u2014\u2014 \u4E2D\u7EE7\u5730\u5740\u5FC5\u987B\u5728 createLibp2p \u7684 addresses.listen \u91CC\u7ED9");
86381
+ }
86382
+ const circuitAddrs = getMobileCircuitAddrs();
86383
+ const relay = getMobileRelays().slice(-1)[0];
86384
+ relayReservationResults.push({ addr: raw, ok: circuitAddrs.length > 0, relay, error: circuitAddrs.length ? void 0 : "\u9884\u7EA6\u8FD4\u56DE\u4F46\u672A\u62FF\u5230 /p2p-circuit \u5730\u5740" });
86385
+ return { ok: circuitAddrs.length > 0, circuitAddrs };
86386
+ } catch (e) {
86387
+ const err2 = String(e?.message || e).slice(0, 240);
86388
+ relayReservationResults.push({ addr: raw, ok: false, error: err2 });
86389
+ console.warn("[mobile-p2p] relay reservation failed:", err2);
86390
+ return { ok: false, circuitAddrs: getMobileCircuitAddrs(), error: err2 };
86391
+ }
86392
+ }
86320
86393
  async function addMobilePeer(addr) {
86321
86394
  const normalized = (addr || "").trim();
86322
86395
  if (!normalized) return { ok: false, error: "\u5730\u5740\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -86340,7 +86413,7 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86340
86413
  return { ok: false, error: "\u8FDE\u63A5\u5931\u8D25: " + String(e?.message || e).slice(0, 120) };
86341
86414
  }
86342
86415
  }
86343
- var node, state, msgHandlers, peerAddrs;
86416
+ var RELAY_HOP_PROTOCOL, node, state, msgHandlers, relayReservationResults, peerAddrs, wsFirstRank;
86344
86417
  var init_mobile_p2p = __esm({
86345
86418
  "src/web/mobile-p2p.ts"() {
86346
86419
  "use strict";
@@ -86354,13 +86427,24 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86354
86427
  init_src72();
86355
86428
  init_src74();
86356
86429
  init_src14();
86430
+ RELAY_HOP_PROTOCOL = "/libp2p/circuit/relay/0.2.0/hop";
86357
86431
  node = null;
86358
86432
  state = { connected: false, peerCount: 0, peerIds: [] };
86359
86433
  msgHandlers = [];
86434
+ relayReservationResults = [];
86360
86435
  peerAddrs = [];
86361
86436
  loadPeerAddrs();
86437
+ wsFirstRank = (a) => {
86438
+ const parts = a.split("/");
86439
+ return parts.includes("ws") || parts.includes("wss") ? 0 : 1;
86440
+ };
86362
86441
  if (typeof globalThis !== "undefined") {
86363
- globalThis.__mobileP2PStateSync = () => ({ ...state });
86442
+ globalThis.__mobileP2PStateSync = () => ({
86443
+ ...state,
86444
+ circuitAddrs: getMobileCircuitAddrs(),
86445
+ relays: getMobileRelays(),
86446
+ relayReservations: getMobileRelayReservations()
86447
+ });
86364
86448
  }
86365
86449
  }
86366
86450
  });
@@ -86571,15 +86655,20 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86571
86655
  // src/web/mobile-agent.ts
86572
86656
  var mobile_agent_exports = {};
86573
86657
  __export(mobile_agent_exports, {
86658
+ MOBILE_JOIN_DOC_RE: () => MOBILE_JOIN_DOC_RE,
86574
86659
  callRemoteAgent: () => callRemoteAgent,
86575
86660
  cancelPhoneAgent: () => cancelPhoneAgent,
86576
86661
  default: () => mobile_agent_default,
86662
+ detectJoinDocUrl: () => detectJoinDocUrl,
86577
86663
  ensureIdentity: () => ensureIdentity,
86664
+ formatMobileJoinResult: () => formatMobileJoinResult,
86578
86665
  getLastWorklog: () => getLastWorklog,
86579
86666
  getLlmConfig: () => getLlmConfig,
86667
+ getMobileJoinState: () => getMobileJoinState,
86580
86668
  handleIncomingAgentMessage: () => handleIncomingAgentMessage,
86581
86669
  handleIncomingPhoneMessage: () => handleIncomingPhoneMessage,
86582
86670
  identityStatus: () => identityStatus,
86671
+ joinGatewayFromDoc: () => joinGatewayFromDoc,
86583
86672
  loginIdentity: () => loginIdentity,
86584
86673
  logoutIdentity: () => logoutIdentity,
86585
86674
  notifyAgentReply: () => notifyAgentReply,
@@ -86726,9 +86815,171 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86726
86815
  } catch {
86727
86816
  }
86728
86817
  }
86818
+ function detectJoinDocUrl(text) {
86819
+ const m2 = MOBILE_JOIN_DOC_RE.exec(String(text || ""));
86820
+ return m2 ? m2[1] : null;
86821
+ }
86822
+ function parseFm(text) {
86823
+ const m2 = /^---\r?\n([\s\S]*?)\r?\n---/.exec(String(text || ""));
86824
+ if (!m2) return {};
86825
+ const out = {};
86826
+ for (const line of m2[1].split(/\r?\n/)) {
86827
+ const kv = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(line.trim());
86828
+ if (!kv) continue;
86829
+ const k = kv[1].toLowerCase();
86830
+ if (k === "name") out.name = kv[2].trim().replace(/^["']|["']$/g, "");
86831
+ if (k === "version") out.version = kv[2].trim().replace(/^["']|["']$/g, "");
86832
+ }
86833
+ return out;
86834
+ }
86835
+ async function getMobileJoinState() {
86836
+ try {
86837
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem(MOBILE_JOIN_STATE_KEY) : null;
86838
+ return raw ? JSON.parse(raw) : null;
86839
+ } catch {
86840
+ return null;
86841
+ }
86842
+ }
86843
+ function saveMobileJoinState(s2) {
86844
+ try {
86845
+ if (typeof localStorage !== "undefined") localStorage.setItem(MOBILE_JOIN_STATE_KEY, JSON.stringify(s2));
86846
+ } catch {
86847
+ }
86848
+ }
86849
+ async function joinGatewayFromDoc(docUrl, opts = {}) {
86850
+ const f = opts.fetchImpl || fetch;
86851
+ const steps = [];
86852
+ const url = String(docUrl || "").trim();
86853
+ if (!/^https?:\/\//i.test(url)) {
86854
+ return { ok: false, docUrl: url, steps, error: `\u5165\u7F51\u8BF4\u660E\u5730\u5740\u5FC5\u987B\u662F http(s) URL (\u6536\u5230: ${url.slice(0, 60)})` };
86855
+ }
86856
+ let docVersion;
86857
+ try {
86858
+ const r = await f(url, { signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3) });
86859
+ if (!r.ok) {
86860
+ steps.push({ step: "\u8BFB\u5165\u7F51\u8BF4\u660E", ok: false, note: `\u6587\u6863\u4E0D\u53EF\u8FBE (HTTP ${r.status})` });
86861
+ return { ok: false, docUrl: url, steps, error: `\u5165\u7F51\u8BF4\u660E\u4E0D\u53EF\u8FBE (HTTP ${r.status})` };
86862
+ }
86863
+ const text = await r.text();
86864
+ const fm = parseFm(text);
86865
+ if (fm.name !== "bolloon-gateway-join" && !/加入网关|bolloon-gateway-join/.test(text)) {
86866
+ steps.push({ step: "\u8BFB\u5165\u7F51\u8BF4\u660E", ok: false, note: `\u4E0D\u662F Bolloon \u5165\u7F51\u8BF4\u660E (name=${fm.name || "\u65E0"})` });
86867
+ return { ok: false, docUrl: url, steps, error: "\u8BE5\u6587\u6863\u4E0D\u662F Bolloon \u7F51\u5173\u5165\u7F51\u8BF4\u660E, \u62D2\u7EDD\u636E\u6B64\u5165\u7F51" };
86868
+ }
86869
+ docVersion = fm.version;
86870
+ steps.push({ step: "\u8BFB\u5165\u7F51\u8BF4\u660E", ok: true, note: `${fm.name || "bolloon-gateway-join"} v${fm.version || "?"} (${text.length} \u5B57\u7B26)` });
86871
+ } catch (e) {
86872
+ steps.push({ step: "\u8BFB\u5165\u7F51\u8BF4\u660E", ok: false, note: `\u8BFB\u53D6\u5931\u8D25: ${String(e?.message || e).slice(0, 120)}` });
86873
+ return { ok: false, docUrl: url, steps, error: `\u5165\u7F51\u8BF4\u660E\u8BFB\u53D6\u5931\u8D25: ${String(e?.message || e).slice(0, 120)}` };
86874
+ }
86875
+ let did = String(opts.did || "");
86876
+ if (did) {
86877
+ steps.push({ step: "DID \u8EAB\u4EFD", ok: true, note: `${did} (\u6CE8\u5165\u8EAB\u4EFD)` });
86878
+ } else {
86879
+ try {
86880
+ const id = await ensureIdentity();
86881
+ did = id.did;
86882
+ steps.push({ step: "DID \u8EAB\u4EFD", ok: true, note: `${did} (\u624B\u673A\u7AEF\u672C\u673A\u751F\u6210)` });
86883
+ } catch (e) {
86884
+ steps.push({ step: "DID \u8EAB\u4EFD", ok: false, note: String(e?.message || e).slice(0, 120) });
86885
+ return { ok: false, docUrl: url, docVersion, steps, error: "\u624B\u673A\u7AEF DID \u751F\u6210\u5931\u8D25" };
86886
+ }
86887
+ }
86888
+ const name10 = String(opts.name || "phone-agent");
86889
+ const capabilities = ["chat", "gateway-join"];
86890
+ let desktopBase = String(opts.desktopBaseUrl ?? "");
86891
+ if (opts.desktopBaseUrl === void 0) {
86892
+ try {
86893
+ const g = await Promise.resolve().then(() => (init_mobile_gateway(), mobile_gateway_exports));
86894
+ desktopBase = String(g.getDesktopBaseUrl() || "");
86895
+ } catch {
86896
+ desktopBase = "";
86897
+ }
86898
+ }
86899
+ desktopBase = desktopBase.replace(/\/+$/, "");
86900
+ let registeredOn = "local";
86901
+ if (desktopBase) {
86902
+ try {
86903
+ const r = await f(`${desktopBase}/api/registry/register`, {
86904
+ method: "POST",
86905
+ headers: { "content-type": "application/json" },
86906
+ body: JSON.stringify({
86907
+ agentId: did,
86908
+ name: name10,
86909
+ wallet: "",
86910
+ service: { name: "chat", description: "\u624B\u673A\u7AEF\u667A\u80FD\u4F53 (\u81EA\u8DB3\u8282\u70B9)", price: { amount: "0", currency: "USDC", per: "task" }, endpoint: "" },
86911
+ capabilities
86912
+ }),
86913
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
86914
+ });
86915
+ if (r.ok) {
86916
+ registeredOn = "desktop";
86917
+ steps.push({ step: "\u670D\u52A1\u767B\u8BB0", ok: true, note: `\u5DF2\u767B\u8BB0\u8FDB\u7535\u8111\u7AEF\u7F51\u7EDC registry (${desktopBase}) \u2014\u2014 \u7F51\u7EDC\u5185\u5176\u4ED6\u667A\u80FD\u4F53\u53EF\u6309\u80FD\u529B\u53D1\u73B0\u6211` });
86918
+ } else {
86919
+ steps.push({ step: "\u670D\u52A1\u767B\u8BB0", ok: false, note: `\u7535\u8111\u7AEF registry \u62D2\u7EDD (HTTP ${r.status}); \u5DF2\u6539\u4E3A\u672C\u673A\u767B\u8BB0` });
86920
+ }
86921
+ } catch (e) {
86922
+ steps.push({ step: "\u670D\u52A1\u767B\u8BB0", ok: false, note: `\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE (${String(e?.message || e).slice(0, 80)}); \u5DF2\u6539\u4E3A\u672C\u673A\u767B\u8BB0` });
86923
+ }
86924
+ } else {
86925
+ steps.push({ step: "\u670D\u52A1\u767B\u8BB0", ok: true, note: "\u672A\u914D\u7F6E\u7535\u8111\u7AEF\u57FA\u5740 \u2192 \u53EA\u5728\u672C\u673A\u767B\u8BB0 (\u624B\u673A\u662F\u81EA\u6CBB\u8282\u70B9; \u8BBE\u7F6E\u91CC\u586B\u7535\u8111\u7AEF\u5730\u5740\u53EF\u767B\u8BB0\u8FDB\u7F51\u7EDC registry)" });
86926
+ }
86927
+ try {
86928
+ const p2p = await Promise.resolve().then(() => (init_mobile_p2p(), mobile_p2p_exports));
86929
+ let peers = 0;
86930
+ try {
86931
+ peers = (p2p.getConnectedPeers?.() || []).length;
86932
+ } catch {
86933
+ peers = 0;
86934
+ }
86935
+ if (peers > 0 && typeof p2p.sendMobileP2PMessage === "function") {
86936
+ const okAnnounce = await p2p.sendMobileP2PMessage("*", "registry.register", JSON.stringify({ agent_id: did, name: name10, capabilities }), did);
86937
+ steps.push({ step: "P2P \u516C\u544A", ok: !!okAnnounce, note: okAnnounce ? `\u5DF2\u5411 ${peers} \u4E2A\u5BF9\u7AEF\u5E7F\u64AD\u672C\u673A\u58F0\u660E` : `\u5E7F\u64AD\u5931\u8D25 (\u5BF9\u7AEF ${peers} \u4E2A)` });
86938
+ } else {
86939
+ steps.push({ step: "P2P \u516C\u544A", ok: false, note: "\u5F53\u524D\u65E0\u5DF2\u8FDE\u63A5\u5BF9\u7AEF (\u6D4F\u89C8\u5668/\u672A\u8FDE\u7535\u8111\u7AEF\u65F6\u6B63\u5E38) \u2014 \u672C\u673A\u58F0\u660E\u5DF2\u5C31\u7EEA, \u8FDE\u4E0A\u5373\u751F\u6548" });
86940
+ }
86941
+ } catch (e) {
86942
+ steps.push({ step: "P2P \u516C\u544A", ok: false, note: `P2P \u5C42\u4E0D\u53EF\u7528: ${String(e?.message || e).slice(0, 80)}` });
86943
+ }
86944
+ saveMobileJoinState({ url, did, name: name10, capabilities, docVersion, registeredOn, desktopBaseUrl: desktopBase || void 0, joinedAt: (/* @__PURE__ */ new Date()).toISOString() });
86945
+ const state2 = await getMobileJoinState();
86946
+ steps.push({ step: "\u843D\u76D8\u5165\u7F51\u6001", ok: !!state2?.did, note: `localStorage:${MOBILE_JOIN_STATE_KEY}` });
86947
+ return { ok: true, docUrl: url, docVersion, did, steps };
86948
+ }
86949
+ function formatMobileJoinResult(r) {
86950
+ if (!r.ok) {
86951
+ return `\u274C \u5165\u7F51\u5931\u8D25: ${r.error || "\u672A\u77E5\u539F\u56E0"}
86952
+
86953
+ ${r.steps.map((s3) => `${s3.ok ? "\u2713" : "\u2717"} ${s3.step}: ${s3.note}`).join("\n")}`;
86954
+ }
86955
+ const s2 = r.steps.find((x) => x.step === "\u670D\u52A1\u767B\u8BB0");
86956
+ return [
86957
+ "\u2705 \u5DF2\u52A0\u5165\u5168\u7403\u667A\u80FD\u4F53\u7F51\u7EDC (\u624B\u673A\u7AEF\u81EA\u8DB3\u6267\u884C)",
86958
+ "",
86959
+ `DID: ${r.did}`,
86960
+ `\u5165\u7F51\u8BF4\u660E: v${r.docVersion || "?"} (${r.docUrl})`,
86961
+ s2 ? `\u767B\u8BB0: ${s2.note}` : "",
86962
+ "",
86963
+ ...r.steps.map((x) => `${x.ok ? "\u2713" : "\u2717"} ${x.step}: ${x.note}`),
86964
+ "",
86965
+ "\u7528\u300C\u7F51\u7EDC \u2192 Agent \u7F51\u7EDC\u300D\u53EF\u67E5\u770B\u6210\u5458; \u8BBE\u7F6E\u91CC\u586B\u7535\u8111\u7AEF\u5730\u5740\u53EF\u628A\u672C\u673A\u767B\u8BB0\u8FDB\u7F51\u7EDC registry\u3002"
86966
+ ].filter((l) => l !== "").join("\n");
86967
+ }
86729
86968
  async function runLocalAgent(goal) {
86730
86969
  const win = typeof window !== "undefined" ? window : null;
86731
86970
  const cap = win?.Capacitor;
86971
+ const joinDocUrl = detectJoinDocUrl(goal);
86972
+ if (joinDocUrl) {
86973
+ _lastWorklog = [`\u{1F9E9} \u8BC6\u522B\u4E3A\u5165\u7F51\u53E3\u4EE4: ${joinDocUrl}`];
86974
+ const r = await joinGatewayFromDoc(joinDocUrl).catch((e) => ({
86975
+ ok: false,
86976
+ docUrl: joinDocUrl,
86977
+ steps: [{ step: "\u5165\u7F51", ok: false, note: String(e?.message || e).slice(0, 120) }],
86978
+ error: String(e?.message || e)
86979
+ }));
86980
+ _lastWorklog = [..._lastWorklog, ...r.steps.map((s2) => `${s2.ok ? "\u2713" : "\u2717"} ${s2.step}: ${s2.note}`)];
86981
+ return formatMobileJoinResult(r);
86982
+ }
86732
86983
  const bridge = cap && cap.Plugins && cap.Plugins.RokidBridge;
86733
86984
  if (bridge && cap.isNativePlatform?.()) {
86734
86985
  try {
@@ -86984,7 +87235,7 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86984
87235
  } catch {
86985
87236
  }
86986
87237
  }
86987
- var IDENTITY_DB, _identity, _identityDb, _llmConfig, _lastWorklog, _send, _ownDid, replyHandlers, inboundChatHandlers, mobile_agent_default;
87238
+ var IDENTITY_DB, _identity, _identityDb, _llmConfig, _lastWorklog, MOBILE_JOIN_DOC_RE, MOBILE_JOIN_STATE_KEY, _send, _ownDid, replyHandlers, inboundChatHandlers, mobile_agent_default;
86988
87239
  var init_mobile_agent = __esm({
86989
87240
  "src/web/mobile-agent.ts"() {
86990
87241
  "use strict";
@@ -86994,6 +87245,8 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86994
87245
  _identityDb = null;
86995
87246
  _llmConfig = null;
86996
87247
  _lastWorklog = [];
87248
+ MOBILE_JOIN_DOC_RE = /read\s+(https?:\/\/\S*bolloon-gateway-join\.md)/i;
87249
+ MOBILE_JOIN_STATE_KEY = "bolloon_gateway_join";
86997
87250
  _send = null;
86998
87251
  _ownDid = "";
86999
87252
  replyHandlers = /* @__PURE__ */ new Set();
@@ -99765,13 +100018,21 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
99765
100018
  if (!r.ok) return { ok: false, addrs: [], error: `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
99766
100019
  const j = await r.json();
99767
100020
  const raw = Array.isArray(j?.wsAddrs) ? j.wsAddrs : [];
99768
- const addrs = raw.map((a) => {
99769
- let out = a;
99770
- if (host) out = out.replace(/\/ip4\/(0\.0\.0\.0|127\.0\.0\.1)\//, `/ip4/${host}/`);
99771
- out = out.replace(/\/ip6\/::1\//, `/ip4/${host || "127.0.0.1"}/`);
99772
- return out;
99773
- }).filter((a) => ip4Of(a) && !/\/ip4\/(0\.0\.0\.0|127\.0\.0\.1)\//.test(a));
99774
- return { ok: j?.ok !== false, peerId: j?.peerId || "", addrs: Array.from(new Set(addrs)) };
100021
+ const rewrite = (a) => {
100022
+ let out2 = a;
100023
+ if (host) out2 = out2.replace(/\/ip4\/(0\.0\.0\.0|127\.0\.0\.1)\//, `/ip4/${host}/`);
100024
+ out2 = out2.replace(/\/ip6\/::1\//, `/ip4/${host || "127.0.0.1"}/`);
100025
+ return out2;
100026
+ };
100027
+ const keep = (a) => ip4Of(a) && !/\/ip4\/(0\.0\.0\.0|127\.0\.0\.1)\//.test(a);
100028
+ const addrs = raw.map(rewrite).filter(keep);
100029
+ const relayRaw = Array.isArray(j?.relayAddrs) ? j.relayAddrs : [];
100030
+ const relayAddrs = relayRaw.map(rewrite).filter(keep);
100031
+ const out = { ok: j?.ok !== false, peerId: j?.peerId || "", addrs: Array.from(new Set(addrs)) };
100032
+ if (j?.isRelay === true) out.isRelay = true;
100033
+ if (relayAddrs.length) out.relayAddrs = Array.from(new Set(relayAddrs));
100034
+ if (typeof j?.relayProtocol === "string") out.relayProtocol = j.relayProtocol;
100035
+ return out;
99775
100036
  } catch (e) {
99776
100037
  return { ok: false, addrs: [], error: e?.message || String(e) };
99777
100038
  }
@@ -149882,6 +150143,32 @@ ${error.stack}` : head;
149882
150143
  return `error:${errMsg3(err2)}`;
149883
150144
  }
149884
150145
  }
150146
+ function relaysFromCircuitAddrs(circuitAddrs) {
150147
+ const out = [];
150148
+ for (const a of circuitAddrs) {
150149
+ const head = a.split("/p2p-circuit")[0] || "";
150150
+ const ids = head.match(/\/p2p\/([^/]+)/g) || [];
150151
+ const last = ids[ids.length - 1];
150152
+ const relay = last ? last.slice("/p2p/".length) : "";
150153
+ if (relay && !out.includes(relay)) out.push(relay);
150154
+ }
150155
+ return out;
150156
+ }
150157
+ function circuitInfoOf(libp2p) {
150158
+ try {
150159
+ const all2 = (libp2p?.getMultiaddrs?.() || []).map(
150160
+ (a) => a && typeof a.toString === "function" ? a.toString() : String(a)
150161
+ );
150162
+ const isWs = (a) => {
150163
+ const parts = a.split("/");
150164
+ return parts.includes("ws") || parts.includes("wss");
150165
+ };
150166
+ const circuitAddrs = all2.filter((a) => a.includes("/p2p-circuit")).sort((a, b) => (isWs(a) ? 0 : 1) - (isWs(b) ? 0 : 1));
150167
+ return { circuitAddrs, relays: relaysFromCircuitAddrs(circuitAddrs) };
150168
+ } catch {
150169
+ return { circuitAddrs: [], relays: [] };
150170
+ }
150171
+ }
149885
150172
  function verifyStarted(node2) {
149886
150173
  const peerId = peerIdOf(node2);
149887
150174
  const lps = libp2pStatusOf(node2);
@@ -150147,7 +150434,9 @@ ${error.stack}` : head;
150147
150434
  ok: true,
150148
150435
  running: false,
150149
150436
  peers: [],
150150
- libp2pStatus: "not-created"
150437
+ libp2pStatus: "not-created",
150438
+ circuitAddrs: [],
150439
+ relays: []
150151
150440
  };
150152
150441
  if (lastStartError) out.lastError = lastStartError;
150153
150442
  return out;
@@ -150157,6 +150446,7 @@ ${error.stack}` : head;
150157
150446
  const libp2pStatus = libp2pStatusOf(node2);
150158
150447
  const peerId = currentPeerId ?? peerIdOf(node2);
150159
150448
  const running = libp2pError ? false : libp2p ? libp2pStatus === "started" : node2.status === "started";
150449
+ const { circuitAddrs, relays } = circuitInfoOf(libp2p);
150160
150450
  let peers = [];
150161
150451
  try {
150162
150452
  const list = libp2p?.getPeers?.() || [];
@@ -150174,7 +150464,7 @@ ${error.stack}` : head;
150174
150464
  blockCount = void 0;
150175
150465
  }
150176
150466
  }
150177
- const out = { ok: true, running, peers, libp2pStatus };
150467
+ const out = { ok: true, running, peers, libp2pStatus, circuitAddrs, relays };
150178
150468
  if (peerId) out.peerId = peerId;
150179
150469
  if (blockCount !== void 0) out.blockCount = blockCount;
150180
150470
  if (lastStartError) out.lastError = lastStartError;
@@ -150186,6 +150476,8 @@ ${error.stack}` : head;
150186
150476
  running: false,
150187
150477
  peers: [],
150188
150478
  libp2pStatus: libp2pStatusOf(node2),
150479
+ circuitAddrs: [],
150480
+ relays: [],
150189
150481
  error: errDetail(err2)
150190
150482
  };
150191
150483
  if (lastStartError) out.lastError = lastStartError;
@@ -162617,8 +162909,10 @@ ${error.stack}` : head;
162617
162909
  resolve(path) {
162618
162910
  const p = path || "";
162619
162911
  if (p === "/channels") return () => core.channels.get();
162912
+ if (p === "/api/data/snapshot") return () => core.data.snapshot();
162620
162913
  if (p === "/api/peers") return () => core.peers.list();
162621
162914
  if (p === "/api/mcp/tools") return () => core.mcp.tools();
162915
+ if (p === "/api/skills") return () => core.skills.list();
162622
162916
  if (p === "/api/auth/status") return () => core.identity.status();
162623
162917
  if (p === "/api/payments/pending") return () => core.payments.pending();
162624
162918
  if (p === "/api/llm-config") return () => core.data.getLlmConfig();
@@ -162652,6 +162946,7 @@ ${error.stack}` : head;
162652
162946
  if (p === "/api/desktop/url") return () => core.desktop.url();
162653
162947
  if (p === "/api/desktop/sync") return () => core.desktop.sync();
162654
162948
  if (p === "/api/judgments/cached") return () => core.desktop.judgments();
162949
+ if (p === "/api/x402/info") return () => core.x402.list();
162655
162950
  if (p === "/api/orbit/status") return () => core.orbit.status();
162656
162951
  if (p === "/api/orbit/replica") return () => core.orbit.replica();
162657
162952
  if (p.startsWith("/sessions/")) {
@@ -162663,6 +162958,10 @@ ${error.stack}` : head;
162663
162958
  /** POST 路径 → 内核函数 */
162664
162959
  resolvePost(path, body) {
162665
162960
  const p = path || "";
162961
+ if (p === "/api/mcp/call") {
162962
+ const b = body || {};
162963
+ return () => core.mcp.call(String(b.name || ""), b.args || {});
162964
+ }
162666
162965
  if (p === "/message") {
162667
162966
  const b = body || {};
162668
162967
  return () => core.message.send({ text: b.text, channelId: b.channelId });
@@ -162752,6 +163051,10 @@ ${error.stack}` : head;
162752
163051
  const b = body || {};
162753
163052
  return () => core.desktop.setUrl(String(b.url || ""));
162754
163053
  }
163054
+ if (p === "/api/x402/info/buy") {
163055
+ const b = body || {};
163056
+ return () => core.x402.buy(b);
163057
+ }
162755
163058
  if (p === "/api/network/connect") return () => core.network.connect();
162756
163059
  if (p === "/api/social/announce") return () => core.social.announce();
162757
163060
  if (p === "/api/chain/config") {
@@ -162887,6 +163190,7 @@ ${error.stack}` : head;
162887
163190
  const dataLayer = await Promise.resolve().then(() => (init_mobile_data(), mobile_data_exports));
162888
163191
  const id = await agentLayer.ensureIdentity();
162889
163192
  let seeds = seedAddrs && seedAddrs.length ? seedAddrs : void 0;
163193
+ let relayAddrs;
162890
163194
  let desktopPeer = "";
162891
163195
  if (!seeds) {
162892
163196
  try {
@@ -162896,10 +163200,11 @@ ${error.stack}` : head;
162896
163200
  seeds = d2.addrs;
162897
163201
  desktopPeer = d2.peerId || "";
162898
163202
  }
163203
+ if (d2.ok && Array.isArray(d2.relayAddrs) && d2.relayAddrs.length) relayAddrs = d2.relayAddrs;
162899
163204
  } catch {
162900
163205
  }
162901
163206
  }
162902
- const st = await startMobileP2P2({ seedAddrs: seeds, ownDid: id.did });
163207
+ const st = await startMobileP2P2({ seedAddrs: seeds, ownDid: id.did, relayAddrs });
162903
163208
  if (desktopPeer) busBroadcast({ type: "p2p-desktop", peerId: desktopPeer, addrs: seeds || [] });
162904
163209
  try {
162905
163210
  const social = await Promise.resolve().then(() => (init_mobile_social(), mobile_social_exports));
@@ -163168,6 +163473,50 @@ ${error.stack}` : head;
163168
163473
  return { judgments: s2.getCachedJudgments() };
163169
163474
  }
163170
163475
  },
163476
+ // 微信息 (x402 付费信息) — 手机端**不持 EVM 私钥**: 浏览与代付一律转发电脑端;
163477
+ // 电脑端不可达就如实回 desktop-unreachable, 绝不返回假数据 (2026-09-13)
163478
+ x402: {
163479
+ /** 桌面基址 (设置页填; 与 mobile.js 的 desktopBaseUrl() 同一 localStorage key) */
163480
+ async baseUrl() {
163481
+ try {
163482
+ const g = await Promise.resolve().then(() => (init_mobile_gateway(), mobile_gateway_exports));
163483
+ return String(g.getDesktopBaseUrl() || "").replace(/\/+$/, "");
163484
+ } catch {
163485
+ return "";
163486
+ }
163487
+ },
163488
+ /** 免费元数据列表 (电脑端已发布的付费信息); 不可达 → {count:0,items:[],note:'desktop-unreachable'} */
163489
+ async list() {
163490
+ const base6 = await core.x402.baseUrl();
163491
+ if (!base6) return { count: 0, items: [], note: "desktop-unreachable" };
163492
+ try {
163493
+ const r = await fetch(`${base6}/api/x402/info`);
163494
+ if (!r.ok) return { count: 0, items: [], note: "desktop-unreachable" };
163495
+ const d2 = await r.json();
163496
+ if (!d2 || !Array.isArray(d2.items)) return { count: 0, items: [], note: "desktop-unreachable" };
163497
+ return d2;
163498
+ } catch {
163499
+ return { count: 0, items: [], note: "desktop-unreachable" };
163500
+ }
163501
+ },
163502
+ /** 购买并验真: 转发电脑端 /api/x402/info/buy 代付; 失败把后端 error 原文带回 (不吞错) */
163503
+ async buy(body) {
163504
+ const base6 = await core.x402.baseUrl();
163505
+ if (!base6) return { ok: false, error: "\u9700\u8981\u7535\u8111\u7AEF\u5728\u7EBF (\u8BBE\u7F6E\u91CC\u586B\u684C\u9762\u5730\u5740)" };
163506
+ try {
163507
+ const r = await fetch(`${base6}/api/x402/info/buy`, {
163508
+ method: "POST",
163509
+ headers: { "Content-Type": "application/json" },
163510
+ body: JSON.stringify(body || {})
163511
+ });
163512
+ const d2 = await r.json().catch(() => null);
163513
+ if (!r.ok) return { ok: false, status: r.status, error: d2 && (d2.error || d2.message) || `\u7535\u8111\u7AEF\u8FD4\u56DE ${r.status}` };
163514
+ return d2 || { ok: false, error: "\u7535\u8111\u7AEF\u8FD4\u56DE\u7A7A\u54CD\u5E94" };
163515
+ } catch (e) {
163516
+ return { ok: false, error: "\u7535\u8111\u7AEF\u4E0D\u53EF\u8FBE: " + String(e?.message || e).slice(0, 120) };
163517
+ }
163518
+ }
163519
+ },
163171
163520
  // 自动社交 (E1): 服务声明广播 / 发现 / 心跳 — 协议见 docs/wiki/agent-economic-protocol.md
163172
163521
  social: {
163173
163522
  async announce() {
@@ -163311,6 +163660,17 @@ ${error.stack}` : head;
163311
163660
  { name: "gateway_call", description: "\u901A\u8FC7 Agent Gateway \u8C03\u7528\u670D\u52A1 (\u81EA\u52A8\u95ED\u73AF)" },
163312
163661
  { name: "gateway_join", description: "\u901A\u8FC7\u94FE\u63A5\u52A0\u5165\u5171\u4EAB Agent \u7F51\u7EDC" }
163313
163662
  ];
163663
+ },
163664
+ async call(name10, args) {
163665
+ const { mobileGatewayTool: mobileGatewayTool2 } = await Promise.resolve().then(() => (init_mobile_gateway(), mobile_gateway_exports));
163666
+ return mobileGatewayTool2(String(name10 || ""), args || {});
163667
+ }
163668
+ },
163669
+ skills: {
163670
+ async list() {
163671
+ const s2 = await Promise.resolve().then(() => (init_mobile_sync(), mobile_sync_exports));
163672
+ const snap = s2.getLastSnapshot();
163673
+ return snap && Array.isArray(snap.skills) ? snap.skills : [];
163314
163674
  }
163315
163675
  },
163316
163676
  message: {