@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.
@@ -86655,15 +86655,20 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86655
86655
  // src/web/mobile-agent.ts
86656
86656
  var mobile_agent_exports = {};
86657
86657
  __export(mobile_agent_exports, {
86658
+ MOBILE_JOIN_DOC_RE: () => MOBILE_JOIN_DOC_RE,
86658
86659
  callRemoteAgent: () => callRemoteAgent,
86659
86660
  cancelPhoneAgent: () => cancelPhoneAgent,
86660
86661
  default: () => mobile_agent_default,
86662
+ detectJoinDocUrl: () => detectJoinDocUrl,
86661
86663
  ensureIdentity: () => ensureIdentity,
86664
+ formatMobileJoinResult: () => formatMobileJoinResult,
86662
86665
  getLastWorklog: () => getLastWorklog,
86663
86666
  getLlmConfig: () => getLlmConfig,
86667
+ getMobileJoinState: () => getMobileJoinState,
86664
86668
  handleIncomingAgentMessage: () => handleIncomingAgentMessage,
86665
86669
  handleIncomingPhoneMessage: () => handleIncomingPhoneMessage,
86666
86670
  identityStatus: () => identityStatus,
86671
+ joinGatewayFromDoc: () => joinGatewayFromDoc,
86667
86672
  loginIdentity: () => loginIdentity,
86668
86673
  logoutIdentity: () => logoutIdentity,
86669
86674
  notifyAgentReply: () => notifyAgentReply,
@@ -86810,9 +86815,171 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
86810
86815
  } catch {
86811
86816
  }
86812
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
+ }
86813
86968
  async function runLocalAgent(goal) {
86814
86969
  const win = typeof window !== "undefined" ? window : null;
86815
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
+ }
86816
86983
  const bridge = cap && cap.Plugins && cap.Plugins.RokidBridge;
86817
86984
  if (bridge && cap.isNativePlatform?.()) {
86818
86985
  try {
@@ -87068,7 +87235,7 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
87068
87235
  } catch {
87069
87236
  }
87070
87237
  }
87071
- 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;
87072
87239
  var init_mobile_agent = __esm({
87073
87240
  "src/web/mobile-agent.ts"() {
87074
87241
  "use strict";
@@ -87078,6 +87245,8 @@ ${[...listenStats.errors.entries()].map(([addr, err2]) => {
87078
87245
  _identityDb = null;
87079
87246
  _llmConfig = null;
87080
87247
  _lastWorklog = [];
87248
+ MOBILE_JOIN_DOC_RE = /read\s+(https?:\/\/\S*bolloon-gateway-join\.md)/i;
87249
+ MOBILE_JOIN_STATE_KEY = "bolloon_gateway_join";
87081
87250
  _send = null;
87082
87251
  _ownDid = "";
87083
87252
  replyHandlers = /* @__PURE__ */ new Set();
@@ -162740,8 +162909,10 @@ ${error.stack}` : head;
162740
162909
  resolve(path) {
162741
162910
  const p = path || "";
162742
162911
  if (p === "/channels") return () => core.channels.get();
162912
+ if (p === "/api/data/snapshot") return () => core.data.snapshot();
162743
162913
  if (p === "/api/peers") return () => core.peers.list();
162744
162914
  if (p === "/api/mcp/tools") return () => core.mcp.tools();
162915
+ if (p === "/api/skills") return () => core.skills.list();
162745
162916
  if (p === "/api/auth/status") return () => core.identity.status();
162746
162917
  if (p === "/api/payments/pending") return () => core.payments.pending();
162747
162918
  if (p === "/api/llm-config") return () => core.data.getLlmConfig();
@@ -162787,6 +162958,10 @@ ${error.stack}` : head;
162787
162958
  /** POST 路径 → 内核函数 */
162788
162959
  resolvePost(path, body) {
162789
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
+ }
162790
162965
  if (p === "/message") {
162791
162966
  const b = body || {};
162792
162967
  return () => core.message.send({ text: b.text, channelId: b.channelId });
@@ -163485,6 +163660,17 @@ ${error.stack}` : head;
163485
163660
  { name: "gateway_call", description: "\u901A\u8FC7 Agent Gateway \u8C03\u7528\u670D\u52A1 (\u81EA\u52A8\u95ED\u73AF)" },
163486
163661
  { name: "gateway_join", description: "\u901A\u8FC7\u94FE\u63A5\u52A0\u5165\u5171\u4EAB Agent \u7F51\u7EDC" }
163487
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 : [];
163488
163674
  }
163489
163675
  },
163490
163676
  message: {
@@ -664,3 +664,27 @@ body {
664
664
  .list-icon .ico { width: 21px; height: 21px; }
665
665
 
666
666
  .icon-btn .ico { width: 20px; height: 20px; }
667
+
668
+ /* 供应商选择: 芯片式 (API 配置页, 2026-09-14) */
669
+ .provider-chips {
670
+ display: flex;
671
+ flex-wrap: wrap;
672
+ gap: 8px;
673
+ }
674
+ .provider-chip {
675
+ padding: 8px 14px;
676
+ border: 1px solid var(--border);
677
+ border-radius: 999px;
678
+ background: var(--bg-hover);
679
+ color: var(--text-secondary);
680
+ font-size: 13px;
681
+ line-height: 1.2;
682
+ -webkit-tap-highlight-color: transparent;
683
+ }
684
+ .provider-chip:active { transform: scale(.96); }
685
+ .provider-chip.active {
686
+ border-color: var(--accent);
687
+ background: var(--accent);
688
+ color: var(--bg);
689
+ font-weight: 600;
690
+ }
@@ -34,9 +34,10 @@
34
34
  <div id="app" class="app">
35
35
  <!-- 顶部导航 -->
36
36
  <header class="topbar">
37
+ <button class="icon-btn" id="btn-index" title="索引"><svg class="ico" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
37
38
  <div class="topbar-title" id="topbar-title">首页</div>
38
39
  <div class="topbar-actions" id="topbar-actions">
39
- <button class="icon-btn" id="btn-refresh" title="刷新"><svg class="ico" viewBox="0 0 24 24"><path d="M20 12a8 8 0 1 1-2.3-5.6"/><path d="M20 4v4.5h-4.5"/></svg></button>
40
+ <button class="icon-btn" id="btn-search" title="搜索"><svg class="ico" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6.5"/><path d="M16 16l4.5 4.5"/></svg></button>
40
41
  <button class="icon-btn" id="btn-add" title="添加会话"><svg class="ico" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></button>
41
42
  </div>
42
43
  </header>
@@ -60,25 +61,34 @@
60
61
  <div class="detail-body" id="detail-body"></div>
61
62
  </div>
62
63
  </main>
63
-
64
- <!-- 网络 tab -->
65
- <section class="page" id="page-network" data-tab="network" hidden>
64
+ <!-- 好友 tab: 好友与 P2P (原来散在网络页, 现已归拢; 网络页不再重复这些) -->
65
+ <section class="page" id="page-friends" data-tab="friends" hidden>
66
66
  <div class="list">
67
- <div class="list-item" id="item-join-global"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.7 2.6 2.7 14.4 0 17M12 3.5c-2.7 2.6-2.7 14.4 0 17"/><circle cx="12" cy="12" r="1.2" fill="currentColor" stroke="none"/></svg></span><span>一键入网 · 全球智能体网络</span><span class="list-arrow">›</span></div>
68
- <div class="list-item" id="item-join-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M3.5 9.5a13 13 0 0 1 17 0M6.5 13a8.5 8.5 0 0 1 11 0M9.6 16.3a4 4 0 0 1 4.8 0"/><circle cx="12" cy="19.5" r="1.1" fill="currentColor" stroke="none"/></svg></span><span>加入网络</span><span class="list-arrow">›</span></div>
69
67
  <div class="list-item" id="item-add-friend"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 19c.8-3 3-4.6 5.5-4.6S13.7 16 14.5 19"/><path d="M17 8v6M14 11h6"/></svg></span><span>连接好友</span><span class="list-arrow">›</span></div>
70
68
  <div class="list-item" id="item-nearby"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 16 0"/><path d="M7.5 12a4.5 4.5 0 0 1 9 0"/><circle cx="12" cy="12" r="1.4"/><path d="M12 13.6V19"/></svg></span><span>附近设备</span><span class="list-arrow">›</span></div>
71
69
  <div class="list-item" id="item-scan-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M4 8V6a2 2 0 0 1 2-2h2M16 4h2a2 2 0 0 1 2 2v2M20 16v2a2 2 0 0 1-2 2h-2M8 20H6a2 2 0 0 1-2-2v-2"/><path d="M4 12h16"/></svg></span><span>扫码入网 / 加好友</span></div>
72
70
  </div>
73
71
  <input type="file" id="qr-scan-input" accept="image/*" capture="environment" style="display:none">
74
- <div class="section-label">P2P 连接</div>
75
- <div class="list" id="p2p-status"></div>
76
- <div class="section-label">Agent 网络</div>
77
- <div class="list" id="net-members"></div>
78
72
  <div class="list">
79
73
  <div class="list-item" id="item-p2p"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.3 2.3 3.5 5.3 3.5 8.5s-1.2 6.2-3.5 8.5c-2.3-2.3-3.5-5.3-3.5-8.5S9.7 5.8 12 3.5z"/></svg></span><span>P2P 好友</span></div>
80
74
  <div class="list-item" id="item-p2p-id"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><rect x="3" y="5.5" width="18" height="13" rx="2.5"/><circle cx="8.5" cy="11" r="2"/><path d="M5.6 15.8a3.2 3.2 0 0 1 5.8 0M13.5 10h5M13.5 13.5h5"/></svg></span><span>我的 P2P ID</span></div>
81
75
  </div>
76
+ <div class="section-label">P2P 连接</div>
77
+ <div class="list" id="p2p-status"></div>
78
+ <div class="section-label">好友列表</div>
79
+ <div class="list" id="contacts-list"></div>
80
+ </section>
81
+
82
+ </main>
83
+
84
+ <!-- 网络 tab -->
85
+ <section class="page" id="page-network" data-tab="network" hidden>
86
+ <div class="list">
87
+ <div class="list-item" id="item-join-global"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.7 2.6 2.7 14.4 0 17M12 3.5c-2.7 2.6-2.7 14.4 0 17"/><circle cx="12" cy="12" r="1.2" fill="currentColor" stroke="none"/></svg></span><span>一键入网 · 全球智能体网络</span><span class="list-arrow">›</span></div>
88
+ <div class="list-item" id="item-join-net"><span class="list-icon"><svg class="ico" viewBox="0 0 24 24"><path d="M3.5 9.5a13 13 0 0 1 17 0M6.5 13a8.5 8.5 0 0 1 11 0M9.6 16.3a4 4 0 0 1 4.8 0"/><circle cx="12" cy="19.5" r="1.1" fill="currentColor" stroke="none"/></svg></span><span>加入网络</span><span class="list-arrow">›</span></div>
89
+ </div>
90
+ <div class="section-label">Agent 网络</div>
91
+ <div class="list" id="net-members"></div>
82
92
  <div class="section-label">Agent 服务 (协议自动发现)</div>
83
93
  <div class="list" id="agent-services"></div>
84
94
  <div class="list">
@@ -86,10 +96,8 @@
86
96
  </div>
87
97
  <div class="section-label">微信息 (微支付)</div>
88
98
  <div class="list" id="x402-info-list"></div>
89
- <div class="section-label">P2P 好友列表</div>
90
- <div class="list" id="contacts-list"></div>
91
- <div class="section-label">MCP 工具 (触控调用)</div>
92
- <div class="list" id="mcp-tools"></div>
99
+ <div class="section-label">智能体控制 (MCP / Skills)</div>
100
+ <div class="list" id="agent-control"></div>
93
101
  <div class="section-label">待人工审批支付</div>
94
102
  <div class="list" id="approval-list"></div>
95
103
  <div class="section-label">A2UI 动态面板 (智能体生成)</div>
@@ -122,6 +130,7 @@
122
130
  <!-- 底部 tab -->
123
131
  <nav class="tabbar" id="tabbar">
124
132
  <button class="tab active" data-tab="main"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="7" height="7" rx="1.6"/><rect x="13.5" y="3.5" width="7" height="7" rx="1.6"/><rect x="3.5" y="13.5" width="7" height="7" rx="1.6"/><rect x="13.5" y="13.5" width="7" height="7" rx="1.6"/></svg></span><span>首页</span></button>
133
+ <button class="tab" data-tab="friends"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 19c.8-3 3-4.6 5.5-4.6S13.7 16 14.5 19"/><path d="M17 8v6M14 11h6"/></svg></span><span>好友</span></button>
125
134
  <button class="tab" data-tab="network"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17"/><path d="M12 3.5c2.3 2.3 3.5 5.3 3.5 8.5s-1.2 6.2-3.5 8.5c-2.3-2.3-3.5-5.3-3.5-8.5S9.7 5.8 12 3.5z"/></svg></span><span>网络</span></button>
126
135
  <button class="tab" data-tab="me"><span class="tab-icon"><svg class="ico" viewBox="0 0 24 24"><circle cx="12" cy="8.5" r="4"/><path d="M4.5 20.5a7.5 7.5 0 0 1 15 0"/></svg></span><span>我</span></button>
127
136
  </nav>