soulnet-dsh 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/lib/client.js +2484 -658
  2. package/lib/client.js.map +1 -1
  3. package/lib/events-CLARJLU9.js +1126 -0
  4. package/lib/events-CLARJLU9.js.map +1 -0
  5. package/lib/group-contract-N1I1OV1e.js +409 -0
  6. package/lib/group-contract-N1I1OV1e.js.map +1 -0
  7. package/lib/index.js +820 -805
  8. package/lib/index.js.map +1 -1
  9. package/lib/sessions.js +712 -26
  10. package/lib/sessions.js.map +1 -1
  11. package/lib/tools.js +398 -4
  12. package/lib/tools.js.map +1 -1
  13. package/lib/types/api/index.d.ts +3 -0
  14. package/lib/types/client/AgentPane.d.ts +1 -3
  15. package/lib/types/client/AlterPane.d.ts +3 -3
  16. package/lib/types/client/DraftCard.d.ts +3 -1
  17. package/lib/types/client/FriendList.d.ts +3 -1
  18. package/lib/types/client/GroupCreateDialog.d.ts +3 -1
  19. package/lib/types/client/MemoryNotification.d.ts +4 -0
  20. package/lib/types/client/MemoryPane.d.ts +25 -0
  21. package/lib/types/client/SettingsSection.d.ts +11 -3
  22. package/lib/types/client/SoulmirrorPage.d.ts +1 -3
  23. package/lib/types/client/UpdateAction.d.ts +5 -0
  24. package/lib/types/client/UpdateTrigger.d.ts +5 -0
  25. package/lib/types/client/alter-card.d.ts +3 -5
  26. package/lib/types/client/alter-ui.d.ts +2 -1
  27. package/lib/types/client/api.d.ts +134 -3
  28. package/lib/types/client/dir-picker.d.ts +3 -0
  29. package/lib/types/client/inbox-state.d.ts +4 -2
  30. package/lib/types/client/locales.d.ts +101 -14
  31. package/lib/types/client/page-state.d.ts +4 -5
  32. package/lib/types/client/page-store.d.ts +2 -2
  33. package/lib/types/friend-settings.d.ts +3 -0
  34. package/lib/types/group-contract.d.ts +37 -0
  35. package/lib/types/group-settings.d.ts +4 -0
  36. package/lib/types/index.d.ts +4 -0
  37. package/lib/types/memory/extract.d.ts +39 -0
  38. package/lib/types/memory/store.d.ts +67 -0
  39. package/lib/types/network/paygate.d.ts +61 -0
  40. package/lib/types/network/soulnet.d.ts +14 -0
  41. package/lib/types/network/types.d.ts +85 -4
  42. package/lib/types/persona.d.ts +2 -0
  43. package/lib/types/sessions/index.d.ts +42 -0
  44. package/lib/types/settings.d.ts +29 -0
  45. package/lib/types-zwvjR1n5.js.map +1 -1
  46. package/package.json +11 -6
package/lib/client.js CHANGED
@@ -237,7 +237,7 @@ window.__ModuleLoader__.load({
237
237
  },
238
238
  "--dsw-alias-label-primary-inverted": {
239
239
  light: "#ffffff",
240
- dark: "#ffffff"
240
+ dark: "#0f1115"
241
241
  },
242
242
  "--dsw-alias-brand-primary": {
243
243
  light: "#0f1115",
@@ -488,6 +488,7 @@ window.__ModuleLoader__.load({
488
488
  notice: {
489
489
  id: message.id,
490
490
  fp: message.from,
491
+ gid: frame.gid,
491
492
  name: `${message.name} · ${group.name}`,
492
493
  body: message.body,
493
494
  ts: message.ts,
@@ -639,6 +640,8 @@ window.__ModuleLoader__.load({
639
640
  }
640
641
  const api = {
641
642
  state: () => call("state"),
643
+ /** Wallet status for the create-group UI (paid join needs a wallet). */
644
+ payWallet: () => call("pay.wallet"),
642
645
  /** Self-upgrade: is a newer soulnet-dsh published? */
643
646
  upgradeCheck: () => call("upgrade.check", {}),
644
647
  /** Self-upgrade: install `soulnet-dsh@version` (pnpm may take minutes — long timeout). */
@@ -679,6 +682,18 @@ window.__ModuleLoader__.load({
679
682
  presence: (fps) => call("presence", { fps: [...fps] }),
680
683
  /** The owner instructs their alter (an owner user/message + a woken turn in the alter session). */
681
684
  instruct: (text) => call("alter.instruct", { text }),
685
+ /** Cancel (delete) extracted pre-memories by uid. */
686
+ cancelMemory: (ids) => call("memory.cancel", { ids: [...ids] }),
687
+ memoryList: (allow) => call("memory.list", { ...allow }),
688
+ memoryAdd: (input) => call("memory.add", { ...input }),
689
+ memoryUpdate: (uid, content, scope) => call("memory.update", {
690
+ uid,
691
+ content,
692
+ ...scope === void 0 ? {} : { scope }
693
+ }),
694
+ memoryRemove: (uid) => call("memory.remove", { uid }),
695
+ /** 埋点:进群(未读多)时总结该群最近一段消息并提炼记忆。 */
696
+ memorySummarize: (gid) => call("memory.summarize", { gid }),
682
697
  /** The alter's latest state (null = no session yet). */
683
698
  sessionLatest: () => call("session.latest", {}),
684
699
  /** The alter transcript (last `limit` items). */
@@ -750,10 +765,15 @@ window.__ModuleLoader__.load({
750
765
  id
751
766
  }),
752
767
  /** Apply to join a group from its public URI (`soulmirror://group?gid=…&relay=…`). */
753
- groupApply: (uri, note) => call("group.apply", {
768
+ /** Fetch a group's public card (join policy, paid price/address). */
769
+ groupLookup: (uri) => call("group.lookup", { uri }),
770
+ groupApply: (uri, note, payment) => call("group.apply", {
754
771
  uri,
755
- ...note === void 0 ? {} : { note }
772
+ ...note === void 0 ? {} : { note },
773
+ ...payment === void 0 ? {} : { payment }
756
774
  }),
775
+ /** Pay the group's join price from THIS user's local CDP wallet and apply with the proof (one click). */
776
+ groupPaidJoin: (uri) => call("group.paidJoin", { uri }),
757
777
  groupApplications: (gid) => call("group.applications", { gid }),
758
778
  groupApprove: (gid, fp) => call("group.approve", {
759
779
  gid,
@@ -806,6 +826,7 @@ window.__ModuleLoader__.load({
806
826
  };
807
827
  listeners = /* @__PURE__ */ new Set();
808
828
  mailListeners = /* @__PURE__ */ new Set();
829
+ memoryListeners = /* @__PURE__ */ new Set();
809
830
  frameListeners = /* @__PURE__ */ new Set();
810
831
  source;
811
832
  everConnected = false;
@@ -876,6 +897,13 @@ window.__ModuleLoader__.load({
876
897
  this.frameListeners.delete(listener);
877
898
  };
878
899
  };
900
+ /** Subscribe to memory-extraction frames (the owner's popup). Does not open the SSE stream by itself. */
901
+ onMemory = (listener) => {
902
+ this.memoryListeners.add(listener);
903
+ return () => {
904
+ this.memoryListeners.delete(listener);
905
+ };
906
+ };
879
907
  /** Fold a `presence` answer (fp -> online) into the friend rows. */
880
908
  applyPresence = (online) => {
881
909
  let inbox = this.snapshot.inbox;
@@ -987,6 +1015,14 @@ window.__ModuleLoader__.load({
987
1015
  inbox: folded.state
988
1016
  });
989
1017
  return;
1018
+ case "memory":
1019
+ for (const l of this.memoryListeners) l({
1020
+ phase: frame.phase,
1021
+ count: frame.count,
1022
+ ...frame.memories === void 0 ? {} : { memories: frame.memories },
1023
+ ...frame.clue === void 0 ? {} : { clue: frame.clue }
1024
+ });
1025
+ return;
990
1026
  case "alter":
991
1027
  this.set({ revision });
992
1028
  return;
@@ -1023,6 +1059,7 @@ window.__ModuleLoader__.load({
1023
1059
  "alter",
1024
1060
  "agent",
1025
1061
  "draft",
1062
+ "memory",
1026
1063
  "group_message",
1027
1064
  "group_typing",
1028
1065
  "group_update",
@@ -1093,7 +1130,7 @@ window.__ModuleLoader__.load({
1093
1130
  });
1094
1131
  }
1095
1132
  /** Per-friend alter settings: reply tier + protocol override (friend pane, above the action bar). */
1096
- function FriendSettingsPanel({ fp, name, tier, tierExplicit, protocol, defaultTier, perHour, t, onClose }) {
1133
+ function FriendSettingsPanel({ fp, name, tier, tierExplicit, protocol, muted, defaultTier, perHour, t, onClose }) {
1097
1134
  const [draft, setDraft] = (0, react.useState)(protocol ?? "");
1098
1135
  const [busy, setBusy] = (0, react.useState)(false);
1099
1136
  const [note, setNote] = (0, react.useState)(void 0);
@@ -1156,6 +1193,23 @@ window.__ModuleLoader__.load({
1156
1193
  }),
1157
1194
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("friend.tier.hint", { n: perHour }) })
1158
1195
  ] }),
1196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1197
+ style: {
1198
+ display: "flex",
1199
+ alignItems: "center",
1200
+ gap: 8,
1201
+ flexDirection: "row"
1202
+ },
1203
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1204
+ type: "checkbox",
1205
+ checked: muted,
1206
+ disabled: busy,
1207
+ onChange: (e) => {
1208
+ save({ muted: e.target.checked });
1209
+ },
1210
+ "data-soulmirror-friend-mute": true
1211
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("friend.mute") })]
1212
+ }),
1159
1213
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("friend.protocol") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1160
1214
  className: "sm-textarea-box",
1161
1215
  value: draft,
@@ -1396,6 +1450,28 @@ window.__ModuleLoader__.load({
1396
1450
  });
1397
1451
  }
1398
1452
  //#endregion
1453
+ //#region src/client/dir-picker.ts
1454
+ /**
1455
+ * Module-level access to the host's directory picker (`ctx.workspaces`).
1456
+ * Set once in the client `apply` (bundle purity keeps `ctx` out of React
1457
+ * components); the AgentSettingsSheet reads it for its "browse" button next to
1458
+ * the working-directory field. Falls back to null when the host has no picker
1459
+ * or the user cancels, so the field stays editable by hand either way.
1460
+ */
1461
+ let pick;
1462
+ function setDirectoryPicker(fn) {
1463
+ pick = fn;
1464
+ }
1465
+ /** Open the host's native directory picker; resolves null when unavailable or cancelled. */
1466
+ async function pickDirectory() {
1467
+ if (pick === void 0) return null;
1468
+ try {
1469
+ return await pick();
1470
+ } catch {
1471
+ return null;
1472
+ }
1473
+ }
1474
+ //#endregion
1399
1475
  //#region src/client/page-state.ts
1400
1476
  const EMPTY_THREAD = {
1401
1477
  entries: [],
@@ -1561,6 +1637,12 @@ window.__ModuleLoader__.load({
1561
1637
  entries: state.entries.filter((e) => e.clientId !== clientId)
1562
1638
  };
1563
1639
  }
1640
+ /** Case-insensitive filter over name / remark / card name / fingerprint prefix. */
1641
+ function filterFriends(friends, query) {
1642
+ const q = query.trim().toLowerCase();
1643
+ if (q === "") return [...friends];
1644
+ return friends.filter((f) => f.name.toLowerCase().includes(q) || (f.remark?.toLowerCase().includes(q) ?? false) || (f.cardName?.toLowerCase().includes(q) ?? false) || f.fp.toLowerCase().startsWith(q));
1645
+ }
1564
1646
  /** Selection key of the pinned first item "My alter" (never a fingerprint). */
1565
1647
  const ALTER_KEY = "alter";
1566
1648
  /** Selection key of one group. */
@@ -1603,30 +1685,40 @@ window.__ModuleLoader__.load({
1603
1685
  * The tab set the third column offers for a selection. `admin` (group
1604
1686
  * management) is group-owner/admin only; the rest follow the product's rows:
1605
1687
  * a friend has chat + home, a group has chat / announce / home / members
1606
- * (+ admin). `agent` adds a lightweight `info` tab. Extra modules (group
1607
- * memory, per-agent memory) slot in here later.
1688
+ * (+ admin), the alter and every agent have a memory page.
1608
1689
  */
1609
1690
  function tabsFor(kind, canAdmin) {
1610
1691
  switch (kind) {
1611
1692
  case "alter": return [
1612
1693
  "chat",
1613
1694
  "home",
1695
+ "memory",
1614
1696
  "settings"
1615
1697
  ];
1616
- case "friend": return ["chat", "home"];
1698
+ case "friend": return [
1699
+ "chat",
1700
+ "home",
1701
+ "memory"
1702
+ ];
1617
1703
  case "group": return canAdmin ? [
1618
1704
  "chat",
1619
1705
  "announce",
1620
1706
  "home",
1707
+ "memory",
1621
1708
  "members",
1622
1709
  "admin"
1623
1710
  ] : [
1624
1711
  "chat",
1625
1712
  "announce",
1626
1713
  "home",
1714
+ "memory",
1627
1715
  "members"
1628
1716
  ];
1629
- case "agent": return ["chat", "info"];
1717
+ case "agent": return [
1718
+ "chat",
1719
+ "memory",
1720
+ "info"
1721
+ ];
1630
1722
  }
1631
1723
  }
1632
1724
  /** The default tab when a selection is entered. */
@@ -1715,13 +1807,10 @@ window.__ModuleLoader__.load({
1715
1807
  "members",
1716
1808
  "admin",
1717
1809
  "info",
1810
+ "memory",
1718
1811
  "settings"
1719
1812
  ];
1720
- const COL2_TABS = [
1721
- "contacts",
1722
- "agents",
1723
- "groups"
1724
- ];
1813
+ const COL2_TABS = ["messages", "contacts"];
1725
1814
  /**
1726
1815
  * A persisted `paneTab` only makes sense for the selection it was saved under:
1727
1816
  * the friend it belonged to may be gone (the page then falls back to the alter,
@@ -1739,7 +1828,7 @@ window.__ModuleLoader__.load({
1739
1828
  const fallback = {
1740
1829
  open: false,
1741
1830
  selected: void 0,
1742
- col2Tab: "contacts",
1831
+ col2Tab: "messages",
1743
1832
  paneTab: DEFAULT_PANE_TAB
1744
1833
  };
1745
1834
  try {
@@ -1751,7 +1840,7 @@ window.__ModuleLoader__.load({
1751
1840
  return {
1752
1841
  open: p.open === true,
1753
1842
  selected,
1754
- col2Tab: COL2_TABS.includes(p.col2Tab) ? p.col2Tab : "contacts",
1843
+ col2Tab: COL2_TABS.includes(p.col2Tab) ? p.col2Tab : "messages",
1755
1844
  paneTab: clampPaneTab(p.paneTab, selected)
1756
1845
  };
1757
1846
  } catch {
@@ -1951,7 +2040,7 @@ window.__ModuleLoader__.load({
1951
2040
  });
1952
2041
  this.prime(selection);
1953
2042
  };
1954
- /** Switch the second-column section (contacts / agents / groups). */
2043
+ /** Switch the second-column section (messages / contacts). */
1955
2044
  setCol2Tab = (tab) => {
1956
2045
  if (tab !== this.snapshot.col2Tab) this.set({ col2Tab: tab });
1957
2046
  };
@@ -2179,6 +2268,333 @@ window.__ModuleLoader__.load({
2179
2268
  };
2180
2269
  const pageStore = new PageStore();
2181
2270
  //#endregion
2271
+ //#region src/client/MemoryNotification.tsx
2272
+ /**
2273
+ * Memory-extraction popup (product parity with the lingshu reference):
2274
+ * every turn ends with a "distilling" notice that steps through a simulated
2275
+ * progress, then shows the extracted memories with a countdown that KEEPS them
2276
+ * by default, or a short "nothing to save" notice when extraction came back
2277
+ * empty. Keeping shows a transient guide bubble.
2278
+ */
2279
+ const KEEP_SECONDS = 5;
2280
+ const EMPTY_SECONDS = 3;
2281
+ const GUIDE_MS = 5e3;
2282
+ const STEP_DELAY_MS = 1100;
2283
+ const box = {
2284
+ position: "fixed",
2285
+ top: 84,
2286
+ right: 24,
2287
+ width: 320,
2288
+ maxWidth: "calc(100vw - 48px)",
2289
+ zIndex: 2e3,
2290
+ background: "var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-base))",
2291
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,0.3))",
2292
+ borderRadius: 12,
2293
+ boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
2294
+ padding: 16,
2295
+ color: "var(--dsw-alias-label-primary, #e6e6e6)",
2296
+ fontFamily: "inherit"
2297
+ };
2298
+ const title = {
2299
+ margin: 0,
2300
+ fontSize: 14,
2301
+ fontWeight: 600
2302
+ };
2303
+ const hint = {
2304
+ marginTop: 6,
2305
+ fontSize: 12.5,
2306
+ lineHeight: 1.55,
2307
+ opacity: .82
2308
+ };
2309
+ const list = {
2310
+ marginTop: 10,
2311
+ display: "flex",
2312
+ flexDirection: "column",
2313
+ gap: 6
2314
+ };
2315
+ const stepRow = {
2316
+ display: "flex",
2317
+ alignItems: "center",
2318
+ gap: 8,
2319
+ fontSize: 12.5,
2320
+ lineHeight: 1.4
2321
+ };
2322
+ const dot = {
2323
+ width: 6,
2324
+ height: 6,
2325
+ borderRadius: 3,
2326
+ flexShrink: 0
2327
+ };
2328
+ const dotLoading = {
2329
+ ...dot,
2330
+ background: "var(--dsw-alias-accent, #4c8dff)",
2331
+ animation: "soulnet-pulse 1.2s ease-in-out infinite"
2332
+ };
2333
+ const dotDone = {
2334
+ ...dot,
2335
+ background: "var(--dsw-alias-state-success-primary, #4ade80)"
2336
+ };
2337
+ const points = {
2338
+ marginTop: 8,
2339
+ display: "flex",
2340
+ flexDirection: "column",
2341
+ gap: 6,
2342
+ maxHeight: 200,
2343
+ overflowY: "auto"
2344
+ };
2345
+ const point = {
2346
+ fontSize: 12.5,
2347
+ lineHeight: 1.5,
2348
+ padding: "7px 10px",
2349
+ borderRadius: 8,
2350
+ background: "var(--dsw-alias-bg-layer-1, rgba(255,255,255,0.04))"
2351
+ };
2352
+ const actions = {
2353
+ marginTop: 12,
2354
+ display: "flex",
2355
+ gap: 8,
2356
+ justifyContent: "flex-end"
2357
+ };
2358
+ const ghostBtn = {
2359
+ padding: "6px 12px",
2360
+ fontSize: 12.5,
2361
+ borderRadius: 8,
2362
+ cursor: "pointer",
2363
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,0.4))",
2364
+ background: "transparent",
2365
+ color: "inherit"
2366
+ };
2367
+ const primaryBtn = {
2368
+ padding: "6px 12px",
2369
+ fontSize: 12.5,
2370
+ borderRadius: 8,
2371
+ cursor: "pointer",
2372
+ border: "none",
2373
+ background: "var(--dsw-alias-accent, #4c8dff)",
2374
+ color: "#fff"
2375
+ };
2376
+ const guideBox = {
2377
+ ...box,
2378
+ width: 300,
2379
+ top: 84,
2380
+ right: 24
2381
+ };
2382
+ const guideCopy = {
2383
+ fontSize: 13,
2384
+ fontWeight: 600
2385
+ };
2386
+ const guideSub = {
2387
+ marginTop: 6,
2388
+ fontSize: 12,
2389
+ lineHeight: 1.5,
2390
+ opacity: .8
2391
+ };
2392
+ function MemoryNotification({ t }) {
2393
+ const [task, setTask] = (0, react.useState)(null);
2394
+ const timersRef = (0, react.useRef)([]);
2395
+ const intervalRef = (0, react.useRef)(null);
2396
+ const clearAll = (0, react.useCallback)(() => {
2397
+ timersRef.current.forEach(clearTimeout);
2398
+ timersRef.current = [];
2399
+ if (intervalRef.current !== null) {
2400
+ clearInterval(intervalRef.current);
2401
+ intervalRef.current = null;
2402
+ }
2403
+ }, []);
2404
+ const startCountdown = (0, react.useCallback)((seconds, onZero) => {
2405
+ intervalRef.current = setInterval(() => {
2406
+ setTask((prev) => {
2407
+ if (prev === null) return prev;
2408
+ const next = prev.remaining - 1;
2409
+ if (next > 0) return {
2410
+ ...prev,
2411
+ remaining: next
2412
+ };
2413
+ if (intervalRef.current !== null) clearInterval(intervalRef.current);
2414
+ intervalRef.current = null;
2415
+ onZero();
2416
+ return prev;
2417
+ });
2418
+ }, 1e3);
2419
+ }, []);
2420
+ (0, react.useEffect)(() => networkStore.onMemory((frame) => {
2421
+ clearAll();
2422
+ if (frame.phase === "extracting") {
2423
+ setTask({
2424
+ phase: "processing",
2425
+ memories: [],
2426
+ remaining: KEEP_SECONDS,
2427
+ step: 0,
2428
+ count: 0,
2429
+ clue: frame.clue ?? ""
2430
+ });
2431
+ const stepTimer = setTimeout(() => {
2432
+ setTask((prev) => prev !== null && prev.phase === "processing" ? {
2433
+ ...prev,
2434
+ step: 1
2435
+ } : prev);
2436
+ }, STEP_DELAY_MS);
2437
+ timersRef.current.push(stepTimer);
2438
+ return;
2439
+ }
2440
+ const memories = frame.memories ?? [];
2441
+ const count = frame.count;
2442
+ if (memories.length > 0) {
2443
+ setTask({
2444
+ phase: "result",
2445
+ memories,
2446
+ remaining: KEEP_SECONDS,
2447
+ step: 2,
2448
+ count,
2449
+ clue: frame.clue ?? ""
2450
+ });
2451
+ startCountdown(KEEP_SECONDS, () => {
2452
+ setTask((prev) => prev === null ? prev : {
2453
+ ...prev,
2454
+ phase: "guide"
2455
+ });
2456
+ const guideTimer = setTimeout(() => setTask(null), GUIDE_MS);
2457
+ timersRef.current.push(guideTimer);
2458
+ });
2459
+ } else {
2460
+ setTask({
2461
+ phase: "empty",
2462
+ memories: [],
2463
+ remaining: EMPTY_SECONDS,
2464
+ step: 2,
2465
+ count,
2466
+ clue: frame.clue ?? ""
2467
+ });
2468
+ startCountdown(EMPTY_SECONDS, () => setTask(null));
2469
+ }
2470
+ }), [clearAll, startCountdown]);
2471
+ const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
2472
+ (0, react.useEffect)(() => {
2473
+ clearAll();
2474
+ setTask(null);
2475
+ }, [
2476
+ page.selected,
2477
+ page.open,
2478
+ clearAll
2479
+ ]);
2480
+ const onCancel = (0, react.useCallback)(() => {
2481
+ const ids = task?.memories.map((m) => m.id).filter(Boolean) ?? [];
2482
+ api.cancelMemory(ids).catch(() => {});
2483
+ clearAll();
2484
+ setTask(null);
2485
+ }, [task, clearAll]);
2486
+ const onClose = (0, react.useCallback)(() => {
2487
+ clearAll();
2488
+ setTask(null);
2489
+ }, [clearAll]);
2490
+ if (task === null) return null;
2491
+ if (task.phase === "guide") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2492
+ style: guideBox,
2493
+ "aria-live": "polite",
2494
+ "data-soulmirror-float": true,
2495
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2496
+ style: guideCopy,
2497
+ children: t("memory.guide", {
2498
+ count: task.count,
2499
+ plural: task.count === 1 ? "y" : "ies"
2500
+ })
2501
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2502
+ style: guideSub,
2503
+ children: t("memory.guide.sub")
2504
+ })]
2505
+ });
2506
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2507
+ style: box,
2508
+ "aria-live": "polite",
2509
+ "data-soulmirror-float": true,
2510
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2511
+ style: title,
2512
+ children: t("memory.title")
2513
+ }), task.phase === "processing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2514
+ style: hint,
2515
+ children: t("memory.processing.hint")
2516
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2517
+ style: list,
2518
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2519
+ style: stepRow,
2520
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: task.step >= 0 ? dotDone : dotLoading }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("memory.step.clue", { summary: task.clue === "" ? "…" : task.clue }) })]
2521
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2522
+ style: stepRow,
2523
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: task.step >= 1 ? dotLoading : dot }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("memory.step.semantic") })]
2524
+ })]
2525
+ })] }) : task.phase === "result" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2527
+ style: hint,
2528
+ children: t("memory.result.hint")
2529
+ }),
2530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2531
+ style: {
2532
+ marginTop: 10,
2533
+ fontSize: 12.5,
2534
+ fontWeight: 600
2535
+ },
2536
+ children: t("memory.result.title")
2537
+ }),
2538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2539
+ style: points,
2540
+ children: task.memories.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2541
+ style: point,
2542
+ children: m.content
2543
+ }, m.id))
2544
+ }),
2545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2546
+ style: actions,
2547
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2548
+ type: "button",
2549
+ style: ghostBtn,
2550
+ onClick: onCancel,
2551
+ children: t("memory.cancel")
2552
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2553
+ type: "button",
2554
+ style: primaryBtn,
2555
+ onClick: () => {
2556
+ clearAll();
2557
+ setTask((prev) => prev === null ? prev : {
2558
+ ...prev,
2559
+ phase: "guide"
2560
+ });
2561
+ const guideTimer = setTimeout(() => setTask(null), GUIDE_MS);
2562
+ timersRef.current.push(guideTimer);
2563
+ },
2564
+ children: t("memory.keep", { n: task.remaining })
2565
+ })]
2566
+ })
2567
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2569
+ style: hint,
2570
+ children: t("memory.empty.hint")
2571
+ }),
2572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2573
+ style: {
2574
+ marginTop: 10,
2575
+ fontSize: 12.5,
2576
+ fontWeight: 600
2577
+ },
2578
+ children: t("memory.empty.title")
2579
+ }),
2580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2581
+ style: actions,
2582
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2583
+ type: "button",
2584
+ style: ghostBtn,
2585
+ onClick: onClose,
2586
+ children: t("memory.empty.gotIt", { n: task.remaining })
2587
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2588
+ type: "button",
2589
+ style: primaryBtn,
2590
+ onClick: onClose,
2591
+ children: t("memory.empty.continue")
2592
+ })]
2593
+ })
2594
+ ] })]
2595
+ });
2596
+ }
2597
+ //#endregion
2182
2598
  //#region src/client/nav-seat.ts
2183
2599
  /**
2184
2600
  * Whether a sidebar that declares `sidebar.nav.primary` (soulnet-dsh-sidebar)
@@ -2268,11 +2684,10 @@ window.__ModuleLoader__.load({
2268
2684
  /**
2269
2685
  * `shell.overlay` entry `soulmirror-inbox`: the new-mail cue — a ui-primitives
2270
2686
  * Toast "New message from <name>" for mail whose session is not the one on
2271
- * screen and whose thread is not open on the SoulMirror page. Always mounted
2272
- * (the overlay layer is click-through until an entry renders something), so
2273
- * the SSE stream is kept open by the store subscription here and the footer
2274
- * badge updates live even while the page is closed. The P2 inbox popover
2275
- * that used to live here was replaced by the full page (./SoulmirrorPage.tsx).
2687
+ * screen and whose thread is not open on the SoulMirror page. The memory
2688
+ * extraction popup lives in ./MemoryNotification.tsx (mounted here so the SSE
2689
+ * stream stays open for its own subscription too). Always mounted (the overlay
2690
+ * layer is click-through until an entry renders something).
2276
2691
  */
2277
2692
  function InboxOverlay({ currentSessionId, t }) {
2278
2693
  (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
@@ -2280,7 +2695,10 @@ window.__ModuleLoader__.load({
2280
2695
  const seq = (0, react.useRef)(0);
2281
2696
  (0, react.useEffect)(() => networkStore.onMail((notice) => {
2282
2697
  const page = pageStore.getSnapshot();
2283
- if (page.open && page.selected === notice.fp) return;
2698
+ const threadKey = notice.gid !== void 0 ? groupKey(notice.gid) : notice.fp;
2699
+ if (page.open && page.selected === threadKey) return;
2700
+ const inbox = networkStore.getSnapshot().inbox;
2701
+ if (notice.gid !== void 0 ? inbox.groups.find((g) => g.gid === notice.gid)?.muted === true : inbox.friends.find((f) => f.fp === notice.fp)?.muted === true) return;
2284
2702
  if (!shouldNotify(notice, currentSessionId())) return;
2285
2703
  seq.current += 1;
2286
2704
  const entry = {
@@ -2292,19 +2710,34 @@ window.__ModuleLoader__.load({
2292
2710
  const dismiss = (0, react.useCallback)((key) => {
2293
2711
  setToasts((prev) => prev.filter((x) => x.key !== key));
2294
2712
  }, []);
2295
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: toasts.map((toast) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MailToast, {
2713
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryNotification, { t }), toasts.map((toast) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MailToast, {
2296
2714
  entry: toast,
2297
2715
  t,
2298
2716
  onDone: () => {
2299
2717
  dismiss(toast.key);
2300
2718
  }
2301
- }, toast.key)) });
2719
+ }, toast.key))] });
2302
2720
  }
2303
2721
  function MailToast({ entry, t, onDone }) {
2304
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
2305
- text: t("toast.newMail", { name: entry.name }),
2306
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 16 }),
2307
- onDone
2722
+ (0, react.useEffect)(() => {
2723
+ const timer = setTimeout(onDone, 4500);
2724
+ return () => {
2725
+ clearTimeout(timer);
2726
+ };
2727
+ }, []);
2728
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2729
+ type: "button",
2730
+ className: "sm-mail-toast",
2731
+ onClick: onDone,
2732
+ "data-soulmirror-mail-toast": true,
2733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 16 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2734
+ style: {
2735
+ overflow: "hidden",
2736
+ textOverflow: "ellipsis",
2737
+ whiteSpace: "nowrap"
2738
+ },
2739
+ children: t("toast.newMail", { name: entry.name })
2740
+ })]
2308
2741
  });
2309
2742
  }
2310
2743
  //#endregion
@@ -2320,7 +2753,7 @@ window.__ModuleLoader__.load({
2320
2753
  "inbox.pending.noGreeting": "(no greeting)",
2321
2754
  "inbox.pending.accepted": "Accepted {name}.",
2322
2755
  "inbox.empty": "No friends yet — paste a card URI below, or /add.",
2323
- "inbox.empty.noIdentity": "No identity yet — create one in Settings → SoulMirror network.",
2756
+ "inbox.empty.noIdentity": "No identity yet — create one in Settings → Soulnet.",
2324
2757
  "inbox.noIdentity": "No identity yet",
2325
2758
  "inbox.noMessages": "No messages yet",
2326
2759
  "inbox.typing": "typing…",
@@ -2333,17 +2766,53 @@ window.__ModuleLoader__.load({
2333
2766
  "inbox.add": "Add",
2334
2767
  "inbox.add.placeholder": "Add a friend: paste a card URI (soulmirror://card?…)",
2335
2768
  "toast.newMail": "New message from {name}",
2769
+ "memory.title": "Saving your key memories",
2770
+ "memory.processing.hint": "I'm listening — I'll distill the important bits into long-term memory that understands you better.",
2771
+ "memory.step.clue": "Spotted a new cue: \"{summary}\"",
2772
+ "memory.step.semantic": "Extracting key meaning: checking for long-term value",
2773
+ "memory.result.hint": "These are saved as pre-memories; you can review them in your knowledge space later.",
2774
+ "memory.result.title": "Extracted memories",
2775
+ "memory.cancel": "Discard",
2776
+ "memory.keep": "Keep {n}s",
2777
+ "memory.empty.hint": "Nothing worth remembering this time — I won't write anything down.",
2778
+ "memory.empty.title": "No memories found",
2779
+ "memory.empty.gotIt": "Got it {n}s",
2780
+ "memory.empty.continue": "Keep chatting",
2781
+ "memory.guide": "{count} key memor{plural} remembered!",
2782
+ "memory.guide.sub": "Keep chatting and I'll slowly remember your preferences and ideas.",
2783
+ "memory.tab": "Memory",
2784
+ "memory.add": "Add",
2785
+ "memory.add.placeholder": "Add a memory by hand…",
2786
+ "memory.save": "Save",
2787
+ "memory.edit": "Edit",
2788
+ "memory.delete": "Delete",
2789
+ "memory.empty.page": "No memories yet.",
2790
+ "memory.origin.auto": "auto",
2791
+ "memory.origin.manual": "manual",
2792
+ "memory.scope.global": "global",
2793
+ "memory.scope.agent": "agent {name}",
2794
+ "memory.scope.group": "group",
2795
+ "memory.scope.friend": "shared",
2796
+ "memory.scope.label": "Scope",
2797
+ "memory.scope.onlyAgent": "this agent only",
2798
+ "memory.scope.onlyGroup": "this group only",
2799
+ "memory.scope.onlyFriend": "this friend only",
2800
+ "memory.filter.label": "Filter",
2801
+ "memory.filter.all": "All",
2802
+ "memory.empty.filter": "No memories in this scope.",
2336
2803
  "bubble.in": "from {name}",
2337
2804
  "bubble.out": "to {name}",
2338
2805
  "bubble.mine": "my alter",
2339
2806
  "bubble.auto": "auto-reply",
2340
2807
  "bubble.note": "SoulMirror",
2341
- "settings.nav": "SoulMirror network",
2342
- "page.update.banner": "New version v{v} - click to upgrade",
2343
- "page.update.confirm": "Upgrade SoulMirror to v{v} now? The service restarts itself and this page reloads.",
2344
- "page.update.busy": "Upgrading… the page will reload by itself.",
2345
- "settings.title": "SoulMirror network",
2346
- "settings.intro": "This dsh is a node on the SoulMirror network: an end-to-end encrypted A2A identity, friends, and your alter — one dsh session (\"My alter\") that speaks to every friend on your behalf.",
2808
+ "settings.nav": "Soulnet",
2809
+ "sidebar.update": "Upgrade v{v}",
2810
+ "sidebar.update.busy": "Upgrading…",
2811
+ "page.update.installing": "Installing v{v}…",
2812
+ "page.update.restarting": "Restarting the service…",
2813
+ "page.update.reloading": "Back — reloading…",
2814
+ "settings.title": "Soulnet",
2815
+ "settings.intro": "This dsh is a node on Soulnet: an end-to-end encrypted A2A identity, friends, and your alter — one dsh session (\"My alter\") that speaks to every friend on your behalf.",
2347
2816
  "settings.status": "Backend",
2348
2817
  "settings.status.soulnet": "soulnet light peer",
2349
2818
  "settings.status.fake": "in-memory fake",
@@ -2408,14 +2877,14 @@ window.__ModuleLoader__.load({
2408
2877
  "settings.add.sent": "Request sent to {name}. The thread appears once they accept.",
2409
2878
  "settings.error": "Error: {message}",
2410
2879
  "settings.refresh": "Refresh",
2411
- "onboard.title": "Welcome to the SoulMirror network",
2880
+ "onboard.title": "Welcome to Soulnet",
2412
2881
  "onboard.body": "Create your A2A identity: a key pair generated on this machine. Others add you by pasting your card URI.",
2413
2882
  "onboard.name": "Display name",
2414
2883
  "onboard.create": "Create identity",
2415
2884
  "onboard.done": "Done",
2416
2885
  "onboard.later": "Later",
2417
2886
  "onboard.created": "Identity created. Copy your card URI and back up identity.json.",
2418
- "onboard.waiting": "The soulnet peer is not running yet ({state}). Check Settings → SoulMirror network.",
2887
+ "onboard.waiting": "The soulnet peer is not running yet ({state}). Check Settings → Soulnet.",
2419
2888
  "cmd.friends.open": "Open the thread with {name}",
2420
2889
  "cmd.friends.none": "No friends yet",
2421
2890
  "cmd.soulmirror.page": "Open SoulMirror — My alter",
@@ -2433,7 +2902,7 @@ window.__ModuleLoader__.load({
2433
2902
  "page.add.note": "Note (optional, also the greeting)",
2434
2903
  "page.add.send": "Send request",
2435
2904
  "page.empty.noFriends": "No friends yet. Paste a card URI under \"Add friend\" at the bottom of the list, or send yours to a friend.",
2436
- "page.empty.noFriends.noIdentity": "No identity yet — create one in Settings → SoulMirror network.",
2905
+ "page.empty.noFriends.noIdentity": "No identity yet — create one in Settings → Soulnet.",
2437
2906
  "page.empty.noMatch": "No friend matches \"{query}\".",
2438
2907
  "page.header.online": "online",
2439
2908
  "page.header.offline": "offline",
@@ -2462,13 +2931,12 @@ window.__ModuleLoader__.load({
2462
2931
  "page.composer.instructing": "Sending…",
2463
2932
  "page.composer.direct": "Debug: direct to {name} · end-to-end encrypted · your alter does not see it",
2464
2933
  "alter.me": "My alter",
2934
+ "alter.wallet": "Wallet",
2465
2935
  "alter.row.hint": "Talk to your alter directly",
2466
2936
  "alter.row.drafts": "{n} draft(s) waiting for you",
2467
2937
  "alter.status.running": "Your alter is working…",
2468
2938
  "alter.status.idle": "Ready · one memory shared across every friend",
2469
2939
  "alter.status.noSession": "Starting the alter session…",
2470
- "alter.openDsh": "Open in dsh",
2471
- "alter.openDsh.hint": "The same conversation as a native dsh session (trajectory, tools, approvals).",
2472
2940
  "alter.pendbar": "{n} draft(s) of your alter wait for your decision",
2473
2941
  "alter.pendbar.go": "Review in {name}'s thread →",
2474
2942
  "alter.empty.title": "Tell your alter what to do",
@@ -2479,6 +2947,8 @@ window.__ModuleLoader__.load({
2479
2947
  "alter.thinking": "Your alter is working…",
2480
2948
  "alter.item.inbound": "Mail from {name}",
2481
2949
  "alter.item.view": "open thread",
2950
+ "alter.item.groupInbound": "Group message · {name}",
2951
+ "alter.item.viewGroup": "view in {name}",
2482
2952
  "alter.item.send": "To {name}",
2483
2953
  "alter.item.send.pending": "sending…",
2484
2954
  "alter.item.send.sent": "sent",
@@ -2496,6 +2966,7 @@ window.__ModuleLoader__.load({
2496
2966
  "draft.title": "Your alter wants to send this",
2497
2967
  "draft.title.to": "Your alter wants to send this to {name}",
2498
2968
  "draft.openThread": "open thread",
2969
+ "draft.openGroup": "view in group",
2499
2970
  "draft.reason.draft-tier": "Held because this friend's reply tier is \"draft\" (you review before anything goes out).",
2500
2971
  "draft.reason.notify-tier": "Held because this friend's reply tier is \"notify\".",
2501
2972
  "draft.reason.rate-limited": "Held because the hourly auto-reply cap for this friend is reached.",
@@ -2534,6 +3005,7 @@ window.__ModuleLoader__.load({
2534
3005
  "tier.short.auto": "auto",
2535
3006
  "friend.settings.title": "How your alter handles {name}",
2536
3007
  "friend.tier": "Reply tier",
3008
+ "friend.mute": "Mute (do not disturb)",
2537
3009
  "friend.tier.hint": "notify: mail is only shown; draft: your alter drafts a reply and you review it on this page; auto: it sends by itself (max {n}/hour).",
2538
3010
  "friend.protocol": "Protocol override for this friend",
2539
3011
  "friend.protocol.placeholder": "Rules for this friend only (they win over the global protocol on conflict). Empty = none.",
@@ -2556,8 +3028,24 @@ window.__ModuleLoader__.load({
2556
3028
  "settings.alter.alterMode.full": "Full dsh (shell + files, like a normal session)",
2557
3029
  "settings.alter.alterMode.hint": "Applies to the next alter session — restart dsh (or reopen the alter session) to switch.",
2558
3030
  "settings.alter.openPage": "Open SoulMirror",
2559
- "settings.alter.openSession": "Open the alter session in dsh",
2560
3031
  "settings.alter.legacy": "{n} old per-friend session(s) from before 0.1.0-p4 are kept in dsh but receive no mail; you can delete them.",
3032
+ "settings.wallet": "My USDC wallet",
3033
+ "settings.wallet.loading": "Loading…",
3034
+ "settings.wallet.none": "No wallet yet — ask your alter to create one, or bind an existing address.",
3035
+ "settings.wallet.refresh": "Refresh",
3036
+ "settings.wallet.address": "Address",
3037
+ "settings.wallet.balanceUsdc": "USDC",
3038
+ "settings.wallet.balanceEth": "ETH",
3039
+ "settings.pay": "USDC payments (CDP)",
3040
+ "settings.pay.intro": "Your alter can hold a USDC wallet on Base through Coinbase CDP. Fill the three CDP secrets from portal.cdp.coinbase.com (API Keys → Secret API Key; Wallet Secret is shown once) to let the alter create wallets and send automatically. Without CDP you can still receive payments and verify paid group-join transfers.",
3041
+ "settings.pay.cdpKeyId": "CDP API Key ID",
3042
+ "settings.pay.cdpKeySecret": "CDP API Key Secret",
3043
+ "settings.pay.cdpWalletSecret": "CDP Wallet Secret",
3044
+ "settings.pay.cdpNetwork": "CDP Network",
3045
+ "settings.pay.paygateProxy": "Proxy for CDP calls (e.g. http://127.0.0.1:10808)",
3046
+ "settings.pay.paygateBinary": "paygate binary path (empty = bundled/PATH)",
3047
+ "settings.pay.paygatePort": "paygate port (loopback)",
3048
+ "settings.pay.hint": "Saving any of these fields restarts the local payment gateway automatically — no dsh restart needed.",
2561
3049
  "group.section": "Groups",
2562
3050
  "group.create": "New group",
2563
3051
  "group.create.name": "Group name",
@@ -2574,6 +3062,8 @@ window.__ModuleLoader__.load({
2574
3062
  "group.leave.owner": "You are the owner — a group lives and dies with its owner for now.",
2575
3063
  "group.left": "Left the group.",
2576
3064
  "group.empty": "No messages yet — say something below.",
3065
+ "group.draftReminder": "Your alter drafted a reply — awaiting your review.",
3066
+ "group.draftReminder.go": "Review",
2577
3067
  "group.sender.me": "me",
2578
3068
  "group.composer.placeholder": "Message {name}…",
2579
3069
  "group.composer.hint": "Sent as yourself, end-to-end encrypted (sender key).",
@@ -2600,6 +3090,14 @@ window.__ModuleLoader__.load({
2600
3090
  "group.form.join.invite": "invite only",
2601
3091
  "group.form.join.apply": "strangers may apply",
2602
3092
  "group.form.join.open": "open (applications auto-approved)",
3093
+ "group.form.join.paid": "paid (USDC to join)",
3094
+ "group.form.join.paid.noWallet": "needs a wallet",
3095
+ "group.form.join.paid.noWalletHint": "Create a USDC wallet first (Settings → SoulMirror network → USDC payments), then you can charge for joining.",
3096
+ "group.form.joinPrice": "Join price (USDC)",
3097
+ "group.form.joinAddr": "Receive at: {address}",
3098
+ "group.form.join.paid.membersFree": "Members you select now join free; only later applicants pay the join price.",
3099
+ "group.form.join.paid.priceRequired": "Enter the join price (USDC) to create a paid group.",
3100
+ "group.form.join.paid.autoPublic": "Paid groups are public automatically (strangers need the group link to find and pay).",
2603
3101
  "group.form.wake": "Alter wake-up",
2604
3102
  "group.form.wake.mention": "on mention",
2605
3103
  "group.form.wake.always": "always",
@@ -2647,6 +3145,7 @@ window.__ModuleLoader__.load({
2647
3145
  "group.speak.whoOwner": "Only the owner posts in this group.",
2648
3146
  "group.speak.whoAdmins": "Only the owner and admins post in this group.",
2649
3147
  "group.alter.toggle": "My alter participates",
3148
+ "group.mute": "Mute (do not disturb)",
2650
3149
  "group.mode.mention": "replies when @mentioned",
2651
3150
  "group.mode.always": "active — may reply to anything",
2652
3151
  "group.by.alter": "alter",
@@ -2663,6 +3162,7 @@ window.__ModuleLoader__.load({
2663
3162
  "settings.agents.none": "No agents yet — add one below.",
2664
3163
  "settings.agents.name": "Name (used as @mention)",
2665
3164
  "settings.agents.cwd": "Working directory - the folder it reads, writes and runs commands in (optional)",
3165
+ "settings.agents.cwd.browse": "Browse",
2666
3166
  "settings.agents.cwdHint": "Leave empty for its own default folder",
2667
3167
  "settings.agents.preset": "Capability preset",
2668
3168
  "settings.agents.preset.default": "standard coding agent (default)",
@@ -2679,7 +3179,6 @@ window.__ModuleLoader__.load({
2679
3179
  "settings.agents.remove": "Remove",
2680
3180
  "settings.agents.saved": "Agent \"{name}\" saved.",
2681
3181
  "settings.agents.removed": "Removed \"{name}\".",
2682
- "settings.agents.open": "Open session",
2683
3182
  "settings.agents.status.running": "working…",
2684
3183
  "settings.agents.status.idle": "idle",
2685
3184
  "agents.section": "My agents",
@@ -2700,15 +3199,34 @@ window.__ModuleLoader__.load({
2700
3199
  "agent.thinking.done": "Thought",
2701
3200
  "group.join.section": "Join a group",
2702
3201
  "group.join.uri": "Group link (soulmirror://group?…)",
3202
+ "group.join.paid.info": "This group is PAID to join: pay {price} USDC to {addr} from any wallet, then paste the transaction hash below.",
3203
+ "group.join.paid.amountLabel": "Join fee",
3204
+ "group.join.paid.confirm": "You are about to pay {price} USDC from your wallet — continue?",
3205
+ "group.join.paid.confirmPay": "Confirm Payment",
3206
+ "group.join.paid.localHint": "Your wallet is managed here — click “Pay & Apply” below to send the fee and join automatically (no need to copy a transaction hash).",
3207
+ "group.join.paid.payApply": "Pay & Apply",
3208
+ "group.join.paid.paidViaLocal": "Paid {amount} USDC and applied — the owner will verify the payment and approve you.",
3209
+ "group.join.paid.toLabel": "Pay to (auto-filled)",
3210
+ "group.join.paid.payHint": "Send {price} USDC from any wallet to the address above, then paste the transaction hash to apply.",
3211
+ "group.join.paid.tx": "Payment transaction hash (0x…)",
3212
+ "group.join.paid.noPrice": "The group is paid but has no published price — ask the owner.",
3213
+ "group.join.paid.needProof": "This is a paid group ({price} USDC). Pay to {addr} first, then paste the transaction hash to apply.",
3214
+ "group.join.paid.note": "paid {price} USDC to join",
3215
+ "group.join.paid.applied": "Payment attached — application submitted, waiting for the owner to verify.",
2703
3216
  "group.join.apply": "Apply",
2704
3217
  "group.join.applied": "Application sent.",
2705
3218
  "group.apps.badge": "{n} application(s) waiting",
3219
+ "col2.messages": "Messages",
2706
3220
  "col2.contacts": "Contacts",
2707
3221
  "col2.agents": "Agents",
2708
3222
  "col2.groups": "Groups",
2709
3223
  "col2.contacts.section": "You",
2710
3224
  "col2.agents.empty": "No agents yet — create one to give it a job on your behalf.",
2711
3225
  "col2.groups.empty": "No groups yet — create one or join with a group link.",
3226
+ "agents.expand": "Show {n} more",
3227
+ "agents.collapse": "Collapse",
3228
+ "messages.friends": "Friends",
3229
+ "messages.section": "Chats",
2712
3230
  "groups.section": "Groups",
2713
3231
  "pane.chat": "Chat",
2714
3232
  "pane.announce": "Bulletin",
@@ -2717,6 +3235,7 @@ window.__ModuleLoader__.load({
2717
3235
  "pane.admin": "Manage",
2718
3236
  "pane.info": "Info",
2719
3237
  "pane.settings": "Settings",
3238
+ "pane.memory": "Memory",
2720
3239
  "friend.home.profile": "Profile",
2721
3240
  "friend.home.fp": "Fingerprint",
2722
3241
  "friend.home.name": "Name",
@@ -2725,6 +3244,7 @@ window.__ModuleLoader__.load({
2725
3244
  "friend.home.lastActive": "Last active",
2726
3245
  "friend.home.drafts": "Drafts waiting",
2727
3246
  "friend.home.actions": "Actions",
3247
+ "friend.home.sendMessage": "Send a message",
2728
3248
  "alter.home.profile": "Profile",
2729
3249
  "alter.home.session": "Session",
2730
3250
  "alter.home.friends": "Friends",
@@ -2751,7 +3271,7 @@ window.__ModuleLoader__.load({
2751
3271
  "inbox.pending.noGreeting": "(没有问候语)",
2752
3272
  "inbox.pending.accepted": "已接受 {name}。",
2753
3273
  "inbox.empty": "还没有好友——在下面贴名片链接,或用 /add。",
2754
- "inbox.empty.noIdentity": "还没有身份——到 设置 → 灵镜网络 创建。",
3274
+ "inbox.empty.noIdentity": "还没有身份——到 设置 → Soulnet 创建。",
2755
3275
  "inbox.noIdentity": "还没有身份",
2756
3276
  "inbox.noMessages": "还没有消息",
2757
3277
  "inbox.typing": "正在输入…",
@@ -2764,17 +3284,53 @@ window.__ModuleLoader__.load({
2764
3284
  "inbox.add": "添加",
2765
3285
  "inbox.add.placeholder": "加好友:贴名片链接(soulmirror://card?…)",
2766
3286
  "toast.newMail": "{name} 发来新消息",
3287
+ "memory.title": "正在帮你沉淀关键记忆",
3288
+ "memory.processing.hint": "你说的我有认真听,会先提炼重要信息,再变成更懂你的长期记忆。",
3289
+ "memory.step.clue": "识别到一条新的对话线索:“{summary}”",
3290
+ "memory.step.semantic": "提取关键语义:正在判断是否具有长期价值",
3291
+ "memory.result.hint": "这些内容会先作为预记忆保存,之后你也可以在知识空间里继续查看和管理。",
3292
+ "memory.result.title": "已提取出的记忆点",
3293
+ "memory.cancel": "取消记忆",
3294
+ "memory.keep": "保留记忆 {n}s",
3295
+ "memory.empty.hint": "这次没有发现值得长期记住的内容,先不写入记忆库。",
3296
+ "memory.empty.title": "未发现记忆点",
3297
+ "memory.empty.gotIt": "知道了 {n}s",
3298
+ "memory.empty.continue": "继续聊聊",
3299
+ "memory.guide": "您的 {count} 个关键记忆已经被记住啦~",
3300
+ "memory.guide.sub": "多和我聊一点,我会慢慢记住你的偏好和想法,越来越像那个懂你的分身。",
3301
+ "memory.tab": "记忆",
3302
+ "memory.add": "添加",
3303
+ "memory.add.placeholder": "手动添加一条记忆…",
3304
+ "memory.save": "保存",
3305
+ "memory.edit": "编辑",
3306
+ "memory.delete": "删除",
3307
+ "memory.empty.page": "还没有记忆。",
3308
+ "memory.origin.auto": "自动提炼",
3309
+ "memory.origin.manual": "手动",
3310
+ "memory.scope.global": "全局",
3311
+ "memory.scope.agent": "分身 {name}",
3312
+ "memory.scope.group": "群记忆",
3313
+ "memory.scope.friend": "好友共享",
3314
+ "memory.scope.label": "范围",
3315
+ "memory.scope.onlyAgent": "仅当前分身",
3316
+ "memory.scope.onlyGroup": "仅当前群",
3317
+ "memory.scope.onlyFriend": "仅当前好友",
3318
+ "memory.filter.label": "筛选",
3319
+ "memory.filter.all": "全部",
3320
+ "memory.empty.filter": "该范围暂无记忆。",
2767
3321
  "bubble.in": "来自 {name}",
2768
3322
  "bubble.out": "发给 {name}",
2769
3323
  "bubble.mine": "我的分身",
2770
3324
  "bubble.auto": "自动回复",
2771
3325
  "bubble.note": "灵镜",
2772
- "settings.nav": "灵镜网络",
2773
- "page.update.banner": "发现新版本 v{v}——点击升级",
2774
- "page.update.confirm": "现在升级 SoulMirror 到 v{v} 吗?服务会自动重启,页面随后自动恢复。",
2775
- "page.update.busy": "升级中……页面会自动刷新。",
2776
- "settings.title": "灵镜网络",
2777
- "settings.intro": "这个 dsh 是灵镜网络上的一个节点:端到端加密的 A2A 身份、好友,以及你的分身——一条 dsh 会话(「我的分身」),替你和所有好友往来。",
3326
+ "settings.nav": "Soulnet",
3327
+ "sidebar.update": "升级 v{v}",
3328
+ "sidebar.update.busy": "升级中…",
3329
+ "page.update.installing": "正在安装 v{v}……",
3330
+ "page.update.restarting": "正在重启服务……",
3331
+ "page.update.reloading": "完成——正在刷新……",
3332
+ "settings.title": "Soulnet",
3333
+ "settings.intro": "这个 dsh 是Soulnet 上的一个节点:端到端加密的 A2A 身份、好友,以及你的分身——一条 dsh 会话(「我的分身」),替你和所有好友往来。",
2778
3334
  "settings.status": "后端",
2779
3335
  "settings.status.soulnet": "soulnet 灵网轻端",
2780
3336
  "settings.status.fake": "内存假实现",
@@ -2839,14 +3395,14 @@ window.__ModuleLoader__.load({
2839
3395
  "settings.add.sent": "已向 {name} 发出申请,对方接受后往来就会出现。",
2840
3396
  "settings.error": "错误:{message}",
2841
3397
  "settings.refresh": "刷新",
2842
- "onboard.title": "欢迎来到灵镜网络",
3398
+ "onboard.title": "欢迎来到Soulnet",
2843
3399
  "onboard.body": "创建你的 A2A 身份:在本机生成一对密钥。别人贴你的名片链接就能加你。",
2844
3400
  "onboard.name": "昵称",
2845
3401
  "onboard.create": "创建身份",
2846
3402
  "onboard.done": "完成",
2847
3403
  "onboard.later": "稍后",
2848
3404
  "onboard.created": "身份已创建。复制名片链接,并备份 identity.json。",
2849
- "onboard.waiting": "soulnet 轻端还没运行({state})。请到 设置 → 灵镜网络 查看。",
3405
+ "onboard.waiting": "soulnet 轻端还没运行({state})。请到 设置 → Soulnet 查看。",
2850
3406
  "cmd.friends.open": "打开与 {name} 的往来",
2851
3407
  "cmd.friends.none": "还没有好友",
2852
3408
  "cmd.soulmirror.page": "打开灵镜——我的分身",
@@ -2864,7 +3420,7 @@ window.__ModuleLoader__.load({
2864
3420
  "page.add.note": "备注(可选,同时作为问候语)",
2865
3421
  "page.add.send": "发送申请",
2866
3422
  "page.empty.noFriends": "还没有好友。把对方的名片链接贴到列表底部的「加好友」,或把你的名片发给对方。",
2867
- "page.empty.noFriends.noIdentity": "还没有身份——到 设置 → 灵镜网络 创建。",
3423
+ "page.empty.noFriends.noIdentity": "还没有身份——到 设置 → Soulnet 创建。",
2868
3424
  "page.empty.noMatch": "没有匹配「{query}」的好友。",
2869
3425
  "page.header.online": "在线",
2870
3426
  "page.header.offline": "离线",
@@ -2893,13 +3449,12 @@ window.__ModuleLoader__.load({
2893
3449
  "page.composer.instructing": "发送中…",
2894
3450
  "page.composer.direct": "调试:直发给 {name} · 端到端加密 · 分身看不到",
2895
3451
  "alter.me": "我的分身",
3452
+ "alter.wallet": "钱包",
2896
3453
  "alter.row.hint": "跟你的分身直接对话",
2897
3454
  "alter.row.drafts": "{n} 项等你拍板",
2898
3455
  "alter.status.running": "分身正在处理…",
2899
3456
  "alter.status.idle": "就绪 · 对所有好友共享同一记忆",
2900
3457
  "alter.status.noSession": "分身会话启动中…",
2901
- "alter.openDsh": "在 dsh 里打开",
2902
- "alter.openDsh.hint": "同一段对话的 dsh 原生会话(轨迹、工具、审批)。",
2903
3458
  "alter.pendbar": "还有 {n} 项分身拟稿等你拍板",
2904
3459
  "alter.pendbar.go": "去「{name}」里看 →",
2905
3460
  "alter.empty.title": "吩咐你的分身",
@@ -2910,6 +3465,8 @@ window.__ModuleLoader__.load({
2910
3465
  "alter.thinking": "分身正在处理…",
2911
3466
  "alter.item.inbound": "{name} 来信",
2912
3467
  "alter.item.view": "看往来",
3468
+ "alter.item.groupInbound": "群消息 · {name}",
3469
+ "alter.item.viewGroup": "在 {name} 群 中查看",
2913
3470
  "alter.item.send": "发给 {name}",
2914
3471
  "alter.item.send.pending": "发送中…",
2915
3472
  "alter.item.send.sent": "已发出",
@@ -2927,6 +3484,7 @@ window.__ModuleLoader__.load({
2927
3484
  "draft.title": "我的分身想这样回复",
2928
3485
  "draft.title.to": "我的分身想这样发给 {name}",
2929
3486
  "draft.openThread": "看往来",
3487
+ "draft.openGroup": "在群中查看",
2930
3488
  "draft.reason.draft-tier": "依据:这位好友的应答档位是「拟稿」(发出前先问你)。",
2931
3489
  "draft.reason.notify-tier": "依据:这位好友的应答档位是「只通知」。",
2932
3490
  "draft.reason.rate-limited": "依据:对这位好友的自动回复已达每小时上限。",
@@ -2965,6 +3523,7 @@ window.__ModuleLoader__.load({
2965
3523
  "tier.short.auto": "自动",
2966
3524
  "friend.settings.title": "分身如何对待 {name}",
2967
3525
  "friend.tier": "应答档位",
3526
+ "friend.mute": "免打扰",
2968
3527
  "friend.tier.hint": "只通知:来信只展示;拟稿:分身拟好回复、在本页等你拍板;自动:分身自己发(每小时最多 {n} 条)。",
2969
3528
  "friend.protocol": "对这位好友的准则补充",
2970
3529
  "friend.protocol.placeholder": "只对这位好友生效的规则(冲突时压过全局准则)。留空 = 没有。",
@@ -2987,8 +3546,24 @@ window.__ModuleLoader__.load({
2987
3546
  "settings.alter.alterMode.full": "完整 dsh(shell + 文件,像普通会话)",
2988
3547
  "settings.alter.alterMode.hint": "对下一个分身会话生效——切换后重启 dsh(或重新打开分身会话)。",
2989
3548
  "settings.alter.openPage": "打开灵镜",
2990
- "settings.alter.openSession": "在 dsh 里打开分身会话",
2991
3549
  "settings.alter.legacy": "0.1.0-p4 之前的 {n} 条按好友分开的旧会话仍留在 dsh 里,但不再收信,可以删除。",
3550
+ "settings.wallet": "我的 USDC 钱包",
3551
+ "settings.wallet.loading": "加载中…",
3552
+ "settings.wallet.none": "还没有钱包——让分身创建一个,或绑定已有地址。",
3553
+ "settings.wallet.refresh": "刷新",
3554
+ "settings.wallet.address": "地址",
3555
+ "settings.wallet.balanceUsdc": "USDC",
3556
+ "settings.wallet.balanceEth": "ETH",
3557
+ "settings.pay": "USDC 支付(CDP)",
3558
+ "settings.pay.intro": "你的分身可以通过 Coinbase CDP 在 Base 链上持有 USDC 钱包。填入 portal.cdp.coinbase.com 生成的三项 CDP 密钥(API Keys → Secret API Key;Wallet Secret 只显示一次),分身就能自动创建钱包和转账。不配 CDP 也可以收款和验证付费进群的到账。",
3559
+ "settings.pay.cdpKeyId": "CDP API Key ID",
3560
+ "settings.pay.cdpKeySecret": "CDP API Key Secret",
3561
+ "settings.pay.cdpWalletSecret": "CDP Wallet Secret",
3562
+ "settings.pay.cdpNetwork": "CDP 网络",
3563
+ "settings.pay.paygateProxy": "CDP 代理地址(如 http://127.0.0.1:10808)",
3564
+ "settings.pay.paygateBinary": "paygate 路径(留空 = 插件内置/PATH)",
3565
+ "settings.pay.paygatePort": "paygate 端口(本机回环)",
3566
+ "settings.pay.hint": "保存以上任意字段会自动重启本地支付网关,无需重启 dsh。",
2992
3567
  "group.section": "群",
2993
3568
  "group.create": "新建群",
2994
3569
  "group.create.name": "群名称",
@@ -3005,6 +3580,8 @@ window.__ModuleLoader__.load({
3005
3580
  "group.leave.owner": "你是群主——目前群与群主共存亡,不能退出。",
3006
3581
  "group.left": "已退群。",
3007
3582
  "group.empty": "还没有消息——在下面说点什么。",
3583
+ "group.draftReminder": "分身拟了一份回复,等待你拍板。",
3584
+ "group.draftReminder.go": "去查看",
3008
3585
  "group.sender.me": "我",
3009
3586
  "group.composer.placeholder": "发到 {name}…",
3010
3587
  "group.composer.hint": "以你本人身份发送,端到端加密(发送者密钥)。",
@@ -3031,6 +3608,14 @@ window.__ModuleLoader__.load({
3031
3608
  "group.form.join.invite": "仅邀请",
3032
3609
  "group.form.join.apply": "陌生人可申请",
3033
3610
  "group.form.join.open": "开放(申请自动通过)",
3611
+ "group.form.join.paid": "付费进群(付 USDC 才能进)",
3612
+ "group.form.join.paid.noWallet": "需先有钱包",
3613
+ "group.form.join.paid.noWalletHint": "请先在 设置 → 灵镜网络 → USDC 支付 里创建 USDC 钱包,之后才能设置付费进群。",
3614
+ "group.form.joinPrice": "进群价格(USDC)",
3615
+ "group.form.joinAddr": "收款地址:{address}",
3616
+ "group.form.join.paid.membersFree": "创建时勾选的好友直接入群、无需付费;之后申请进群的陌生人才需要付费。",
3617
+ "group.form.join.paid.priceRequired": "请填写进群价格(USDC)后才能创建付费群。",
3618
+ "group.form.join.paid.autoPublic": "付费进群会自动设为公开群(陌生人需要群链接才能找到并付款)。",
3034
3619
  "group.form.wake": "分身唤醒",
3035
3620
  "group.form.wake.mention": "被点名时",
3036
3621
  "group.form.wake.always": "总是",
@@ -3078,6 +3663,7 @@ window.__ModuleLoader__.load({
3078
3663
  "group.speak.whoOwner": "这个群里只有群主发言。",
3079
3664
  "group.speak.whoAdmins": "这个群里只有群主和管理员发言。",
3080
3665
  "group.alter.toggle": "我的分身参与",
3666
+ "group.mute": "免打扰",
3081
3667
  "group.mode.mention": "被@才回应",
3082
3668
  "group.mode.always": "积极参与",
3083
3669
  "group.by.alter": "分身",
@@ -3094,6 +3680,7 @@ window.__ModuleLoader__.load({
3094
3680
  "settings.agents.none": "还没有代理——在下面添加一个。",
3095
3681
  "settings.agents.name": "名字(群里 @ 它用)",
3096
3682
  "settings.agents.cwd": "工作目录——它读写文件、跑命令的文件夹(选填)",
3683
+ "settings.agents.cwd.browse": "浏览",
3097
3684
  "settings.agents.cwdHint": "留空 = 使用它自己的默认目录",
3098
3685
  "settings.agents.preset": "能力预设",
3099
3686
  "settings.agents.preset.default": "标准编码 Agent(默认)",
@@ -3110,7 +3697,6 @@ window.__ModuleLoader__.load({
3110
3697
  "settings.agents.remove": "移除",
3111
3698
  "settings.agents.saved": "代理「{name}」已保存。",
3112
3699
  "settings.agents.removed": "已移除「{name}」。",
3113
- "settings.agents.open": "打开会话",
3114
3700
  "settings.agents.status.running": "干活中…",
3115
3701
  "settings.agents.status.idle": "空闲",
3116
3702
  "agents.section": "我的 Agent",
@@ -3131,15 +3717,34 @@ window.__ModuleLoader__.load({
3131
3717
  "agent.thinking.done": "已思考",
3132
3718
  "group.join.section": "加入群",
3133
3719
  "group.join.uri": "群链接(soulmirror://group?…)",
3720
+ "group.join.paid.info": "该群为付费进群:请用任意钱包向 {addr} 支付 {price} USDC,然后在下面粘贴交易哈希。",
3721
+ "group.join.paid.amountLabel": "进群费用",
3722
+ "group.join.paid.confirm": "即将从你的钱包支付 {price} USDC,确认继续?",
3723
+ "group.join.paid.confirmPay": "确认支付",
3724
+ "group.join.paid.localHint": "钱包由本机管理——点击下方「支付并申请」即可自动转账进群(无需复制交易哈希)。",
3725
+ "group.join.paid.payApply": "支付并申请",
3726
+ "group.join.paid.paidViaLocal": "已支付 {amount} USDC 并提交申请,群主验证后会批准你进群。",
3727
+ "group.join.paid.toLabel": "收款地址(已自动填入)",
3728
+ "group.join.paid.payHint": "用任意钱包向上面地址支付 {price} USDC,然后粘贴交易哈希提交申请。",
3729
+ "group.join.paid.tx": "付款交易哈希(0x…)",
3730
+ "group.join.paid.noPrice": "该群是付费群但没有公布价格,请联系群主。",
3731
+ "group.join.paid.needProof": "这是付费群({price} USDC)。请先向 {addr} 付款,再粘贴交易哈希提交申请。",
3732
+ "group.join.paid.note": "已付 {price} USDC 进群",
3733
+ "group.join.paid.applied": "已附付款证明并提交申请,等待群主验证。",
3134
3734
  "group.join.apply": "申请",
3135
3735
  "group.join.applied": "申请已发出。",
3136
3736
  "group.apps.badge": "{n} 条进群申请待处理",
3737
+ "col2.messages": "消息",
3137
3738
  "col2.contacts": "通讯录",
3138
3739
  "col2.agents": "智能体",
3139
3740
  "col2.groups": "群聊",
3140
3741
  "col2.contacts.section": "我",
3141
3742
  "col2.agents.empty": "还没有智能体——创建一个,让它替你做点事。",
3142
3743
  "col2.groups.empty": "还没有群——建一个,或用群链接加入。",
3744
+ "agents.expand": "展开 {n} 个",
3745
+ "agents.collapse": "收起",
3746
+ "messages.friends": "好友",
3747
+ "messages.section": "会话",
3143
3748
  "groups.section": "群聊",
3144
3749
  "pane.chat": "聊天",
3145
3750
  "pane.announce": "公告",
@@ -3148,6 +3753,7 @@ window.__ModuleLoader__.load({
3148
3753
  "pane.admin": "管理",
3149
3754
  "pane.info": "信息",
3150
3755
  "pane.settings": "设置",
3756
+ "pane.memory": "记忆",
3151
3757
  "friend.home.profile": "名片",
3152
3758
  "friend.home.fp": "指纹",
3153
3759
  "friend.home.name": "名字",
@@ -3156,6 +3762,7 @@ window.__ModuleLoader__.load({
3156
3762
  "friend.home.lastActive": "最近活跃",
3157
3763
  "friend.home.drafts": "待审稿",
3158
3764
  "friend.home.actions": "操作",
3765
+ "friend.home.sendMessage": "去发消息",
3159
3766
  "alter.home.profile": "档案",
3160
3767
  "alter.home.session": "会话",
3161
3768
  "alter.home.friends": "好友",
@@ -3637,6 +4244,7 @@ window.__ModuleLoader__.load({
3637
4244
  const [voices, setVoices] = (0, react.useState)(void 0);
3638
4245
  const [voiceCommanders, setVoiceCommanders] = (0, react.useState)({});
3639
4246
  const [duty, setDuty] = (0, react.useState)("");
4247
+ const [muted, setMuted] = (0, react.useState)(group.muted === true);
3640
4248
  const [agentNames, setAgentNames] = (0, react.useState)([]);
3641
4249
  const [agentsSheetOpen, setAgentsSheetOpen] = (0, react.useState)(false);
3642
4250
  const [mention, setMention] = (0, react.useState)(void 0);
@@ -3650,6 +4258,7 @@ window.__ModuleLoader__.load({
3650
4258
  const following = (0, react.useRef)(true);
3651
4259
  const prevFirstSeq = (0, react.useRef)(void 0);
3652
4260
  const prevHeight = (0, react.useRef)(0);
4261
+ const groupDrafts = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot).inbox.drafts.filter((d) => d.gid === gid);
3653
4262
  (0, react.useEffect)(() => {
3654
4263
  setText("");
3655
4264
  setVoices(void 0);
@@ -3725,6 +4334,7 @@ window.__ModuleLoader__.load({
3725
4334
  setVoices(map);
3726
4335
  setVoiceCommanders(cmds);
3727
4336
  setDuty(settings.duty ?? "");
4337
+ if (settings.muted !== void 0) setMuted(settings.muted === true);
3728
4338
  };
3729
4339
  (0, react.useEffect)(() => {
3730
4340
  if (!showAlterChip) return;
@@ -3767,6 +4377,16 @@ window.__ModuleLoader__.load({
3767
4377
  setDuty(prev);
3768
4378
  });
3769
4379
  };
4380
+ const toggleMuted = () => {
4381
+ const prev = muted;
4382
+ const next = !muted;
4383
+ setMuted(next);
4384
+ api.groupSettingsSet(gid, { muted: next }).then(({ settings }) => {
4385
+ applySettings(settings);
4386
+ }).catch(() => {
4387
+ setMuted(prev);
4388
+ });
4389
+ };
3770
4390
  /** Load one of MY agents' work-session trace for this group (local; persists in the timeline). */
3771
4391
  const loadFeed = (name) => {
3772
4392
  api.agentHistory(name, 80, gid).then((h) => {
@@ -4124,6 +4744,18 @@ window.__ModuleLoader__.load({
4124
4744
  ]
4125
4745
  })
4126
4746
  }),
4747
+ groupDrafts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4748
+ className: "sm-pendbar",
4749
+ "data-soulmirror-group-draft-reminder": true,
4750
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.draftReminder") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4751
+ type: "button",
4752
+ className: "sm-linkbtn",
4753
+ onClick: () => {
4754
+ pageStore.select(ALTER_KEY);
4755
+ },
4756
+ children: t("group.draftReminder.go")
4757
+ })]
4758
+ }) : null,
4127
4759
  mutedHint !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4128
4760
  className: "sm-mutebar",
4129
4761
  "data-soulmirror-group-composer": true,
@@ -4132,120 +4764,147 @@ window.__ModuleLoader__.load({
4132
4764
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4133
4765
  className: "sm-composer",
4134
4766
  "data-soulmirror-group-composer": true,
4135
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4136
- className: "sm-composer-box",
4137
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4767
+ children: [
4768
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4138
4769
  style: {
4139
- position: "relative",
4140
- flex: 1,
4141
- display: "flex"
4770
+ display: "flex",
4771
+ alignItems: "center",
4772
+ gap: 14,
4773
+ padding: "0 16px 6px"
4142
4774
  },
4143
- children: [mention !== void 0 && mentionMatches.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4775
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4776
+ className: `sm-switch${muted ? " sm-on" : ""}`,
4777
+ "data-soulmirror-group-mute": true,
4778
+ children: [
4779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4780
+ type: "checkbox",
4781
+ checked: muted,
4782
+ onChange: toggleMuted
4783
+ }),
4784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4785
+ className: "sm-switch-track",
4786
+ "aria-hidden": true
4787
+ }),
4788
+ t("group.mute")
4789
+ ]
4790
+ }), alterSwitch]
4791
+ }),
4792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4793
+ className: "sm-composer-box",
4794
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4144
4795
  style: {
4145
- position: "absolute",
4146
- bottom: "100%",
4147
- left: 0,
4148
- marginBottom: 6,
4149
- zIndex: 20,
4150
- minWidth: 180,
4151
- maxHeight: 220,
4152
- overflowY: "auto",
4153
- borderRadius: 8,
4154
- border: "1px solid rgba(127,127,127,.35)",
4155
- background: "var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2, #fff))",
4156
- boxShadow: "0 6px 24px rgba(0,0,0,.18)",
4157
- padding: 4,
4158
- display: "grid"
4796
+ position: "relative",
4797
+ flex: 1,
4798
+ display: "flex"
4159
4799
  },
4160
- "data-soulmirror-mention-pop": true,
4161
- children: mentionMatches.map((p, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4162
- type: "button",
4163
- className: "sm-linkbtn",
4800
+ children: [mention !== void 0 && mentionMatches.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4164
4801
  style: {
4165
- textAlign: "left",
4166
- padding: "6px 8px",
4167
- borderRadius: 6,
4168
- ...i === Math.min(mentionIdx, mentionMatches.length - 1) ? { background: "var(--dsw-alias-interactive-bg-hover)" } : {}
4802
+ position: "absolute",
4803
+ bottom: "100%",
4804
+ left: 0,
4805
+ marginBottom: 6,
4806
+ zIndex: 20,
4807
+ minWidth: 180,
4808
+ maxHeight: 220,
4809
+ overflowY: "auto",
4810
+ borderRadius: 8,
4811
+ border: "1px solid rgba(127,127,127,.35)",
4812
+ background: "var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2, #fff))",
4813
+ boxShadow: "0 6px 24px rgba(0,0,0,.18)",
4814
+ padding: 4,
4815
+ display: "grid"
4169
4816
  },
4170
- onMouseEnter: () => {
4171
- setMentionIdx(i);
4817
+ "data-soulmirror-mention-pop": true,
4818
+ children: mentionMatches.map((p, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4819
+ type: "button",
4820
+ className: "sm-linkbtn",
4821
+ style: {
4822
+ textAlign: "left",
4823
+ padding: "6px 8px",
4824
+ borderRadius: 6,
4825
+ ...i === Math.min(mentionIdx, mentionMatches.length - 1) ? { background: "var(--dsw-alias-interactive-bg-hover)" } : {}
4826
+ },
4827
+ onMouseEnter: () => {
4828
+ setMentionIdx(i);
4829
+ },
4830
+ onMouseDown: (e) => {
4831
+ e.preventDefault();
4832
+ pickMention(p.name);
4833
+ },
4834
+ children: [
4835
+ p.owner !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4836
+ "aria-hidden": true,
4837
+ style: { marginRight: 4 },
4838
+ children: "🤖"
4839
+ }) : null,
4840
+ "@",
4841
+ p.name,
4842
+ p.owner !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4843
+ className: "sm-statepill",
4844
+ style: { marginLeft: 6 },
4845
+ children: p.owner
4846
+ }) : null
4847
+ ]
4848
+ }, `${p.name}|${p.owner ?? ""}`))
4849
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4850
+ ref: inputRef,
4851
+ className: "sm-textarea",
4852
+ rows: 1,
4853
+ value: text,
4854
+ placeholder: t("group.composer.placeholder", { name: group.name }),
4855
+ onChange: (e) => {
4856
+ setText(e.target.value);
4857
+ detectMention(e.target.value, e.target.selectionStart ?? e.target.value.length);
4172
4858
  },
4173
- onMouseDown: (e) => {
4174
- e.preventDefault();
4175
- pickMention(p.name);
4859
+ onBlur: () => {
4860
+ setTimeout(() => {
4861
+ setMention(void 0);
4862
+ }, 120);
4176
4863
  },
4177
- children: [
4178
- p.owner !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4179
- "aria-hidden": true,
4180
- style: { marginRight: 4 },
4181
- children: "🤖"
4182
- }) : null,
4183
- "@",
4184
- p.name,
4185
- p.owner !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4186
- className: "sm-statepill",
4187
- style: { marginLeft: 6 },
4188
- children: p.owner
4189
- }) : null
4190
- ]
4191
- }, `${p.name}|${p.owner ?? ""}`))
4192
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4193
- ref: inputRef,
4194
- className: "sm-textarea",
4195
- rows: 1,
4196
- value: text,
4197
- placeholder: t("group.composer.placeholder", { name: group.name }),
4198
- onChange: (e) => {
4199
- setText(e.target.value);
4200
- detectMention(e.target.value, e.target.selectionStart ?? e.target.value.length);
4201
- },
4202
- onBlur: () => {
4203
- setTimeout(() => {
4204
- setMention(void 0);
4205
- }, 120);
4206
- },
4207
- onKeyDown: (e) => {
4208
- if (e.key === "Escape" && mention !== void 0) {
4209
- e.preventDefault();
4210
- e.stopPropagation();
4211
- setMention(void 0);
4212
- return;
4213
- }
4214
- if (mention !== void 0 && mentionMatches.length > 0 && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
4215
- e.preventDefault();
4216
- const len = mentionMatches.length;
4217
- setMentionIdx((i) => (Math.min(i, len - 1) + (e.key === "ArrowDown" ? 1 : len - 1)) % len);
4218
- return;
4219
- }
4220
- if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
4221
- e.preventDefault();
4222
- if (mention !== void 0 && mentionMatches.length > 0) pickMention(mentionMatches[Math.min(mentionIdx, mentionMatches.length - 1)].name);
4223
- else send();
4224
- }
4225
- },
4226
- "data-soulmirror-group-input": true
4864
+ onKeyDown: (e) => {
4865
+ if (e.key === "Escape" && mention !== void 0) {
4866
+ e.preventDefault();
4867
+ e.stopPropagation();
4868
+ setMention(void 0);
4869
+ return;
4870
+ }
4871
+ if (mention !== void 0 && mentionMatches.length > 0 && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
4872
+ e.preventDefault();
4873
+ const len = mentionMatches.length;
4874
+ setMentionIdx((i) => (Math.min(i, len - 1) + (e.key === "ArrowDown" ? 1 : len - 1)) % len);
4875
+ return;
4876
+ }
4877
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
4878
+ e.preventDefault();
4879
+ if (mention !== void 0 && mentionMatches.length > 0) pickMention(mentionMatches[Math.min(mentionIdx, mentionMatches.length - 1)].name);
4880
+ else send();
4881
+ }
4882
+ },
4883
+ "data-soulmirror-group-input": true
4884
+ })]
4885
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4886
+ variant: "primary",
4887
+ size: "sm",
4888
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline16, { size: 14 }),
4889
+ disabled: text.trim() === "",
4890
+ onClick: send,
4891
+ "data-soulmirror-group-send": true,
4892
+ children: t("group.send")
4227
4893
  })]
4228
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4229
- variant: "primary",
4230
- size: "sm",
4231
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline16, { size: 14 }),
4232
- disabled: text.trim() === "",
4233
- onClick: send,
4234
- "data-soulmirror-group-send": true,
4235
- children: t("group.send")
4236
- })]
4237
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4238
- className: "sm-composer-hint",
4239
- style: {
4240
- display: "flex",
4241
- alignItems: "center",
4242
- gap: 10
4243
- },
4244
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4245
- style: { flex: 1 },
4246
- children: t("group.composer.hint")
4247
- }), alterSwitch]
4248
- })]
4894
+ }),
4895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4896
+ className: "sm-composer-hint",
4897
+ style: {
4898
+ display: "flex",
4899
+ alignItems: "center",
4900
+ gap: 10
4901
+ },
4902
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4903
+ style: { flex: 1 },
4904
+ children: t("group.composer.hint")
4905
+ })
4906
+ })
4907
+ ]
4249
4908
  }),
4250
4909
  agentsSheetOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupAgentsSheet, {
4251
4910
  t,
@@ -4521,35 +5180,143 @@ window.__ModuleLoader__.load({
4521
5180
  })]
4522
5181
  })] });
4523
5182
  }
4524
- function SettingField({ label, field, scope, values, user, writable, type = "text", options, optionLabel, min }) {
4525
- const current = values?.[field] ?? "";
4526
- const [draft, setDraft] = (0, react.useState)(String(current));
5183
+ function WalletBlock({ t }) {
5184
+ const [copied, copy] = useCopy$1();
5185
+ const [wallet, setWallet] = (0, react.useState)(void 0);
5186
+ const [err, setErr] = (0, react.useState)(void 0);
5187
+ const refresh = (0, react.useCallback)(() => {
5188
+ api.payWallet().then((r) => {
5189
+ setWallet(r.wallet ?? null);
5190
+ setErr(r.error);
5191
+ }).catch(() => {
5192
+ setWallet(null);
5193
+ });
5194
+ }, []);
4527
5195
  (0, react.useEffect)(() => {
4528
- setDraft(String(current));
4529
- }, [current]);
4530
- const commit = () => {
4531
- if (draft === String(current)) return;
4532
- const value = type === "number" ? Math.max(min ?? 0, Math.floor(Number(draft) || 0)) : draft;
4533
- scope.set(field, value).catch(() => {});
4534
- };
4535
- const overridden = field in user;
4536
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4537
- style: {
4538
- display: "grid",
4539
- gap: 4,
4540
- fontSize: "0.85em"
4541
- },
4542
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4543
- style: { opacity: .8 },
4544
- children: [label, overridden ? " *" : ""]
4545
- }), type === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
4546
- value: draft,
4547
- disabled: !writable,
4548
- onChange: (e) => {
4549
- setDraft(e.target.value);
4550
- scope.set(field, e.target.value).catch(() => {});
4551
- },
4552
- style: input,
5196
+ refresh();
5197
+ const timer = setInterval(refresh, 15e3);
5198
+ return () => {
5199
+ clearInterval(timer);
5200
+ };
5201
+ }, [refresh]);
5202
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5203
+ style: card,
5204
+ "data-soulmirror-wallet-block": true,
5205
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
5206
+ style: h4,
5207
+ children: t("settings.wallet")
5208
+ }), wallet === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5209
+ style: small$1,
5210
+ children: t("settings.wallet.loading")
5211
+ }) : wallet === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5212
+ style: small$1,
5213
+ children: err !== void 0 ? err : t("settings.wallet.none")
5214
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5215
+ style: rowStyle,
5216
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5217
+ type: "button",
5218
+ onClick: refresh,
5219
+ "data-soulmirror-wallet-refresh": true,
5220
+ children: t("settings.wallet.refresh")
5221
+ })
5222
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5223
+ style: {
5224
+ display: "grid",
5225
+ gap: 4
5226
+ },
5227
+ children: [
5228
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5229
+ style: rowStyle,
5230
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5231
+ style: {
5232
+ opacity: .8,
5233
+ fontSize: "0.82em"
5234
+ },
5235
+ children: t("settings.wallet.address")
5236
+ }), wallet.network !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5237
+ style: {
5238
+ ...small$1,
5239
+ fontSize: "0.75em"
5240
+ },
5241
+ children: wallet.network
5242
+ }) : null]
5243
+ }),
5244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5245
+ style: mono,
5246
+ "data-soulmirror-wallet-address": true,
5247
+ children: wallet.address
5248
+ }),
5249
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5250
+ style: rowStyle,
5251
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5252
+ type: "button",
5253
+ onClick: () => {
5254
+ copy(wallet.address ?? "");
5255
+ },
5256
+ children: copied ? t("settings.card.copied") : t("settings.card.copy")
5257
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5258
+ type: "button",
5259
+ onClick: refresh,
5260
+ "data-soulmirror-wallet-refresh": true,
5261
+ children: t("settings.wallet.refresh")
5262
+ })]
5263
+ }),
5264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5265
+ style: {
5266
+ display: "grid",
5267
+ gridTemplateColumns: "1fr 1fr",
5268
+ gap: 6,
5269
+ marginTop: 2
5270
+ },
5271
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5272
+ style: small$1,
5273
+ children: [
5274
+ t("settings.wallet.balanceUsdc"),
5275
+ ": ",
5276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: wallet.balance_usdc ?? "—" })
5277
+ ]
5278
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5279
+ style: small$1,
5280
+ children: [
5281
+ t("settings.wallet.balanceEth"),
5282
+ ": ",
5283
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: wallet.balance_eth ?? "—" })
5284
+ ]
5285
+ })]
5286
+ })
5287
+ ]
5288
+ }) })]
5289
+ });
5290
+ }
5291
+ function SettingField({ label, field, scope, values, user, writable, type = "text", options, optionLabel, min }) {
5292
+ const current = values?.[field] ?? "";
5293
+ const [draft, setDraft] = (0, react.useState)(String(current));
5294
+ (0, react.useEffect)(() => {
5295
+ setDraft(String(current));
5296
+ }, [current]);
5297
+ const commit = () => {
5298
+ if (draft === String(current)) return;
5299
+ const value = type === "number" ? Math.max(min ?? 0, Math.floor(Number(draft) || 0)) : draft;
5300
+ scope.set(field, value).catch(() => {});
5301
+ };
5302
+ const overridden = field in user;
5303
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5304
+ style: {
5305
+ display: "grid",
5306
+ gap: 4,
5307
+ fontSize: "0.85em"
5308
+ },
5309
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5310
+ style: { opacity: .8 },
5311
+ children: [label, overridden ? " *" : ""]
5312
+ }), type === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
5313
+ value: draft,
5314
+ disabled: !writable,
5315
+ onChange: (e) => {
5316
+ setDraft(e.target.value);
5317
+ scope.set(field, e.target.value).catch(() => {});
5318
+ },
5319
+ style: input,
4553
5320
  "data-soulmirror-setting": field,
4554
5321
  children: (options ?? []).map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4555
5322
  value: o,
@@ -4557,7 +5324,7 @@ window.__ModuleLoader__.load({
4557
5324
  }, o))
4558
5325
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4559
5326
  style: input,
4560
- type: type === "number" ? "number" : "text",
5327
+ type: type === "password" ? "password" : type === "number" ? "number" : "text",
4561
5328
  min,
4562
5329
  value: draft,
4563
5330
  disabled: !writable,
@@ -4697,7 +5464,7 @@ window.__ModuleLoader__.load({
4697
5464
  const key = BINARY_SOURCE_KEYS[source];
4698
5465
  return key === void 0 ? source : t(key);
4699
5466
  }
4700
- function SoulmirrorSettingsSection({ openSession, scope, t }) {
5467
+ function SoulmirrorSettingsSection({ scope, t }) {
4701
5468
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
4702
5469
  const subscribeScope = (0, react.useCallback)((listener) => scope.subscribe(listener), [scope]);
4703
5470
  const readScope = (0, react.useCallback)(() => scope.getSnapshot(), [scope]);
@@ -4938,28 +5705,17 @@ window.__ModuleLoader__.load({
4938
5705
  }),
4939
5706
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4940
5707
  style: rowStyle,
4941
- children: [
4942
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4943
- type: "button",
4944
- onClick: () => {
4945
- pageStore.open("alter");
4946
- },
4947
- "data-soulmirror-settings-open-page": true,
4948
- children: t("settings.alter.openPage")
4949
- }),
4950
- state?.alter?.sessionId != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4951
- type: "button",
4952
- onClick: () => {
4953
- openSession(state.alter.sessionId);
4954
- },
4955
- "data-soulmirror-settings-open-alter-session": true,
4956
- children: t("settings.alter.openSession")
4957
- }) : null,
4958
- state?.alter?.legacyFriendSessions !== void 0 && Object.keys(state.alter.legacyFriendSessions).length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4959
- style: small$1,
4960
- children: t("settings.alter.legacy", { n: Object.keys(state.alter.legacyFriendSessions).length })
4961
- }) : null
4962
- ]
5708
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5709
+ type: "button",
5710
+ onClick: () => {
5711
+ pageStore.open("alter");
5712
+ },
5713
+ "data-soulmirror-settings-open-page": true,
5714
+ children: t("settings.alter.openPage")
5715
+ }), state?.alter?.legacyFriendSessions !== void 0 && Object.keys(state.alter.legacyFriendSessions).length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5716
+ style: small$1,
5717
+ children: t("settings.alter.legacy", { n: Object.keys(state.alter.legacyFriendSessions).length })
5718
+ }) : null]
4963
5719
  }),
4964
5720
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4965
5721
  style: {
@@ -4980,6 +5736,87 @@ window.__ModuleLoader__.load({
4980
5736
  })
4981
5737
  ]
4982
5738
  }),
5739
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WalletBlock, { t }),
5740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5741
+ style: card,
5742
+ "data-soulmirror-settings-pay": true,
5743
+ children: [
5744
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
5745
+ style: h4,
5746
+ children: t("settings.pay")
5747
+ }),
5748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5749
+ style: small$1,
5750
+ children: t("settings.pay.intro")
5751
+ }),
5752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5753
+ label: t("settings.pay.cdpKeyId"),
5754
+ field: "cdpKeyId",
5755
+ scope,
5756
+ values: settings.value,
5757
+ user: userLayer,
5758
+ writable
5759
+ }),
5760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5761
+ label: t("settings.pay.cdpKeySecret"),
5762
+ field: "cdpKeySecret",
5763
+ scope,
5764
+ values: settings.value,
5765
+ user: userLayer,
5766
+ writable,
5767
+ type: "password"
5768
+ }),
5769
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5770
+ label: t("settings.pay.cdpWalletSecret"),
5771
+ field: "cdpWalletSecret",
5772
+ scope,
5773
+ values: settings.value,
5774
+ user: userLayer,
5775
+ writable,
5776
+ type: "password"
5777
+ }),
5778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5779
+ label: t("settings.pay.cdpNetwork"),
5780
+ field: "cdpNetwork",
5781
+ scope,
5782
+ values: settings.value,
5783
+ user: userLayer,
5784
+ writable,
5785
+ type: "select",
5786
+ options: ["base-sepolia", "base"]
5787
+ }),
5788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5789
+ label: t("settings.pay.paygateProxy"),
5790
+ field: "paygateProxy",
5791
+ scope,
5792
+ values: settings.value,
5793
+ user: userLayer,
5794
+ writable
5795
+ }),
5796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5797
+ label: t("settings.pay.paygateBinary"),
5798
+ field: "paygateBinary",
5799
+ scope,
5800
+ values: settings.value,
5801
+ user: userLayer,
5802
+ writable
5803
+ }),
5804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingField, {
5805
+ label: t("settings.pay.paygatePort"),
5806
+ field: "paygatePort",
5807
+ scope,
5808
+ values: settings.value,
5809
+ user: userLayer,
5810
+ writable,
5811
+ type: "number",
5812
+ min: 1
5813
+ }),
5814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5815
+ style: small$1,
5816
+ children: t("settings.pay.hint")
5817
+ })
5818
+ ]
5819
+ }),
4983
5820
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4984
5821
  style: card,
4985
5822
  children: [
@@ -5550,14 +6387,36 @@ window.__ModuleLoader__.load({
5550
6387
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5551
6388
  style: { opacity: .8 },
5552
6389
  children: t("settings.agents.cwd")
5553
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5554
- style: field,
5555
- value: cwd,
5556
- placeholder: t("settings.agents.cwdHint"),
5557
- onChange: (e) => {
5558
- setCwd(e.target.value);
6390
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6391
+ style: {
6392
+ display: "flex",
6393
+ gap: 6
5559
6394
  },
5560
- "data-soulmirror-agent-cwd": true
6395
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6396
+ style: {
6397
+ ...field,
6398
+ flex: 1,
6399
+ minWidth: 0
6400
+ },
6401
+ value: cwd,
6402
+ placeholder: t("settings.agents.cwdHint"),
6403
+ onChange: (e) => {
6404
+ setCwd(e.target.value);
6405
+ },
6406
+ "data-soulmirror-agent-cwd": true
6407
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6408
+ type: "button",
6409
+ className: "sm-ghostbtn",
6410
+ style: { whiteSpace: "nowrap" },
6411
+ disabled: busy,
6412
+ onClick: () => {
6413
+ pickDirectory().then((p) => {
6414
+ if (p !== null) setCwd(p);
6415
+ });
6416
+ },
6417
+ "data-soulmirror-agent-cwd-browse": true,
6418
+ children: t("settings.agents.cwd.browse")
6419
+ })]
5561
6420
  })]
5562
6421
  }),
5563
6422
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -5685,15 +6544,377 @@ window.__ModuleLoader__.load({
5685
6544
  });
5686
6545
  }
5687
6546
  //#endregion
6547
+ //#region src/client/MemoryPane.tsx
6548
+ /**
6549
+ * The memory page (added as a pane tab for the alter and every seat agent):
6550
+ * list, add-by-hand, edit and delete memories, each tagged with its origin
6551
+ * (auto-extracted vs manual) and its scope (global vs this agent vs group).
6552
+ * The add and edit forms let the owner pick the scope (global vs only this
6553
+ * agent / only this group) instead of forcing the pane's fixed scope.
6554
+ */
6555
+ const pill = {
6556
+ fontSize: 10.5,
6557
+ padding: "1px 6px",
6558
+ borderRadius: 8,
6559
+ border: "1px solid rgba(127,127,127,.4)",
6560
+ color: "var(--dsw-alias-label-secondary)",
6561
+ whiteSpace: "nowrap"
6562
+ };
6563
+ const autoPill = {
6564
+ ...pill,
6565
+ borderColor: "rgba(90,150,255,.5)",
6566
+ color: "var(--dsw-alias-accent, #4c8dff)"
6567
+ };
6568
+ const manualPill = {
6569
+ ...pill,
6570
+ borderColor: "rgba(140,200,120,.5)",
6571
+ color: "var(--dsw-alias-state-success-primary, #4ade80)"
6572
+ };
6573
+ function scopeLabel(t, scope) {
6574
+ switch (scope.kind) {
6575
+ case "global": return t("memory.scope.global");
6576
+ case "agent": return t("memory.scope.agent", { name: scope.name ?? "" });
6577
+ case "shared-group": return t("memory.scope.group");
6578
+ case "shared-friend": return t("memory.scope.friend");
6579
+ default: return scope.kind;
6580
+ }
6581
+ }
6582
+ function MemoryPane({ t, allow, scope }) {
6583
+ const [items, setItems] = (0, react.useState)([]);
6584
+ const [loading, setLoading] = (0, react.useState)(true);
6585
+ const [draft, setDraft] = (0, react.useState)("");
6586
+ const [draftKind, setDraftKind] = (0, react.useState)(scope.kind === "agent" ? "agent" : scope.kind === "shared-group" ? "shared-group" : scope.kind === "shared-friend" ? "shared-friend" : "global");
6587
+ const [editing, setEditing] = (0, react.useState)(void 0);
6588
+ const [editText, setEditText] = (0, react.useState)("");
6589
+ const [editKind, setEditKind] = (0, react.useState)("global");
6590
+ const [busy, setBusy] = (0, react.useState)(false);
6591
+ /** Which scope the list is filtered to (`all` = show every allowed memory). */
6592
+ const [filter, setFilter] = (0, react.useState)("all");
6593
+ const agentName = scope.kind === "agent" ? scope.name : void 0;
6594
+ const groupGid = scope.kind === "shared-group" ? scope.gid : void 0;
6595
+ const friendFp = scope.kind === "shared-friend" ? scope.fp : void 0;
6596
+ /** Build the memory scope object for a selectable kind, using this pane's identity. */
6597
+ const scopeFor = (kind) => kind === "agent" ? {
6598
+ kind: "agent",
6599
+ name: agentName ?? ""
6600
+ } : kind === "shared-group" ? {
6601
+ kind: "shared-group",
6602
+ gid: groupGid ?? ""
6603
+ } : kind === "shared-friend" ? {
6604
+ kind: "shared-friend",
6605
+ fp: friendFp ?? ""
6606
+ } : { kind: "global" };
6607
+ /** The scope choices this pane offers (global always; its own only when it has one). */
6608
+ const scopeOptions = scope.kind === "agent" ? [{
6609
+ kind: "global",
6610
+ label: t("memory.scope.global")
6611
+ }, {
6612
+ kind: "agent",
6613
+ label: t("memory.scope.onlyAgent")
6614
+ }] : scope.kind === "shared-group" ? [{
6615
+ kind: "global",
6616
+ label: t("memory.scope.global")
6617
+ }, {
6618
+ kind: "shared-group",
6619
+ label: t("memory.scope.onlyGroup")
6620
+ }] : scope.kind === "shared-friend" ? [{
6621
+ kind: "global",
6622
+ label: t("memory.scope.global")
6623
+ }, {
6624
+ kind: "shared-friend",
6625
+ label: t("memory.scope.onlyFriend")
6626
+ }] : [{
6627
+ kind: "global",
6628
+ label: t("memory.scope.global")
6629
+ }];
6630
+ /** The list filtered by the active filter (`all` = everything allowed). */
6631
+ const filteredItems = filter === "all" ? items : items.filter((m) => m.scope.kind === filter);
6632
+ const reload = (0, react.useCallback)(() => {
6633
+ setLoading(true);
6634
+ api.memoryList(allow).then(({ memories }) => {
6635
+ setItems(memories);
6636
+ }).catch(() => {}).finally(() => {
6637
+ setLoading(false);
6638
+ });
6639
+ }, [allow]);
6640
+ (0, react.useEffect)(() => {
6641
+ reload();
6642
+ }, [reload]);
6643
+ const add = () => {
6644
+ if (draft.trim() === "" || busy) return;
6645
+ setBusy(true);
6646
+ api.memoryAdd({
6647
+ kind: "fact",
6648
+ content: draft.trim(),
6649
+ scope: scopeFor(draftKind)
6650
+ }).then(() => {
6651
+ setDraft("");
6652
+ reload();
6653
+ }).catch(() => {}).finally(() => {
6654
+ setBusy(false);
6655
+ });
6656
+ };
6657
+ const saveEdit = (uid) => {
6658
+ if (editText.trim() === "") return;
6659
+ api.memoryUpdate(uid, editText.trim(), scopeFor(editKind)).then(() => {
6660
+ setEditing(void 0);
6661
+ reload();
6662
+ }).catch(() => {});
6663
+ };
6664
+ const remove = (uid) => {
6665
+ api.memoryRemove(uid).then(() => {
6666
+ reload();
6667
+ }).catch(() => {});
6668
+ };
6669
+ const scopePicker = (value, onChange, dataAttr) => {
6670
+ if (scopeOptions.length <= 1) return null;
6671
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6672
+ style: {
6673
+ display: "flex",
6674
+ alignItems: "center",
6675
+ gap: 6,
6676
+ fontSize: 12,
6677
+ opacity: .85,
6678
+ whiteSpace: "nowrap"
6679
+ },
6680
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("memory.scope.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
6681
+ className: "sm-select",
6682
+ style: {
6683
+ fontSize: 12,
6684
+ padding: "3px 6px"
6685
+ },
6686
+ value,
6687
+ onChange: (e) => {
6688
+ onChange(e.target.value);
6689
+ },
6690
+ "data-soulmirror-memory-scope-pick": dataAttr,
6691
+ children: scopeOptions.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6692
+ value: o.kind,
6693
+ children: o.label
6694
+ }, o.kind))
6695
+ })]
6696
+ });
6697
+ };
6698
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6699
+ className: "sm-home",
6700
+ "data-soulmirror-memory-pane": true,
6701
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6702
+ className: "sm-home-inner",
6703
+ children: [
6704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6705
+ style: {
6706
+ display: "flex",
6707
+ gap: 8,
6708
+ alignItems: "flex-end"
6709
+ },
6710
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
6711
+ className: "sm-textarea-box",
6712
+ style: {
6713
+ flex: 1,
6714
+ minHeight: 40,
6715
+ resize: "vertical"
6716
+ },
6717
+ value: draft,
6718
+ placeholder: t("memory.add.placeholder"),
6719
+ onChange: (e) => {
6720
+ setDraft(e.target.value);
6721
+ },
6722
+ "data-soulmirror-memory-draft": true
6723
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6724
+ type: "button",
6725
+ className: "sm-ghostbtn",
6726
+ disabled: busy || draft.trim() === "",
6727
+ onClick: add,
6728
+ "data-soulmirror-memory-add": true,
6729
+ children: t("memory.add")
6730
+ })]
6731
+ }),
6732
+ scopePicker(draftKind, setDraftKind, "add"),
6733
+ scopeOptions.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6734
+ style: {
6735
+ display: "flex",
6736
+ alignItems: "center",
6737
+ gap: 6,
6738
+ flexWrap: "wrap",
6739
+ marginTop: 8
6740
+ },
6741
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6742
+ style: {
6743
+ fontSize: 12,
6744
+ opacity: .85
6745
+ },
6746
+ children: t("memory.filter.label")
6747
+ }), [{
6748
+ kind: "all",
6749
+ label: t("memory.filter.all")
6750
+ }, ...scopeOptions].map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6751
+ type: "button",
6752
+ className: "sm-ghostbtn",
6753
+ style: {
6754
+ fontSize: 12,
6755
+ padding: "2px 10px",
6756
+ borderRadius: 12,
6757
+ ...filter === o.kind ? {
6758
+ borderColor: "var(--dsw-alias-accent, #4c8dff)",
6759
+ color: "var(--dsw-alias-accent, #4c8dff)"
6760
+ } : {}
6761
+ },
6762
+ "aria-pressed": filter === o.kind,
6763
+ onClick: () => {
6764
+ setFilter(o.kind);
6765
+ },
6766
+ "data-soulmirror-memory-filter": o.kind,
6767
+ children: o.label
6768
+ }, o.kind))]
6769
+ }) : null,
6770
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6771
+ className: "sm-muted",
6772
+ style: {
6773
+ fontSize: 12,
6774
+ padding: "8px 0",
6775
+ display: "block"
6776
+ },
6777
+ children: t("page.thread.loading")
6778
+ }) : items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6779
+ className: "sm-muted",
6780
+ style: {
6781
+ fontSize: 12,
6782
+ padding: "8px 0",
6783
+ display: "block"
6784
+ },
6785
+ children: t("memory.empty.page")
6786
+ }) : filteredItems.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6787
+ className: "sm-muted",
6788
+ style: {
6789
+ fontSize: 12,
6790
+ padding: "8px 0",
6791
+ display: "block"
6792
+ },
6793
+ children: t("memory.empty.filter")
6794
+ }) : null,
6795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6796
+ style: {
6797
+ display: "grid",
6798
+ gap: 8,
6799
+ marginTop: 8
6800
+ },
6801
+ children: filteredItems.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6802
+ className: "sm-home-card",
6803
+ style: { padding: "10px 12px" },
6804
+ "data-soulmirror-memory": m.uid,
6805
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6806
+ style: {
6807
+ display: "flex",
6808
+ gap: 6,
6809
+ alignItems: "center",
6810
+ flexWrap: "wrap"
6811
+ },
6812
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6813
+ style: m.origin === "manual" ? manualPill : autoPill,
6814
+ "data-soulmirror-memory-origin": m.origin,
6815
+ children: m.origin === "manual" ? t("memory.origin.manual") : t("memory.origin.auto")
6816
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6817
+ style: pill,
6818
+ "data-soulmirror-memory-scope": m.scope.kind,
6819
+ children: scopeLabel(t, m.scope)
6820
+ })]
6821
+ }), editing === m.uid ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6822
+ style: {
6823
+ display: "grid",
6824
+ gap: 8,
6825
+ marginTop: 8
6826
+ },
6827
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
6828
+ className: "sm-textarea-box",
6829
+ style: {
6830
+ flex: 1,
6831
+ minHeight: 40
6832
+ },
6833
+ value: editText,
6834
+ onChange: (e) => {
6835
+ setEditText(e.target.value);
6836
+ }
6837
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6838
+ style: {
6839
+ display: "flex",
6840
+ gap: 8,
6841
+ alignItems: "center",
6842
+ flexWrap: "wrap"
6843
+ },
6844
+ children: [
6845
+ scopePicker(editKind, setEditKind, "edit"),
6846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
6847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6848
+ type: "button",
6849
+ className: "sm-ghostbtn",
6850
+ onClick: () => {
6851
+ saveEdit(m.uid);
6852
+ },
6853
+ "data-soulmirror-memory-save": true,
6854
+ children: t("memory.save")
6855
+ }),
6856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6857
+ type: "button",
6858
+ className: "sm-ghostbtn",
6859
+ onClick: () => {
6860
+ setEditing(void 0);
6861
+ },
6862
+ children: t("inbox.close")
6863
+ })
6864
+ ]
6865
+ })]
6866
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6867
+ style: {
6868
+ marginTop: 6,
6869
+ fontSize: 13,
6870
+ lineHeight: 1.5,
6871
+ whiteSpace: "pre-wrap",
6872
+ overflowWrap: "anywhere"
6873
+ },
6874
+ children: m.content
6875
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6876
+ style: {
6877
+ display: "flex",
6878
+ gap: 8,
6879
+ marginTop: 6
6880
+ },
6881
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6882
+ type: "button",
6883
+ className: "sm-linkbtn sm-muted",
6884
+ style: { fontSize: 11.5 },
6885
+ onClick: () => {
6886
+ setEditing(m.uid);
6887
+ setEditText(m.content);
6888
+ setEditKind(m.scope.kind === "agent" ? "agent" : m.scope.kind === "shared-group" ? "shared-group" : m.scope.kind === "shared-friend" ? "shared-friend" : "global");
6889
+ },
6890
+ "data-soulmirror-memory-edit": true,
6891
+ children: t("memory.edit")
6892
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6893
+ type: "button",
6894
+ className: "sm-linkbtn sm-muted",
6895
+ style: { fontSize: 11.5 },
6896
+ onClick: () => {
6897
+ remove(m.uid);
6898
+ },
6899
+ "data-soulmirror-memory-delete": true,
6900
+ children: t("memory.delete")
6901
+ })]
6902
+ })] })]
6903
+ }, m.uid))
6904
+ })
6905
+ ]
6906
+ })
6907
+ });
6908
+ }
6909
+ //#endregion
5688
6910
  //#region src/client/AgentPane.tsx
5689
6911
  /**
5690
6912
  * Right pane for one NAMED seat agent: the owner talks to it directly here
5691
6913
  * (composer → `agent.instruct`), watches its transcript (`agent.history`,
5692
6914
  * same fold as the alter's — owner bubbles right, the agent's notes left,
5693
6915
  * the group messages that woke it as compact cards, its group posts as send
5694
- * lines) and reaches its settings (the sheet) and its native dsh session
5695
- * from the header. Live: SSE `agent` frames for this name trigger a
5696
- * debounced refetch.
6916
+ * lines) and reaches its settings (the sheet). Live: SSE `agent` frames for
6917
+ * this name trigger a debounced refetch.
5697
6918
  */
5698
6919
  const FOLLOW_SLACK$2 = 48;
5699
6920
  const HISTORY_LIMIT = 200;
@@ -5702,7 +6923,7 @@ window.__ModuleLoader__.load({
5702
6923
  const d = new Date(ts);
5703
6924
  return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
5704
6925
  }
5705
- function AgentPane({ t, agent, onOpenSession, onRemoved }) {
6926
+ function AgentPane({ t, agent, onRemoved }) {
5706
6927
  const name = agent.name;
5707
6928
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
5708
6929
  const [history, setHistory] = (0, react.useState)(void 0);
@@ -5999,13 +7220,13 @@ window.__ModuleLoader__.load({
5999
7220
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6000
7221
  className: "sm-home-title",
6001
7222
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("agent.home.actions") })
6002
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7223
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6003
7224
  style: {
6004
7225
  display: "flex",
6005
7226
  gap: 6,
6006
7227
  flexWrap: "wrap"
6007
7228
  },
6008
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7229
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6009
7230
  type: "button",
6010
7231
  className: "sm-ghostbtn",
6011
7232
  onClick: () => {
@@ -6016,18 +7237,7 @@ window.__ModuleLoader__.load({
6016
7237
  " ",
6017
7238
  t("agent.settings")
6018
7239
  ]
6019
- }), sessionId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6020
- type: "button",
6021
- className: "sm-ghostbtn",
6022
- onClick: () => {
6023
- onOpenSession(sessionId);
6024
- },
6025
- children: [
6026
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6027
- " ",
6028
- t("alter.openDsh")
6029
- ]
6030
- }) : null]
7240
+ })
6031
7241
  })]
6032
7242
  })
6033
7243
  ]
@@ -6071,9 +7281,9 @@ window.__ModuleLoader__.load({
6071
7281
  ]
6072
7282
  })]
6073
7283
  }),
6074
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6075
7285
  className: "sm-chat-head-actions",
6076
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
7286
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6077
7287
  label: t("agent.settings"),
6078
7288
  side: "bottom",
6079
7289
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -6089,23 +7299,7 @@ window.__ModuleLoader__.load({
6089
7299
  t("agent.settings")
6090
7300
  ]
6091
7301
  })
6092
- }), sessionId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6093
- label: t("alter.openDsh.hint"),
6094
- side: "bottom",
6095
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6096
- type: "button",
6097
- className: "sm-ghostbtn",
6098
- onClick: () => {
6099
- onOpenSession(sessionId);
6100
- },
6101
- "data-soulmirror-agent-open-dsh": true,
6102
- children: [
6103
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6104
- " ",
6105
- t("alter.openDsh")
6106
- ]
6107
- })
6108
- }) : null]
7302
+ })
6109
7303
  })
6110
7304
  ]
6111
7305
  }),
@@ -6115,7 +7309,17 @@ window.__ModuleLoader__.load({
6115
7309
  onChange: pageStore.setPaneTab,
6116
7310
  t
6117
7311
  }),
6118
- paneTab === "info" ? agentInfo : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7312
+ paneTab === "info" ? agentInfo : paneTab === "memory" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryPane, {
7313
+ t,
7314
+ allow: {
7315
+ global: true,
7316
+ agent: name
7317
+ },
7318
+ scope: {
7319
+ kind: "agent",
7320
+ name
7321
+ }
7322
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6119
7323
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6120
7324
  ref: scroller,
6121
7325
  className: "sm-thread",
@@ -6244,12 +7448,17 @@ window.__ModuleLoader__.load({
6244
7448
  default: return t("draft.reason.unknown-trigger");
6245
7449
  }
6246
7450
  }
6247
- function DraftCard({ draft, t, showTarget, onGoFriend }) {
7451
+ function DraftCard({ draft, t, showTarget, onGoFriend, onGoGroup }) {
6248
7452
  const busy = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot).deciding.includes(draft.id);
6249
7453
  const [mode, setMode] = (0, react.useState)("idle");
6250
7454
  const [text, setText] = (0, react.useState)(draft.body);
6251
7455
  const [feedback, setFeedback] = (0, react.useState)("");
6252
7456
  const inReplyTo = draft.trigger?.kind === "inbound" ? draft.trigger.name ?? draft.name : void 0;
7457
+ const openTarget = draft.gid !== void 0 && onGoGroup !== void 0 ? () => {
7458
+ onGoGroup(draft.gid);
7459
+ } : onGoFriend !== void 0 ? () => {
7460
+ onGoFriend(draft.fp);
7461
+ } : void 0;
6253
7462
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6254
7463
  className: "sm-draft",
6255
7464
  "data-soulmirror-draft": draft.id,
@@ -6263,13 +7472,11 @@ window.__ModuleLoader__.load({
6263
7472
  children: t("draft.tag")
6264
7473
  }),
6265
7474
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: showTarget === true ? t("draft.title.to", { name: draft.name }) : t("draft.title") }),
6266
- showTarget === true && onGoFriend !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7475
+ showTarget === true && openTarget !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6267
7476
  type: "button",
6268
7477
  className: "sm-linkbtn",
6269
- onClick: () => {
6270
- onGoFriend(draft.fp);
6271
- },
6272
- children: t("draft.openThread")
7478
+ onClick: openTarget,
7479
+ children: draft.gid !== void 0 ? t("draft.openGroup") : t("draft.openThread")
6273
7480
  }) : null,
6274
7481
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6275
7482
  className: "sm-row-time",
@@ -6436,7 +7643,7 @@ window.__ModuleLoader__.load({
6436
7643
  const d = new Date(ts);
6437
7644
  return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
6438
7645
  }
6439
- function AlterPane({ t, onOpenSession, onGoFriend, renderCards, scope }) {
7646
+ function AlterPane({ t, onGoFriend, onGoGroup, renderCards, scope }) {
6440
7647
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
6441
7648
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
6442
7649
  const alter = page.alter;
@@ -6444,6 +7651,23 @@ window.__ModuleLoader__.load({
6444
7651
  const drafts = net.inbox.drafts;
6445
7652
  const running = alter.status === "running" || alter.chat.running || alter.instructing;
6446
7653
  const [draft, setDraft] = (0, react.useState)("");
7654
+ const [wallet, setWallet] = (0, react.useState)(void 0);
7655
+ (0, react.useEffect)(() => {
7656
+ let alive = true;
7657
+ const load = () => {
7658
+ api.payWallet().then((r) => {
7659
+ if (alive) setWallet(r.wallet ?? null);
7660
+ }).catch(() => {
7661
+ if (alive) setWallet(null);
7662
+ });
7663
+ };
7664
+ load();
7665
+ const timer = setInterval(load, 15e3);
7666
+ return () => {
7667
+ alive = false;
7668
+ clearInterval(timer);
7669
+ };
7670
+ }, []);
6447
7671
  const scroller = (0, react.useRef)(null);
6448
7672
  const textarea = (0, react.useRef)(null);
6449
7673
  const following = (0, react.useRef)(true);
@@ -6460,7 +7684,7 @@ window.__ModuleLoader__.load({
6460
7684
  if (el === null) return;
6461
7685
  if (firstPaint.current && items.length > 0) {
6462
7686
  firstPaint.current = false;
6463
- el.scrollTop = 0;
7687
+ el.scrollTop = el.scrollHeight;
6464
7688
  return;
6465
7689
  }
6466
7690
  if (following.current) el.scrollTop = el.scrollHeight;
@@ -6568,31 +7792,33 @@ window.__ModuleLoader__.load({
6568
7792
  })]
6569
7793
  });
6570
7794
  case "inbound": {
6571
- const name = nameOf(friends, item.fp, item.name);
7795
+ const group = item.gid === void 0 ? void 0 : net.inbox.groups.find((g) => g.gid === item.gid);
7796
+ const name = group !== void 0 ? group.name : nameOf(friends, item.fp, item.name);
7797
+ const open = () => {
7798
+ if (group !== void 0) onGoGroup(group.gid);
7799
+ else onGoFriend(item.fp);
7800
+ };
6572
7801
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6573
7802
  className: "sm-citem sm-wide",
6574
- "data-soulmirror-alter-item": "inbound",
7803
+ "data-soulmirror-alter-item": group !== void 0 ? "group" : "inbound",
6575
7804
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6576
7805
  className: "sm-inmail",
6577
7806
  role: "button",
6578
7807
  tabIndex: 0,
6579
- onClick: () => {
6580
- onGoFriend(item.fp);
6581
- },
7808
+ onClick: open,
6582
7809
  onKeyDown: (e) => {
6583
- if (e.key === "Enter") onGoFriend(item.fp);
7810
+ if (e.key === "Enter") open();
6584
7811
  },
6585
7812
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6586
7813
  className: "sm-inmail-head",
6587
7814
  children: [
6588
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.item.inbound", { name: "" }) }),
6589
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: name }),
7815
+ group !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.item.groupInbound", { name }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.item.inbound", { name: "" }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: name })] }),
6590
7816
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", formatClock(item.ts)] }),
6591
7817
  item.auto ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", t("bubble.auto")] }) : null,
6592
7818
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6593
7819
  style: { marginLeft: "auto" },
6594
7820
  className: "sm-linkbtn",
6595
- children: t("alter.item.view")
7821
+ children: group !== void 0 ? t("alter.item.viewGroup", { name }) : t("alter.item.view")
6596
7822
  })
6597
7823
  ]
6598
7824
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6725,110 +7951,88 @@ window.__ModuleLoader__.load({
6725
7951
  "data-soulmirror-alter-home": true,
6726
7952
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6727
7953
  className: "sm-home-inner",
6728
- children: [
6729
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6730
- className: "sm-home-id",
7954
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7955
+ className: "sm-home-id",
7956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7957
+ className: "sm-avatar sm-avatar-alter sm-avatar-lg",
7958
+ "aria-hidden": true,
7959
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 18 })
7960
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7961
+ style: {
7962
+ display: "grid",
7963
+ gap: 2,
7964
+ minWidth: 0
7965
+ },
6731
7966
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6732
- className: "sm-avatar sm-avatar-alter sm-avatar-lg",
6733
- "aria-hidden": true,
6734
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 18 })
6735
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6736
- style: {
6737
- display: "grid",
6738
- gap: 2,
6739
- minWidth: 0
6740
- },
7967
+ className: "sm-home-id-name",
7968
+ children: t("alter.me")
7969
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7970
+ className: "sm-home-id-sub",
6741
7971
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6742
- className: "sm-home-id-name",
6743
- children: t("alter.me")
6744
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6745
- className: "sm-home-id-sub",
6746
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6747
- className: `sm-livedot${running ? " sm-busy" : ""}`,
6748
- "aria-hidden": true
6749
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: alter.sessionId === void 0 ? t("alter.status.noSession") : running ? t("alter.status.running") : t("alter.status.idle") })]
6750
- })]
7972
+ className: `sm-livedot${running ? " sm-busy" : ""}`,
7973
+ "aria-hidden": true
7974
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: alter.sessionId === void 0 ? t("alter.status.noSession") : running ? t("alter.status.running") : t("alter.status.idle") })]
6751
7975
  })]
6752
- }),
6753
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6754
- className: "sm-home-card",
6755
- children: [
6756
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6757
- className: "sm-home-title",
6758
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.home.profile") })
6759
- }),
6760
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6761
- className: "sm-home-line",
6762
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6763
- className: "sm-home-line-key",
6764
- children: t("alter.home.session")
6765
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6766
- className: "sm-home-line-val",
6767
- children: alter.sessionId ?? t("alter.home.none")
6768
- })]
6769
- }),
6770
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6771
- className: "sm-home-line",
6772
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6773
- className: "sm-home-line-key",
6774
- children: t("alter.home.friends")
6775
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6776
- className: "sm-home-line-val",
6777
- children: friends.length
6778
- })]
6779
- }),
6780
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6781
- className: "sm-home-line",
6782
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6783
- className: "sm-home-line-key",
6784
- children: t("alter.home.drafts")
6785
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6786
- className: "sm-home-line-val",
6787
- children: drafts.length
6788
- })]
6789
- }),
6790
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6791
- className: "sm-home-line",
6792
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6793
- className: "sm-home-line-key",
6794
- children: t("alter.home.defaultTier")
6795
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6796
- className: "sm-home-line-val",
6797
- children: t(`tier.short.${net.state?.alter?.defaultTier ?? "draft"}`)
6798
- })]
6799
- }),
6800
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6801
- className: "sm-home-line",
6802
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6803
- className: "sm-home-line-key",
6804
- children: t("alter.home.perHour")
6805
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6806
- className: "sm-home-line-val",
6807
- children: net.state?.alter?.autoReplyPerHour ?? 20
6808
- })]
6809
- })
6810
- ]
6811
- }),
6812
- alter.sessionId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6813
- className: "sm-home-card",
6814
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7976
+ })]
7977
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7978
+ className: "sm-home-card",
7979
+ children: [
7980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6815
7981
  className: "sm-home-title",
6816
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.home.actions") })
6817
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6818
- type: "button",
6819
- className: "sm-ghostbtn",
6820
- onClick: () => {
6821
- onOpenSession(alter.sessionId);
6822
- },
6823
- "data-soulmirror-alter-open-dsh": true,
6824
- children: [
6825
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6826
- " ",
6827
- t("alter.openDsh")
6828
- ]
6829
- })]
6830
- }) : null
6831
- ]
7982
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.home.profile") })
7983
+ }),
7984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7985
+ className: "sm-home-line",
7986
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7987
+ className: "sm-home-line-key",
7988
+ children: t("alter.home.session")
7989
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7990
+ className: "sm-home-line-val",
7991
+ children: alter.sessionId ?? t("alter.home.none")
7992
+ })]
7993
+ }),
7994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7995
+ className: "sm-home-line",
7996
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7997
+ className: "sm-home-line-key",
7998
+ children: t("alter.home.friends")
7999
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8000
+ className: "sm-home-line-val",
8001
+ children: friends.length
8002
+ })]
8003
+ }),
8004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8005
+ className: "sm-home-line",
8006
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8007
+ className: "sm-home-line-key",
8008
+ children: t("alter.home.drafts")
8009
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8010
+ className: "sm-home-line-val",
8011
+ children: drafts.length
8012
+ })]
8013
+ }),
8014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8015
+ className: "sm-home-line",
8016
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8017
+ className: "sm-home-line-key",
8018
+ children: t("alter.home.defaultTier")
8019
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8020
+ className: "sm-home-line-val",
8021
+ children: t(`tier.short.${net.state?.alter?.defaultTier ?? "draft"}`)
8022
+ })]
8023
+ }),
8024
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8025
+ className: "sm-home-line",
8026
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8027
+ className: "sm-home-line-key",
8028
+ children: t("alter.home.perHour")
8029
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8030
+ className: "sm-home-line-val",
8031
+ children: net.state?.alter?.autoReplyPerHour ?? 20
8032
+ })]
8033
+ })
8034
+ ]
8035
+ })]
6832
8036
  })
6833
8037
  });
6834
8038
  const cardProps = {
@@ -6836,10 +8040,7 @@ window.__ModuleLoader__.load({
6836
8040
  sessionId: alter.sessionId,
6837
8041
  status: alter.status
6838
8042
  },
6839
- scope,
6840
- openSession: () => {
6841
- if (alter.sessionId !== void 0) onOpenSession(alter.sessionId);
6842
- }
8043
+ scope
6843
8044
  };
6844
8045
  const alterSettings = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6845
8046
  className: "sm-home",
@@ -6857,58 +8058,86 @@ window.__ModuleLoader__.load({
6857
8058
  children: [
6858
8059
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
6859
8060
  className: "sm-chat-head",
8061
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8062
+ className: "sm-avatar sm-avatar-alter sm-avatar-lg",
8063
+ "aria-hidden": true,
8064
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 18 })
8065
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8066
+ style: {
8067
+ flex: 1,
8068
+ minWidth: 0,
8069
+ display: "grid"
8070
+ },
8071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8072
+ className: "sm-chat-head-name",
8073
+ children: t("alter.me")
8074
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8075
+ className: "sm-chat-head-sub",
8076
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8077
+ className: `sm-livedot${running ? " sm-busy" : ""}`,
8078
+ "aria-hidden": true
8079
+ }), alter.sessionId === void 0 ? t("alter.status.noSession") : running ? t("alter.status.running") : t("alter.status.idle")]
8080
+ })]
8081
+ })]
8082
+ }),
8083
+ wallet !== void 0 && wallet !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8084
+ className: "sm-alter-wallet",
8085
+ "data-soulmirror-alter-wallet": true,
8086
+ style: {
8087
+ display: "flex",
8088
+ alignItems: "center",
8089
+ gap: 8,
8090
+ padding: "6px 14px",
8091
+ fontSize: "0.8em",
8092
+ opacity: .9,
8093
+ borderBottom: "1px solid rgba(127,127,127,.18)"
8094
+ },
6860
8095
  children: [
6861
8096
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6862
- className: "sm-avatar sm-avatar-alter sm-avatar-lg",
6863
- "aria-hidden": true,
6864
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 18 })
8097
+ style: { opacity: .7 },
8098
+ children: t("alter.wallet")
6865
8099
  }),
6866
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6867
8101
  style: {
8102
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
6868
8103
  flex: 1,
6869
8104
  minWidth: 0,
6870
- display: "grid"
8105
+ overflow: "hidden",
8106
+ textOverflow: "ellipsis",
8107
+ whiteSpace: "nowrap"
6871
8108
  },
6872
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6873
- className: "sm-chat-head-name",
6874
- children: t("alter.me")
6875
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6876
- className: "sm-chat-head-sub",
6877
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6878
- className: `sm-livedot${running ? " sm-busy" : ""}`,
6879
- "aria-hidden": true
6880
- }), alter.sessionId === void 0 ? t("alter.status.noSession") : running ? t("alter.status.running") : t("alter.status.idle")]
6881
- })]
8109
+ title: wallet.address,
8110
+ "data-soulmirror-alter-wallet-address": true,
8111
+ children: wallet.address
8112
+ }),
8113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8114
+ style: {
8115
+ opacity: .85,
8116
+ whiteSpace: "nowrap"
8117
+ },
8118
+ children: wallet.balance_usdc !== void 0 ? `USDC ${wallet.balance_usdc}` : ""
6882
8119
  }),
6883
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6884
- className: "sm-chat-head-actions",
6885
- children: alter.sessionId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6886
- label: t("alter.openDsh.hint"),
6887
- side: "bottom",
6888
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6889
- type: "button",
6890
- className: "sm-ghostbtn",
6891
- onClick: () => {
6892
- onOpenSession(alter.sessionId);
6893
- },
6894
- "data-soulmirror-alter-open-dsh": true,
6895
- children: [
6896
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6897
- " ",
6898
- t("alter.openDsh")
6899
- ]
6900
- })
6901
- }) : null
6902
- })
8120
+ wallet.balance_eth !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8121
+ style: {
8122
+ opacity: .6,
8123
+ whiteSpace: "nowrap"
8124
+ },
8125
+ "data-soulmirror-alter-wallet-eth": true,
8126
+ children: ["ETH ", Number(wallet.balance_eth).toFixed(4)]
8127
+ }) : null
6903
8128
  ]
6904
- }),
8129
+ }) : null,
6905
8130
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContentTabs, {
6906
8131
  tabs,
6907
8132
  active: paneTab,
6908
8133
  onChange: pageStore.setPaneTab,
6909
8134
  t
6910
8135
  }),
6911
- paneTab === "settings" ? alterSettings : paneTab === "home" ? alterHome : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8136
+ paneTab === "settings" ? alterSettings : paneTab === "home" ? alterHome : paneTab === "memory" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryPane, {
8137
+ t,
8138
+ allow: { global: true },
8139
+ scope: { kind: "global" }
8140
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6912
8141
  drafts.length > 0 && firstDraft !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6913
8142
  className: "sm-pendbar",
6914
8143
  "data-soulmirror-alter-pendbar": drafts.length,
@@ -6965,7 +8194,8 @@ window.__ModuleLoader__.load({
6965
8194
  draft: d,
6966
8195
  t,
6967
8196
  showTarget: true,
6968
- onGoFriend
8197
+ onGoFriend,
8198
+ onGoGroup
6969
8199
  }, d.id)),
6970
8200
  running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6971
8201
  className: "sm-citem sm-alter",
@@ -7028,6 +8258,36 @@ window.__ModuleLoader__.load({
7028
8258
  });
7029
8259
  }
7030
8260
  //#endregion
8261
+ //#region src/group-contract.ts
8262
+ /**
8263
+ * Paid-join compatibility encoding: on relays that only accept
8264
+ * join ∈ {invite, apply, open}, the paid config is carried as a convention
8265
+ * line in `rules` (`#paid-join <price> <0xaddr>`) — a field old relays keep
8266
+ * verbatim. The line is hidden from every user-facing rules display.
8267
+ */
8268
+ const PAID_JOIN_PREFIX = "#paid-join ";
8269
+ /** Parse the paid-join marker out of rules (any line, trimmed). */
8270
+ function paidJoinFromRules(rules) {
8271
+ if (rules === void 0 || rules === "") return void 0;
8272
+ for (const line of rules.split("\n")) {
8273
+ const t = line.trim();
8274
+ if (t.startsWith("#paid-join ")) {
8275
+ const rest = t.slice(11).trim().split(/\s+/);
8276
+ const price = rest[0];
8277
+ const addr = rest[1];
8278
+ if (price !== void 0 && addr !== void 0 && addr.startsWith("0x") && price !== "") return {
8279
+ price,
8280
+ addr
8281
+ };
8282
+ }
8283
+ }
8284
+ }
8285
+ /** rules without the paid-join marker line (for display / prompts). */
8286
+ function rulesWithoutPaidJoin(rules) {
8287
+ if (rules === void 0 || rules === "") return "";
8288
+ return rules.split("\n").filter((l) => !l.trim().startsWith(PAID_JOIN_PREFIX)).join("\n").trim();
8289
+ }
8290
+ //#endregion
7031
8291
  //#region src/client/group-templates.ts
7032
8292
  const base = (id, patch) => ({
7033
8293
  id,
@@ -7099,7 +8359,7 @@ window.__ModuleLoader__.load({
7099
8359
  * The dialog owns the form state; the caller owns the actual creation
7100
8360
  * (`onCreate`) and the busy flag.
7101
8361
  */
7102
- function GroupCreateDialog({ t, friends, busy, onCreate, onClose }) {
8362
+ function GroupCreateDialog({ t, friends, busy, walletAddress, onCreate, onClose }) {
7103
8363
  const std = GROUP_TEMPLATES[0].profile;
7104
8364
  const [name, setName] = (0, react.useState)("");
7105
8365
  const [sel, setSel] = (0, react.useState)({});
@@ -7110,6 +8370,7 @@ window.__ModuleLoader__.load({
7110
8370
  speakAgents: std.speakAgents,
7111
8371
  speakWho: std.speakWho ?? "all",
7112
8372
  join: std.join ?? "invite",
8373
+ joinPrice: "",
7113
8374
  agentWake: std.agentWake ?? "mention",
7114
8375
  agentTier: std.agentTier ?? "draft",
7115
8376
  rules: "",
@@ -7126,10 +8387,12 @@ window.__ModuleLoader__.load({
7126
8387
  };
7127
8388
  }, [onClose]);
7128
8389
  const members = Object.entries(sel).filter(([, v]) => v).map(([k]) => k);
7129
- const ready = !busy && name.trim() !== "" && members.length > 0;
8390
+ const paidIncomplete = adv.join === "paid" && (walletAddress === void 0 || adv.joinPrice.trim() === "");
8391
+ const ready = !busy && name.trim() !== "" && members.length > 0 && !paidIncomplete;
7130
8392
  const submit = () => {
7131
8393
  if (!ready) return;
7132
8394
  const tags = adv.tags.split(",").map((s) => s.trim()).filter((s) => s !== "");
8395
+ const paid = adv.join === "paid" && walletAddress !== void 0 && adv.joinPrice.trim() !== "";
7133
8396
  const profile = templateProfile(tpl, {
7134
8397
  speakHumans: adv.speakHumans,
7135
8398
  speakAgents: adv.speakAgents,
@@ -7138,6 +8401,10 @@ window.__ModuleLoader__.load({
7138
8401
  agentWake: adv.agentWake,
7139
8402
  agentTier: adv.agentTier,
7140
8403
  ...adv.rules.trim() === "" ? {} : { rules: adv.rules },
8404
+ ...paid ? {
8405
+ joinPrice: adv.joinPrice.trim(),
8406
+ joinAddr: walletAddress
8407
+ } : {},
7141
8408
  ...adv.isPublic ? { public: true } : {},
7142
8409
  ...tags.length === 0 ? {} : { tags }
7143
8410
  });
@@ -7301,31 +8568,92 @@ window.__ModuleLoader__.load({
7301
8568
  }),
7302
8569
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7303
8570
  className: "sm-field",
7304
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.join") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7305
- className: "sm-select",
7306
- value: adv.join,
7307
- onChange: (e) => {
7308
- setAdv({
7309
- ...adv,
7310
- join: e.target.value
7311
- });
7312
- },
7313
- children: [
7314
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7315
- value: "invite",
7316
- children: t("group.form.join.invite")
7317
- }),
7318
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7319
- value: "apply",
7320
- children: t("group.form.join.apply")
7321
- }),
7322
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7323
- value: "open",
7324
- children: t("group.form.join.open")
7325
- })
7326
- ]
7327
- })]
8571
+ children: [
8572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.join") }),
8573
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
8574
+ className: "sm-select",
8575
+ value: adv.join,
8576
+ onChange: (e) => {
8577
+ setAdv({
8578
+ ...adv,
8579
+ join: e.target.value,
8580
+ ...e.target.value === "paid" ? { isPublic: true } : {}
8581
+ });
8582
+ },
8583
+ children: [
8584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8585
+ value: "invite",
8586
+ children: t("group.form.join.invite")
8587
+ }),
8588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8589
+ value: "apply",
8590
+ children: t("group.form.join.apply")
8591
+ }),
8592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8593
+ value: "open",
8594
+ children: t("group.form.join.open")
8595
+ }),
8596
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
8597
+ value: "paid",
8598
+ disabled: walletAddress === void 0,
8599
+ children: [t("group.form.join.paid"), walletAddress === void 0 ? `(${t("group.form.join.paid.noWallet")})` : ""]
8600
+ })
8601
+ ]
8602
+ }),
8603
+ adv.join === "paid" && !adv.isPublic ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8604
+ style: {
8605
+ fontSize: "0.82em",
8606
+ margin: 0
8607
+ },
8608
+ children: t("group.form.join.paid.autoPublic")
8609
+ }) : null
8610
+ ]
7328
8611
  }),
8612
+ adv.join === "paid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8613
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
8614
+ className: "sm-field",
8615
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.joinPrice") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8616
+ className: "sm-input",
8617
+ type: "text",
8618
+ placeholder: "1.00",
8619
+ value: adv.joinPrice,
8620
+ disabled: walletAddress === void 0,
8621
+ onChange: (e) => {
8622
+ setAdv({
8623
+ ...adv,
8624
+ joinPrice: e.target.value
8625
+ });
8626
+ },
8627
+ "data-soulmirror-group-create-join-price": true
8628
+ })]
8629
+ }),
8630
+ paidIncomplete && adv.joinPrice.trim() === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8631
+ style: {
8632
+ fontSize: "0.82em",
8633
+ margin: 0,
8634
+ color: "rgb(220,80,60)"
8635
+ },
8636
+ "data-soulmirror-group-create-paid-missing": true,
8637
+ children: t("group.form.join.paid.priceRequired")
8638
+ }) : null,
8639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8640
+ style: {
8641
+ fontSize: "0.82em",
8642
+ opacity: .7,
8643
+ margin: 0
8644
+ },
8645
+ children: walletAddress === void 0 ? t("group.form.join.paid.noWalletHint") : t("group.form.joinAddr", { address: walletAddress })
8646
+ }),
8647
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8648
+ style: {
8649
+ fontSize: "0.82em",
8650
+ opacity: .7,
8651
+ margin: 0
8652
+ },
8653
+ "data-soulmirror-group-create-paid-hint": true,
8654
+ children: t("group.form.join.paid.membersFree")
8655
+ })
8656
+ ] }) : null,
7329
8657
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7330
8658
  className: "sm-field",
7331
8659
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.wake") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
@@ -7460,6 +8788,10 @@ window.__ModuleLoader__.load({
7460
8788
  * (card URI + note) at the bottom. The selected row is highlighted; clicking
7461
8789
  * a friend row selects it (the friend pane marks it read).
7462
8790
  */
8791
+ /** How many seat agents are shown before the rest collapses behind an expander. */
8792
+ const AGENT_COLLAPSE = 3;
8793
+ /** How many friends / groups the address book shows before the rest collapses. */
8794
+ const CONTACT_COLLAPSE = 10;
7463
8795
  function useCopy() {
7464
8796
  const [copied, setCopied] = (0, react.useState)(false);
7465
8797
  return [copied, (0, react.useCallback)((text) => {
@@ -7599,7 +8931,77 @@ window.__ModuleLoader__.load({
7599
8931
  })]
7600
8932
  }) });
7601
8933
  }
7602
- function FriendList({ t, selected, onSelect, onAccepted, onClose }) {
8934
+ /** One row of the address book (通讯录): avatar + name + presence, alphabetical, no message preview. */
8935
+ function ContactRow({ f, selected, t, onSelect }) {
8936
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8937
+ type: "button",
8938
+ className: `sm-row${selected ? " sm-selected" : ""}`,
8939
+ onClick: onSelect,
8940
+ "aria-pressed": selected,
8941
+ "data-soulmirror-page-contact": f.fp,
8942
+ "data-soulmirror-selected": selected ? "true" : void 0,
8943
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8944
+ className: "sm-avawrap",
8945
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8946
+ className: "sm-avatar",
8947
+ "aria-hidden": true,
8948
+ children: f.name.slice(0, 1)
8949
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8950
+ className: `sm-presence${f.online === true ? " sm-online" : ""}`,
8951
+ title: f.online === true ? t("inbox.online") : t("inbox.offline")
8952
+ })]
8953
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8954
+ className: "sm-row-body",
8955
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8956
+ className: "sm-row-title",
8957
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8958
+ className: "sm-row-name",
8959
+ children: f.name
8960
+ }), f.tier !== void 0 && f.tier !== "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TierPill, {
8961
+ tier: f.tier,
8962
+ t
8963
+ }) : null]
8964
+ })
8965
+ })]
8966
+ }) });
8967
+ }
8968
+ /** One group row of the address book (通讯录): avatar with the group mark + name + member count. */
8969
+ function GroupContactRow({ g, selected, t, onSelect }) {
8970
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8971
+ type: "button",
8972
+ className: `sm-row${selected ? " sm-selected" : ""}`,
8973
+ onClick: onSelect,
8974
+ "aria-pressed": selected,
8975
+ "data-soulmirror-page-contact-group": g.gid,
8976
+ "data-soulmirror-selected": selected ? "true" : void 0,
8977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8978
+ className: "sm-avawrap",
8979
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8980
+ className: "sm-avatar",
8981
+ "aria-hidden": true,
8982
+ children: g.name.slice(0, 1)
8983
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8984
+ className: "sm-gmark",
8985
+ "aria-hidden": true,
8986
+ title: t("group.section"),
8987
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupMarkIcon, {})
8988
+ })]
8989
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8990
+ className: "sm-row-body",
8991
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8992
+ className: "sm-row-title",
8993
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8994
+ className: "sm-row-name",
8995
+ children: g.name
8996
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8997
+ className: "sm-row-time",
8998
+ children: t("group.members", { n: g.members })
8999
+ })]
9000
+ })
9001
+ })]
9002
+ }) });
9003
+ }
9004
+ function FriendList({ t, selected, onSelect, onSelectContact, onAccepted, onClose }) {
7603
9005
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
7604
9006
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
7605
9007
  const { inbox } = net;
@@ -7613,10 +9015,26 @@ window.__ModuleLoader__.load({
7613
9015
  const [note, setNote] = (0, react.useState)(void 0);
7614
9016
  const [protocolOpen, setProtocolOpen] = (0, react.useState)(false);
7615
9017
  const [createOpen, setCreateOpen] = (0, react.useState)(false);
9018
+ const [walletAddress, setWalletAddress] = (0, react.useState)(void 0);
9019
+ const loadWallet = (0, react.useCallback)(() => {
9020
+ api.payWallet().then((r) => {
9021
+ setWalletAddress(r.wallet?.address);
9022
+ }).catch(() => {});
9023
+ }, []);
9024
+ (0, react.useEffect)(() => {
9025
+ loadWallet();
9026
+ }, [loadWallet]);
7616
9027
  const [joinUri, setJoinUri] = (0, react.useState)("");
9028
+ /** The join dialog's group card lookup (paid groups require a payment proof). */
9029
+ const [joinCard, setJoinCard] = (0, react.useState)(void 0);
9030
+ const [joinTx, setJoinTx] = (0, react.useState)("");
9031
+ const [payConfirming, setPayConfirming] = (0, react.useState)(false);
7617
9032
  /** The WeChat-style "+" menu and the small dialog it opens. */
7618
9033
  const [plusOpen, setPlusOpen] = (0, react.useState)(false);
7619
9034
  const [agentSheetOpen, setAgentSheetOpen] = (0, react.useState)(false);
9035
+ const [agentsExpanded, setAgentsExpanded] = (0, react.useState)(false);
9036
+ const [friendsExpanded, setFriendsExpanded] = (0, react.useState)(false);
9037
+ const [groupsExpanded, setGroupsExpanded] = (0, react.useState)(false);
7620
9038
  const [dialog, setDialog] = (0, react.useState)(void 0);
7621
9039
  (0, react.useEffect)(() => {
7622
9040
  if (query.trim() === "") return;
@@ -7657,8 +9075,15 @@ window.__ModuleLoader__.load({
7657
9075
  const nb = b.kind === "friend" ? b.f.name : b.g.name;
7658
9076
  return na.localeCompare(nb);
7659
9077
  });
7660
- const friendRows = rows.filter((r) => r.kind === "friend");
7661
- const groupRows = rows.filter((r) => r.kind === "group");
9078
+ const contactRows = filterFriends(inbox.friends, query).sort((a, b) => a.name.localeCompare(b.name));
9079
+ const groupContactRows = inbox.groups.filter((g) => q === "" || g.name.toLowerCase().includes(q)).sort((a, b) => a.name.localeCompare(b.name));
9080
+ const agents = net.state?.agents ?? [];
9081
+ const agentsExpandedNow = agentsExpanded || agents.length <= AGENT_COLLAPSE;
9082
+ const visibleAgents = agentsExpandedNow ? agents : agents.slice(0, AGENT_COLLAPSE);
9083
+ const friendsExpandedNow = friendsExpanded || contactRows.length <= CONTACT_COLLAPSE;
9084
+ const visibleContactRows = friendsExpandedNow ? contactRows : contactRows.slice(0, CONTACT_COLLAPSE);
9085
+ const groupsExpandedNow = groupsExpanded || groupContactRows.length <= CONTACT_COLLAPSE;
9086
+ const visibleGroupContactRows = groupsExpandedNow ? groupContactRows : groupContactRows.slice(0, CONTACT_COLLAPSE);
7662
9087
  const nameHits = q === "" ? [] : rows.filter((r) => r.kind === "friend" ? r.f.name.toLowerCase().includes(q) || (r.f.remark ?? "").toLowerCase().includes(q) || r.f.fp.toLowerCase().startsWith(q) : r.g.name.toLowerCase().includes(q));
7663
9088
  const contentHits = q === "" ? [] : (() => {
7664
9089
  const out = [];
@@ -7725,33 +9150,74 @@ window.__ModuleLoader__.load({
7725
9150
  const submitJoin = () => {
7726
9151
  if (busy !== void 0 || joinUri.trim() === "" || identity === null) return;
7727
9152
  run("group.apply", async () => {
9153
+ let card = null;
9154
+ try {
9155
+ card = (await api.groupLookup(joinUri.trim())).card;
9156
+ } catch {
9157
+ card = null;
9158
+ }
9159
+ const paid = card !== null ? card.join === "paid" ? {
9160
+ price: card.joinPrice,
9161
+ addr: card.joinAddr
9162
+ } : paidJoinFromRules(card.rulesHead) : void 0;
9163
+ setJoinCard(card === null ? void 0 : {
9164
+ name: card.name,
9165
+ join: card.join,
9166
+ ...paid === void 0 ? {} : {
9167
+ price: paid.price,
9168
+ addr: paid.addr
9169
+ }
9170
+ });
9171
+ if (paid !== void 0) {
9172
+ if (paid.price === void 0 || paid.addr === void 0) throw new Error(t("group.join.paid.noPrice"));
9173
+ if (walletAddress !== void 0) {
9174
+ if (!payConfirming) {
9175
+ setPayConfirming(true);
9176
+ return;
9177
+ }
9178
+ setPayConfirming(false);
9179
+ const { gid, tx_hash, amount } = await api.groupPaidJoin(joinUri.trim());
9180
+ setJoinUri("");
9181
+ setJoinTx("");
9182
+ setJoinCard(void 0);
9183
+ setDialog(void 0);
9184
+ if (gid !== "") onSelect(groupKey(gid));
9185
+ return t("group.join.paid.paidViaLocal", {
9186
+ amount: amount ?? paid.price,
9187
+ tx: tx_hash ?? ""
9188
+ });
9189
+ }
9190
+ if (joinTx.trim() === "") throw new Error(t("group.join.paid.needProof", {
9191
+ price: paid.price,
9192
+ addr: paid.addr
9193
+ }));
9194
+ const { gid } = await api.groupApply(joinUri.trim(), t("group.join.paid.note", { price: paid.price }), {
9195
+ tx_hash: joinTx.trim(),
9196
+ amount: paid.price,
9197
+ to: paid.addr
9198
+ });
9199
+ setJoinUri("");
9200
+ setJoinTx("");
9201
+ setJoinCard(void 0);
9202
+ setDialog(void 0);
9203
+ if (gid !== "") onSelect(groupKey(gid));
9204
+ return t("group.join.paid.applied");
9205
+ }
7728
9206
  const { gid } = await api.groupApply(joinUri.trim());
7729
9207
  setJoinUri("");
9208
+ setJoinCard(void 0);
7730
9209
  setDialog(void 0);
7731
9210
  if (gid !== "") {
7732
- pageStore.setCol2Tab("groups");
9211
+ pageStore.setCol2Tab("messages");
7733
9212
  onSelect(groupKey(gid));
7734
9213
  }
7735
9214
  return t("group.join.applied");
7736
9215
  });
7737
9216
  };
7738
- const upgrade = (0, react.useSyncExternalStore)(upgradeStore.subscribe, upgradeStore.getSnapshot);
7739
- const upgradeBusy = upgrade.phase === "installing" || upgrade.phase === "restarting" || upgrade.phase === "reloading";
7740
9217
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
7741
9218
  className: "sm-list-col",
7742
9219
  "data-soulmirror-page-list": true,
7743
9220
  children: [
7744
- upgrade.hasUpdate || upgradeBusy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7745
- type: "button",
7746
- className: "sm-update-banner",
7747
- disabled: upgradeBusy,
7748
- "data-soulmirror-update-banner": upgrade.latest,
7749
- onClick: () => {
7750
- if (upgradeBusy || upgrade.latest === void 0) return;
7751
- if (window.confirm(t("page.update.confirm", { v: upgrade.latest }))) upgradeStore.run();
7752
- },
7753
- children: upgradeBusy ? t("page.update.busy") : t("page.update.banner", { v: upgrade.latest ?? "" })
7754
- }) : null,
7755
9221
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7756
9222
  className: "sm-list-head",
7757
9223
  children: [
@@ -8038,7 +9504,7 @@ window.__ModuleLoader__.load({
8038
9504
  role: "menuitem",
8039
9505
  onClick: () => {
8040
9506
  setPlusOpen(false);
8041
- pageStore.setCol2Tab("groups");
9507
+ pageStore.setCol2Tab("messages");
8042
9508
  setCreateOpen(true);
8043
9509
  },
8044
9510
  "data-soulmirror-plus-new-group": true,
@@ -8060,6 +9526,7 @@ window.__ModuleLoader__.load({
8060
9526
  onClick: () => {
8061
9527
  setPlusOpen(false);
8062
9528
  setDialog("join");
9529
+ loadWallet();
8063
9530
  },
8064
9531
  "data-soulmirror-plus-join-group": true,
8065
9532
  children: t("foot.joinGroup")
@@ -8072,11 +9539,7 @@ window.__ModuleLoader__.load({
8072
9539
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8073
9540
  className: "sm-col2-tabs",
8074
9541
  "data-soulmirror-col2-tabs": true,
8075
- children: [
8076
- "contacts",
8077
- "agents",
8078
- "groups"
8079
- ].map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9542
+ children: ["messages", "contacts"].map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8080
9543
  type: "button",
8081
9544
  className: `sm-col2-tab${col2Tab === tab ? " sm-active" : ""}`,
8082
9545
  onClick: () => {
@@ -8091,7 +9554,7 @@ window.__ModuleLoader__.load({
8091
9554
  className: "sm-list-body",
8092
9555
  children: [
8093
9556
  protocolOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProtocolEditor, { t }) : null,
8094
- col2Tab === "contacts" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9557
+ col2Tab === "messages" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8095
9558
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8096
9559
  className: "sm-section",
8097
9560
  children: t("col2.contacts.section")
@@ -8144,6 +9607,134 @@ window.__ModuleLoader__.load({
8144
9607
  })]
8145
9608
  }) })
8146
9609
  }),
9610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9611
+ className: "sm-section",
9612
+ style: {
9613
+ display: "flex",
9614
+ alignItems: "center",
9615
+ gap: 8
9616
+ },
9617
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9618
+ style: { flex: 1 },
9619
+ children: t("agents.section")
9620
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9621
+ type: "button",
9622
+ className: "sm-linkbtn",
9623
+ onClick: () => {
9624
+ setAgentSheetOpen(true);
9625
+ },
9626
+ "data-soulmirror-agents-add": true,
9627
+ children: ["+ ", t("settings.agents.add")]
9628
+ })]
9629
+ }),
9630
+ agents.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9631
+ className: "sm-muted",
9632
+ style: {
9633
+ margin: 0,
9634
+ padding: "4px 10px 10px",
9635
+ fontSize: 12
9636
+ },
9637
+ "data-soulmirror-page-empty-list": true,
9638
+ children: t("col2.agents.empty")
9639
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9640
+ style: {
9641
+ listStyle: "none",
9642
+ margin: 0,
9643
+ padding: 0
9644
+ },
9645
+ children: visibleAgents.map((a) => {
9646
+ const key = agentKey(a.name);
9647
+ const agentSelected = selected === key;
9648
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9649
+ type: "button",
9650
+ className: `sm-row${agentSelected ? " sm-selected" : ""}`,
9651
+ onClick: () => {
9652
+ onSelect(key);
9653
+ },
9654
+ "aria-pressed": agentSelected,
9655
+ "data-soulmirror-page-agent": a.name,
9656
+ "data-soulmirror-selected": agentSelected ? "true" : void 0,
9657
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9658
+ className: "sm-avawrap",
9659
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9660
+ className: "sm-avatar",
9661
+ "aria-hidden": true,
9662
+ children: "🤖"
9663
+ })
9664
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9665
+ className: "sm-row-body",
9666
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9667
+ className: "sm-row-title",
9668
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9669
+ className: "sm-row-name",
9670
+ children: [
9671
+ a.name,
9672
+ " ",
9673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9674
+ className: `sm-livedot${a.status === "running" ? " sm-busy" : ""}`,
9675
+ "aria-hidden": true
9676
+ })
9677
+ ]
9678
+ })
9679
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9680
+ className: "sm-row-preview",
9681
+ children: a.status === "running" ? t("settings.agents.status.running") : a.cwd ?? t("settings.agents.status.idle")
9682
+ })]
9683
+ })]
9684
+ }) }, a.name);
9685
+ })
9686
+ }), agents.length > AGENT_COLLAPSE ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9687
+ type: "button",
9688
+ className: "sm-linkbtn",
9689
+ style: { margin: "2px 10px 6px" },
9690
+ onClick: () => {
9691
+ setAgentsExpanded((v) => !v);
9692
+ },
9693
+ "data-soulmirror-agents-expand": true,
9694
+ children: agentsExpandedNow ? t("agents.collapse") : t("agents.expand", { n: agents.length - AGENT_COLLAPSE })
9695
+ }) : null] }),
9696
+ agentSheetOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentSettingsSheet, {
9697
+ t,
9698
+ onClose: () => {
9699
+ setAgentSheetOpen(false);
9700
+ },
9701
+ onSaved: (name) => {
9702
+ onSelect(agentKey(name));
9703
+ }
9704
+ }) : null,
9705
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9706
+ className: "sm-section",
9707
+ children: t("chats.section")
9708
+ }),
9709
+ rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9710
+ className: "sm-muted",
9711
+ style: {
9712
+ margin: 0,
9713
+ padding: "4px 10px 10px",
9714
+ fontSize: 12
9715
+ },
9716
+ "data-soulmirror-page-empty-list": true,
9717
+ children: identity === null ? t("page.empty.noFriends.noIdentity") : t("chats.empty")
9718
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9719
+ style: {
9720
+ listStyle: "none",
9721
+ margin: 0,
9722
+ padding: 0
9723
+ },
9724
+ "data-soulmirror-page-chats": true,
9725
+ children: rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatRow, {
9726
+ row: r,
9727
+ typing: r.kind === "friend" && (inbox.typing[r.f.fp] === true || r.f.typing === true),
9728
+ apps: r.kind === "group" ? inbox.groupApps[r.g.gid] ?? 0 : 0,
9729
+ selected: selected === r.rowKey,
9730
+ t,
9731
+ onSelect: () => {
9732
+ onSelect(r.rowKey);
9733
+ }
9734
+ }, r.rowKey))
9735
+ })
9736
+ ] }),
9737
+ col2Tab === "contacts" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8147
9738
  inbox.pending.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8148
9739
  className: "sm-section",
8149
9740
  children: [
@@ -8223,9 +9814,9 @@ window.__ModuleLoader__.load({
8223
9814
  })] }) : null,
8224
9815
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8225
9816
  className: "sm-section",
8226
- children: t("chats.section")
9817
+ children: t("inbox.friends")
8227
9818
  }),
8228
- friendRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9819
+ contactRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8229
9820
  className: "sm-muted",
8230
9821
  style: {
8231
9822
  margin: 0,
@@ -8233,49 +9824,37 @@ window.__ModuleLoader__.load({
8233
9824
  fontSize: 12
8234
9825
  },
8235
9826
  "data-soulmirror-page-empty-list": true,
8236
- children: identity === null ? t("page.empty.noFriends.noIdentity") : q !== "" ? t("page.empty.noMatch", { query }) : t("chats.empty")
8237
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9827
+ children: identity === null ? t("page.empty.noFriends.noIdentity") : q !== "" ? t("page.empty.noMatch", { query }) : t("inbox.empty")
9828
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8238
9829
  style: {
8239
9830
  listStyle: "none",
8240
9831
  margin: 0,
8241
9832
  padding: 0
8242
9833
  },
8243
- "data-soulmirror-page-friends": true,
8244
- "data-soulmirror-page-chats": true,
8245
- children: friendRows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatRow, {
8246
- row: r,
8247
- typing: inbox.typing[r.f.fp] === true || r.f.typing === true,
8248
- apps: 0,
8249
- selected: selected === r.rowKey,
9834
+ "data-soulmirror-page-contacts": true,
9835
+ children: visibleContactRows.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContactRow, {
9836
+ f,
9837
+ selected: selected === f.fp,
8250
9838
  t,
8251
9839
  onSelect: () => {
8252
- onSelect(r.rowKey);
9840
+ onSelectContact(f.fp);
8253
9841
  }
8254
- }, r.rowKey))
8255
- })
8256
- ] }),
8257
- col2Tab === "agents" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8258
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8259
- className: "sm-section",
8260
- style: {
8261
- display: "flex",
8262
- alignItems: "center",
8263
- gap: 8
9842
+ }, f.fp))
9843
+ }), contactRows.length > CONTACT_COLLAPSE ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9844
+ type: "button",
9845
+ className: "sm-linkbtn",
9846
+ style: { margin: "2px 10px 6px" },
9847
+ onClick: () => {
9848
+ setFriendsExpanded((v) => !v);
8264
9849
  },
8265
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8266
- style: { flex: 1 },
8267
- children: t("agents.section")
8268
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8269
- type: "button",
8270
- className: "sm-linkbtn",
8271
- onClick: () => {
8272
- setAgentSheetOpen(true);
8273
- },
8274
- "data-soulmirror-agents-add": true,
8275
- children: ["+ ", t("settings.agents.add")]
8276
- })]
9850
+ "data-soulmirror-contacts-friends-expand": true,
9851
+ children: friendsExpandedNow ? t("agents.collapse") : t("agents.expand", { n: contactRows.length - CONTACT_COLLAPSE })
9852
+ }) : null] }),
9853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9854
+ className: "sm-section",
9855
+ children: t("groups.section")
8277
9856
  }),
8278
- (net.state?.agents?.length ?? 0) === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9857
+ groupContactRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8279
9858
  className: "sm-muted",
8280
9859
  style: {
8281
9860
  margin: 0,
@@ -8283,95 +9862,33 @@ window.__ModuleLoader__.load({
8283
9862
  fontSize: 12
8284
9863
  },
8285
9864
  "data-soulmirror-page-empty-list": true,
8286
- children: t("col2.agents.empty")
8287
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9865
+ children: q !== "" ? t("page.empty.noMatch", { query }) : t("col2.groups.empty")
9866
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8288
9867
  style: {
8289
9868
  listStyle: "none",
8290
9869
  margin: 0,
8291
9870
  padding: 0
8292
9871
  },
8293
- children: (net.state?.agents ?? []).map((a) => {
8294
- const key = agentKey(a.name);
8295
- const agentSelected = selected === key;
8296
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8297
- type: "button",
8298
- className: `sm-row${agentSelected ? " sm-selected" : ""}`,
8299
- onClick: () => {
8300
- onSelect(key);
8301
- },
8302
- "aria-pressed": agentSelected,
8303
- "data-soulmirror-page-agent": a.name,
8304
- "data-soulmirror-selected": agentSelected ? "true" : void 0,
8305
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8306
- className: "sm-avawrap",
8307
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8308
- className: "sm-avatar",
8309
- "aria-hidden": true,
8310
- children: "🤖"
8311
- })
8312
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8313
- className: "sm-row-body",
8314
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8315
- className: "sm-row-title",
8316
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8317
- className: "sm-row-name",
8318
- children: [
8319
- a.name,
8320
- " ",
8321
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8322
- className: `sm-livedot${a.status === "running" ? " sm-busy" : ""}`,
8323
- "aria-hidden": true
8324
- })
8325
- ]
8326
- })
8327
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8328
- className: "sm-row-preview",
8329
- children: a.status === "running" ? t("settings.agents.status.running") : a.cwd ?? t("settings.agents.status.idle")
8330
- })]
8331
- })]
8332
- }) }, a.name);
8333
- })
8334
- }),
8335
- agentSheetOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentSettingsSheet, {
8336
- t,
8337
- onClose: () => {
8338
- setAgentSheetOpen(false);
9872
+ "data-soulmirror-page-contact-groups": true,
9873
+ children: visibleGroupContactRows.map((g) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupContactRow, {
9874
+ g,
9875
+ selected: selected === groupKey(g.gid),
9876
+ t,
9877
+ onSelect: () => {
9878
+ onSelect(groupKey(g.gid));
9879
+ }
9880
+ }, g.gid))
9881
+ }), groupContactRows.length > CONTACT_COLLAPSE ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9882
+ type: "button",
9883
+ className: "sm-linkbtn",
9884
+ style: { margin: "2px 10px 6px" },
9885
+ onClick: () => {
9886
+ setGroupsExpanded((v) => !v);
8339
9887
  },
8340
- onSaved: (name) => {
8341
- onSelect(agentKey(name));
8342
- }
8343
- }) : null
8344
- ] }),
8345
- col2Tab === "groups" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8346
- className: "sm-section",
8347
- children: t("groups.section")
8348
- }), groupRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8349
- className: "sm-muted",
8350
- style: {
8351
- margin: 0,
8352
- padding: "4px 10px 10px",
8353
- fontSize: 12
8354
- },
8355
- "data-soulmirror-page-empty-list": true,
8356
- children: q !== "" ? t("page.empty.noMatch", { query }) : t("col2.groups.empty")
8357
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8358
- style: {
8359
- listStyle: "none",
8360
- margin: 0,
8361
- padding: 0
8362
- },
8363
- "data-soulmirror-page-groups": true,
8364
- children: groupRows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatRow, {
8365
- row: r,
8366
- typing: false,
8367
- apps: inbox.groupApps[r.g.gid] ?? 0,
8368
- selected: selected === r.rowKey,
8369
- t,
8370
- onSelect: () => {
8371
- onSelect(r.rowKey);
8372
- }
8373
- }, r.rowKey))
8374
- })] })
9888
+ "data-soulmirror-contacts-groups-expand": true,
9889
+ children: groupsExpandedNow ? t("agents.collapse") : t("agents.expand", { n: groupContactRows.length - CONTACT_COLLAPSE })
9890
+ }) : null] })
9891
+ ] })
8375
9892
  ]
8376
9893
  }),
8377
9894
  note !== void 0 || net.error !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -8395,6 +9912,7 @@ window.__ModuleLoader__.load({
8395
9912
  t,
8396
9913
  friends: inbox.friends,
8397
9914
  busy: busy !== void 0,
9915
+ ...walletAddress === void 0 ? {} : { walletAddress },
8398
9916
  onCreate: createGroup,
8399
9917
  onClose: () => {
8400
9918
  setCreateOpen(false);
@@ -8515,6 +10033,8 @@ window.__ModuleLoader__.load({
8515
10033
  autoFocus: true,
8516
10034
  onChange: (e) => {
8517
10035
  setJoinUri(e.target.value);
10036
+ setJoinCard(void 0);
10037
+ setJoinTx("");
8518
10038
  },
8519
10039
  onKeyDown: (e) => {
8520
10040
  if (e.key === "Enter" && !e.nativeEvent.isComposing) submitJoin();
@@ -8522,22 +10042,122 @@ window.__ModuleLoader__.load({
8522
10042
  "data-soulmirror-group-join-uri": true
8523
10043
  })]
8524
10044
  }),
10045
+ joinCard !== void 0 && joinCard.price !== void 0 && joinCard.addr !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10046
+ style: {
10047
+ display: "grid",
10048
+ gap: 8
10049
+ },
10050
+ "data-soulmirror-group-join-paid": true,
10051
+ children: [
10052
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10053
+ style: {
10054
+ border: "1px solid rgba(127,127,127,.3)",
10055
+ borderRadius: 8,
10056
+ padding: "8px 10px",
10057
+ display: "grid",
10058
+ gap: 4,
10059
+ background: "rgba(127,127,127,.06)"
10060
+ },
10061
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10062
+ style: {
10063
+ fontSize: "0.75em",
10064
+ opacity: .7
10065
+ },
10066
+ children: t("group.join.paid.amountLabel")
10067
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10068
+ style: {
10069
+ fontSize: "1.25em",
10070
+ fontWeight: 700,
10071
+ color: "var(--dsw-alias-state-warn-primary, rgb(220,160,60))"
10072
+ },
10073
+ "data-soulmirror-group-join-price": true,
10074
+ children: [joinCard.price, " USDC"]
10075
+ })]
10076
+ }),
10077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10078
+ style: {
10079
+ display: "grid",
10080
+ gap: 2
10081
+ },
10082
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10083
+ style: {
10084
+ fontSize: "0.75em",
10085
+ opacity: .7
10086
+ },
10087
+ children: t("group.join.paid.toLabel")
10088
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10089
+ style: {
10090
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
10091
+ fontSize: "0.82em",
10092
+ wordBreak: "break-all"
10093
+ },
10094
+ "data-soulmirror-group-join-addr": true,
10095
+ children: joinCard.addr
10096
+ })]
10097
+ }),
10098
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10099
+ style: {
10100
+ fontSize: "0.8em",
10101
+ margin: 0,
10102
+ opacity: .8
10103
+ },
10104
+ children: t("group.join.paid.payHint", { price: joinCard.price })
10105
+ }),
10106
+ walletAddress !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10107
+ style: {
10108
+ display: "grid",
10109
+ gap: 4
10110
+ },
10111
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10112
+ style: {
10113
+ fontSize: "0.8em",
10114
+ margin: 0,
10115
+ opacity: .8
10116
+ },
10117
+ "data-soulmirror-group-join-localpay": true,
10118
+ children: t("group.join.paid.localHint")
10119
+ }), payConfirming ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10120
+ style: {
10121
+ fontSize: "0.85em",
10122
+ margin: 0,
10123
+ color: "var(--dsw-alias-state-warn-primary, rgb(220,160,60))",
10124
+ fontWeight: 600
10125
+ },
10126
+ "data-soulmirror-group-join-confirm": true,
10127
+ children: t("group.join.paid.confirm", { price: joinCard.price })
10128
+ }) : null]
10129
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
10130
+ className: "sm-field",
10131
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.join.paid.tx") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
10132
+ className: "sm-input",
10133
+ placeholder: "0x…",
10134
+ value: joinTx,
10135
+ onChange: (e) => {
10136
+ setJoinTx(e.target.value);
10137
+ },
10138
+ "data-soulmirror-group-join-tx": true
10139
+ })]
10140
+ })
10141
+ ]
10142
+ }) : null,
8525
10143
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8526
10144
  className: "sm-modal-foot",
8527
10145
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
8528
10146
  variant: "outline",
8529
10147
  size: "sm",
8530
10148
  onClick: () => {
8531
- setDialog(void 0);
10149
+ if (payConfirming) setPayConfirming(false);
10150
+ else setDialog(void 0);
8532
10151
  },
8533
- children: t("inbox.close")
10152
+ "data-soulmirror-group-join-back": true,
10153
+ children: payConfirming ? t("group.home.edit.cancel") : t("inbox.close")
8534
10154
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
8535
- variant: "outline",
10155
+ variant: "primary",
8536
10156
  size: "sm",
8537
- disabled: busy !== void 0 || joinUri.trim() === "",
10157
+ disabled: busy !== void 0 || joinUri.trim() === "" || joinCard?.price !== void 0 && walletAddress === void 0 && joinTx.trim() === "",
8538
10158
  onClick: submitJoin,
8539
10159
  "data-soulmirror-group-join-apply": true,
8540
- children: t("group.join.apply")
10160
+ children: joinCard?.price !== void 0 && walletAddress !== void 0 ? payConfirming ? t("group.join.paid.confirmPay") : t("group.join.paid.payApply") : t("group.join.apply")
8541
10161
  })]
8542
10162
  })
8543
10163
  ]
@@ -8908,7 +10528,7 @@ window.__ModuleLoader__.load({
8908
10528
  variant: "primary",
8909
10529
  size: "sm",
8910
10530
  onClick: onGoAlter,
8911
- children: t("friend.actbar.goAlter")
10531
+ children: t("friend.home.sendMessage")
8912
10532
  })
8913
10533
  ]
8914
10534
  })]
@@ -8919,6 +10539,7 @@ window.__ModuleLoader__.load({
8919
10539
  tier: friend.tier,
8920
10540
  tierExplicit: friend.tierExplicit === true,
8921
10541
  protocol: friend.protocol,
10542
+ muted: friend.muted === true,
8922
10543
  defaultTier: alterConfig?.defaultTier ?? "draft",
8923
10544
  perHour: alterConfig?.autoReplyPerHour ?? 20,
8924
10545
  t,
@@ -8993,7 +10614,17 @@ window.__ModuleLoader__.load({
8993
10614
  onChange: pageStore.setPaneTab,
8994
10615
  t
8995
10616
  }),
8996
- paneTab === "home" ? friendHome : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
10617
+ paneTab === "home" ? friendHome : paneTab === "memory" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryPane, {
10618
+ t,
10619
+ allow: {
10620
+ global: true,
10621
+ friend: fp
10622
+ },
10623
+ scope: {
10624
+ kind: "shared-friend",
10625
+ fp
10626
+ }
10627
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8997
10628
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8998
10629
  className: "sm-banner",
8999
10630
  role: "note",
@@ -9168,6 +10799,7 @@ window.__ModuleLoader__.load({
9168
10799
  tier: friend.tier,
9169
10800
  tierExplicit: friend.tierExplicit === true,
9170
10801
  protocol: friend.protocol,
10802
+ muted: friend.muted === true,
9171
10803
  defaultTier: alterConfig?.defaultTier ?? "draft",
9172
10804
  perHour: alterConfig?.autoReplyPerHour ?? 20,
9173
10805
  t,
@@ -9330,6 +10962,8 @@ window.__ModuleLoader__.load({
9330
10962
  */
9331
10963
  /** Roster info survives pane switches, so revisiting a group paints names instantly. */
9332
10964
  const infoCache = /* @__PURE__ */ new Map();
10965
+ /** 很久没来群(未读 >= 此阈值)进群时,触发一次群记忆提炼(避免每条消息都总结)。 */
10966
+ const GROUP_MEMORY_MIN_UNREAD = 20;
9333
10967
  const templateLabel = (t, id) => {
9334
10968
  switch (id) {
9335
10969
  case "standard": return t("template.standard");
@@ -9340,7 +10974,7 @@ window.__ModuleLoader__.load({
9340
10974
  default: return id ?? "";
9341
10975
  }
9342
10976
  };
9343
- const joinLabel = (t, join) => join === "apply" ? t("group.form.join.apply") : join === "open" ? t("group.form.join.open") : t("group.form.join.invite");
10977
+ const joinLabel = (t, join, paid) => paid === true || join === "paid" ? t("group.form.join.paid") : join === "apply" ? t("group.form.join.apply") : join === "open" ? t("group.form.join.open") : t("group.form.join.invite");
9344
10978
  const wakeLabel = (t, wake) => wake === "always" ? t("group.form.wake.always") : wake === "never" ? t("group.form.wake.never") : t("group.form.wake.mention");
9345
10979
  const tierLabel = (t, tier) => tier === "notify" ? t("tier.short.notify") : tier === "auto" ? t("tier.short.auto") : t("tier.short.draft");
9346
10980
  const whoLabel = (t, who) => who === "owner" ? t("group.form.speakWho.owner") : who === "admins" ? t("group.form.speakWho.admins") : t("group.form.speakWho.all");
@@ -9358,6 +10992,12 @@ window.__ModuleLoader__.load({
9358
10992
  const [inviteFp, setInviteFp] = (0, react.useState)("");
9359
10993
  const [copied, setCopied] = (0, react.useState)(false);
9360
10994
  const [editOpen, setEditOpen] = (0, react.useState)(false);
10995
+ const [walletAddress, setWalletAddress] = (0, react.useState)(void 0);
10996
+ (0, react.useEffect)(() => {
10997
+ api.payWallet().then((r) => {
10998
+ setWalletAddress(r.wallet?.address);
10999
+ }).catch(() => {});
11000
+ }, []);
9361
11001
  const [edit, setEdit] = (0, react.useState)({
9362
11002
  speakHumans: true,
9363
11003
  speakAgents: true,
@@ -9365,6 +11005,7 @@ window.__ModuleLoader__.load({
9365
11005
  join: "invite",
9366
11006
  agentWake: "mention",
9367
11007
  agentTier: "draft",
11008
+ joinPrice: "",
9368
11009
  rules: "",
9369
11010
  isPublic: false,
9370
11011
  tags: ""
@@ -9425,6 +11066,10 @@ window.__ModuleLoader__.load({
9425
11066
  visible,
9426
11067
  thread.entries.length
9427
11068
  ]);
11069
+ (0, react.useEffect)(() => {
11070
+ if (group.unread < GROUP_MEMORY_MIN_UNREAD) return;
11071
+ api.memorySummarize(gid).catch(() => {});
11072
+ }, [gid]);
9428
11073
  const run = async (key, action) => {
9429
11074
  setBusy(key);
9430
11075
  setError(void 0);
@@ -9487,7 +11132,8 @@ window.__ModuleLoader__.load({
9487
11132
  speakHumans: profile?.speakHumans ?? true,
9488
11133
  speakAgents: profile?.speakAgents ?? true,
9489
11134
  speakWho: profile?.speakWho ?? "all",
9490
- join: profile?.join ?? "invite",
11135
+ join: profile?.join === "paid" ? "paid" : paidJoinFromRules(profile?.rules) !== void 0 ? "paid" : profile?.join ?? "invite",
11136
+ joinPrice: profile?.joinPrice ?? paidJoinFromRules(profile?.rules)?.price ?? "",
9491
11137
  agentWake: profile?.agentWake ?? "mention",
9492
11138
  agentTier: profile?.agentTier ?? "draft",
9493
11139
  rules: profile?.rules ?? "",
@@ -9498,6 +11144,14 @@ window.__ModuleLoader__.load({
9498
11144
  };
9499
11145
  const saveProfile = () => {
9500
11146
  const tags = edit.tags.split(",").map((s) => s.trim()).filter((s) => s !== "");
11147
+ if (edit.join === "paid" && edit.joinPrice.trim() === "") {
11148
+ setError(t("group.form.join.paid.priceRequired"));
11149
+ return;
11150
+ }
11151
+ if (edit.join === "paid" && edit.joinPrice.trim() !== "" && walletAddress === void 0) {
11152
+ setError(t("group.form.join.paid.noWallet"));
11153
+ return;
11154
+ }
9501
11155
  run("profile.save", async () => {
9502
11156
  await api.groupSetProfile(gid, {
9503
11157
  ...profile ?? {},
@@ -9508,6 +11162,10 @@ window.__ModuleLoader__.load({
9508
11162
  agentWake: edit.agentWake,
9509
11163
  agentTier: edit.agentTier,
9510
11164
  rules: edit.rules,
11165
+ ...edit.join === "paid" && edit.joinPrice.trim() !== "" && walletAddress !== void 0 ? {
11166
+ joinPrice: edit.joinPrice.trim(),
11167
+ joinAddr: walletAddress
11168
+ } : {},
9511
11169
  public: edit.isPublic,
9512
11170
  tags
9513
11171
  });
@@ -9527,7 +11185,7 @@ window.__ModuleLoader__.load({
9527
11185
  };
9528
11186
  const chips = profile === void 0 ? [] : [
9529
11187
  templateLabel(t, profile.template),
9530
- joinLabel(t, profile.join),
11188
+ joinLabel(t, profile.join === "paid" ? "paid" : profile.join, profile.join === "paid" ? true : paidJoinFromRules(profile.rules) !== void 0),
9531
11189
  whoLabel(t, profile.speakWho),
9532
11190
  `${t("group.form.wake")} · ${wakeLabel(t, profile.agentWake)}`,
9533
11191
  `${t("group.form.tier")} · ${tierLabel(t, profile.agentTier)}`,
@@ -9601,7 +11259,7 @@ window.__ModuleLoader__.load({
9601
11259
  "data-soulmirror-group-profile-edit": true,
9602
11260
  children: t("group.home.edit")
9603
11261
  }) : null,
9604
- profile?.public === true && relay !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11262
+ relay !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9605
11263
  type: "button",
9606
11264
  className: "sm-ghostbtn",
9607
11265
  onClick: copyUri,
@@ -9678,31 +11336,71 @@ window.__ModuleLoader__.load({
9678
11336
  }),
9679
11337
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9680
11338
  className: "sm-field",
9681
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.join") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
9682
- className: "sm-select",
9683
- value: edit.join,
9684
- onChange: (e) => {
9685
- setEdit({
9686
- ...edit,
9687
- join: e.target.value
9688
- });
9689
- },
9690
- children: [
9691
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9692
- value: "invite",
9693
- children: t("group.form.join.invite")
9694
- }),
9695
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9696
- value: "apply",
9697
- children: t("group.form.join.apply")
9698
- }),
9699
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9700
- value: "open",
9701
- children: t("group.form.join.open")
9702
- })
9703
- ]
9704
- })]
11339
+ children: [
11340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.join") }),
11341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
11342
+ className: "sm-select",
11343
+ value: edit.join,
11344
+ onChange: (e) => {
11345
+ setEdit({
11346
+ ...edit,
11347
+ join: e.target.value,
11348
+ ...e.target.value === "paid" ? { isPublic: true } : {}
11349
+ });
11350
+ },
11351
+ children: [
11352
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11353
+ value: "invite",
11354
+ children: t("group.form.join.invite")
11355
+ }),
11356
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11357
+ value: "apply",
11358
+ children: t("group.form.join.apply")
11359
+ }),
11360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11361
+ value: "open",
11362
+ children: t("group.form.join.open")
11363
+ }),
11364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11365
+ value: "paid",
11366
+ disabled: walletAddress === void 0 && paidJoinFromRules(profile?.rules) === void 0,
11367
+ children: t("group.form.join.paid")
11368
+ })
11369
+ ]
11370
+ }),
11371
+ edit.join === "paid" && !edit.isPublic ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11372
+ style: { fontSize: "0.82em" },
11373
+ children: t("group.form.join.paid.autoPublic")
11374
+ }) : null
11375
+ ]
9705
11376
  }),
11377
+ edit.join === "paid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
11378
+ className: "sm-field",
11379
+ children: [
11380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.joinPrice") }),
11381
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11382
+ className: "sm-input",
11383
+ type: "text",
11384
+ placeholder: "1.00",
11385
+ value: edit.joinPrice,
11386
+ onChange: (e) => {
11387
+ setEdit({
11388
+ ...edit,
11389
+ joinPrice: e.target.value
11390
+ });
11391
+ },
11392
+ "data-soulmirror-group-edit-join-price": true
11393
+ }),
11394
+ edit.joinPrice.trim() === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11395
+ style: {
11396
+ fontSize: "0.82em",
11397
+ color: "rgb(220,80,60)"
11398
+ },
11399
+ "data-soulmirror-group-edit-paid-missing": true,
11400
+ children: t("group.form.join.paid.priceRequired")
11401
+ }) : null
11402
+ ]
11403
+ }) : null,
9706
11404
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9707
11405
  className: "sm-field",
9708
11406
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.wake") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
@@ -9836,10 +11534,10 @@ window.__ModuleLoader__.load({
9836
11534
  className: "sm-statepill",
9837
11535
  children: c
9838
11536
  }, `${c}-${i}`))
9839
- }), profile?.rules !== void 0 && profile.rules !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11537
+ }), profile?.rules !== void 0 && rulesWithoutPaidJoin(profile.rules) !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9840
11538
  className: "sm-home-rules",
9841
11539
  "data-soulmirror-group-rules": true,
9842
- children: profile.rules
11540
+ children: rulesWithoutPaidJoin(profile.rules)
9843
11541
  }) : null] })]
9844
11542
  }),
9845
11543
  pins.length > 0 || canAdmin ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -10564,7 +12262,7 @@ window.__ModuleLoader__.load({
10564
12262
  className: "sm-home-card",
10565
12263
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10566
12264
  className: "sm-home-title",
10567
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.home.profile") }), profile?.public === true && relay !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12265
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.home.profile") }), relay !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10568
12266
  type: "button",
10569
12267
  className: "sm-ghostbtn",
10570
12268
  onClick: copyUri,
@@ -10895,7 +12593,17 @@ window.__ModuleLoader__.load({
10895
12593
  ...roomOwner,
10896
12594
  t
10897
12595
  })] })
10898
- }) : paneTab === "announce" ? announce : paneTab === "members" ? membersPane : paneTab === "admin" ? admin : home
12596
+ }) : paneTab === "announce" ? announce : paneTab === "memory" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryPane, {
12597
+ t,
12598
+ allow: {
12599
+ global: true,
12600
+ group: gid
12601
+ },
12602
+ scope: {
12603
+ kind: "shared-group",
12604
+ gid
12605
+ }
12606
+ }) : paneTab === "members" ? membersPane : paneTab === "admin" ? admin : home
10899
12607
  ]
10900
12608
  });
10901
12609
  }
@@ -10940,7 +12648,7 @@ window.__ModuleLoader__.load({
10940
12648
  node = parent;
10941
12649
  }
10942
12650
  }
10943
- function SoulmirrorPage({ openSession, scope, t, renderSlot }) {
12651
+ function SoulmirrorPage({ scope, t, renderSlot }) {
10944
12652
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
10945
12653
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
10946
12654
  const subscribeScope = (0, react.useCallback)((listener) => scope.subscribe(listener), [scope]);
@@ -11001,6 +12709,10 @@ window.__ModuleLoader__.load({
11001
12709
  if (!target.isConnected) return;
11002
12710
  if (rootEl.contains(target)) return;
11003
12711
  if (target?.closest?.("[data-soulmirror-footer]") != null) return;
12712
+ if (target?.closest?.("[data-soulmirror-modal]") != null) return;
12713
+ if (target?.closest?.("[data-soulmirror-mention-pop]") != null) return;
12714
+ if (target?.closest?.("[data-soulmirror-float]") != null) return;
12715
+ if (target?.closest?.("[data-shell-overlay]") != null) return;
11004
12716
  pageStore.close();
11005
12717
  };
11006
12718
  document.addEventListener("mousedown", onDown);
@@ -11036,9 +12748,12 @@ window.__ModuleLoader__.load({
11036
12748
  const goFriend = (fp) => {
11037
12749
  pageStore.select(fp);
11038
12750
  };
11039
- const openAlterSession = (sessionId) => {
11040
- openSession(sessionId);
11041
- pageStore.close();
12751
+ const goGroup = (gid) => {
12752
+ pageStore.select(groupKey(gid));
12753
+ };
12754
+ const openContact = (fp) => {
12755
+ pageStore.select(fp);
12756
+ pageStore.setPaneTab("home");
11042
12757
  };
11043
12758
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11044
12759
  ref: root,
@@ -11054,6 +12769,7 @@ window.__ModuleLoader__.load({
11054
12769
  onSelect: (key) => {
11055
12770
  pageStore.select(key);
11056
12771
  },
12772
+ onSelectContact: openContact,
11057
12773
  onAccepted: (fp) => {
11058
12774
  pageStore.select(fp);
11059
12775
  },
@@ -11075,18 +12791,87 @@ window.__ModuleLoader__.load({
11075
12791
  }) : seatAgent !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentPane, {
11076
12792
  t,
11077
12793
  agent: seatAgent,
11078
- onOpenSession: openAlterSession,
11079
12794
  onRemoved: goAlter
11080
12795
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlterPane, {
11081
12796
  t,
11082
- onOpenSession: openAlterSession,
11083
12797
  onGoFriend: goFriend,
12798
+ onGoGroup: goGroup,
11084
12799
  renderCards: renderSlot,
11085
12800
  scope
11086
12801
  })]
11087
12802
  });
11088
12803
  }
11089
12804
  //#endregion
12805
+ //#region src/client/UpdateAction.tsx
12806
+ /**
12807
+ * Icon-only update button at the RIGHT end of the sidebar-foot SoulMirror row
12808
+ * (list seat `sidebar.footer.action`, ordered after the entry): invisible
12809
+ * until a newer release is known, then a small circled arrow. One click runs
12810
+ * the full install-restart-reload chain; while busy the tooltip narrates the
12811
+ * phase and the icon pulses. The reloaded page re-checks as current and the
12812
+ * button removes itself.
12813
+ */
12814
+ /** Upward arrow (update available). */
12815
+ function ArrowUpIcon({ size = 13 }) {
12816
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
12817
+ width: size,
12818
+ height: size,
12819
+ viewBox: "0 0 16 16",
12820
+ fill: "none",
12821
+ "aria-hidden": true,
12822
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12823
+ d: "M8 13.2V3.6M3.6 7.6 8 3.2l4.4 4.4",
12824
+ stroke: "currentColor",
12825
+ strokeWidth: "1.7",
12826
+ strokeLinecap: "round",
12827
+ strokeLinejoin: "round"
12828
+ })
12829
+ });
12830
+ }
12831
+ /** Spinning arc shown while the upgrade chain runs. */
12832
+ function SpinnerIcon({ size = 13 }) {
12833
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
12834
+ width: size,
12835
+ height: size,
12836
+ viewBox: "0 0 16 16",
12837
+ fill: "none",
12838
+ "aria-hidden": true,
12839
+ className: "sm-update-spin",
12840
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
12841
+ cx: "8",
12842
+ cy: "8",
12843
+ r: "6",
12844
+ stroke: "currentColor",
12845
+ strokeWidth: "1.7",
12846
+ strokeLinecap: "round",
12847
+ strokeDasharray: "28",
12848
+ strokeDashoffset: "20"
12849
+ })
12850
+ });
12851
+ }
12852
+ function UpdateAction({ t }) {
12853
+ const upgrade = (0, react.useSyncExternalStore)(upgradeStore.subscribe, upgradeStore.getSnapshot);
12854
+ const busy = upgrade.phase === "installing" || upgrade.phase === "restarting" || upgrade.phase === "reloading";
12855
+ if (!upgrade.hasUpdate && !busy) return null;
12856
+ const label = busy ? upgrade.phase === "installing" ? t("page.update.installing", { v: upgrade.latest ?? "" }) : upgrade.phase === "restarting" ? t("page.update.restarting") : t("page.update.reloading") : t("sidebar.update", { v: upgrade.latest ?? "" });
12857
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12858
+ label,
12859
+ delayMs: 200,
12860
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
12861
+ type: "button",
12862
+ className: `sm-update-fab${busy ? " sm-busy" : ""}`,
12863
+ disabled: busy,
12864
+ "aria-label": label,
12865
+ "data-soulmirror-update-action": upgrade.latest,
12866
+ onClick: () => {
12867
+ if (busy || upgrade.latest === void 0) return;
12868
+ upgradeStore.run();
12869
+ },
12870
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpinnerIcon, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArrowUpIcon, {})
12871
+ })
12872
+ });
12873
+ }
12874
+ //#endregion
11090
12875
  //#region src/client/styles.ts
11091
12876
  /**
11092
12877
  * The rules the SoulMirror surfaces need that inline styles cannot express
@@ -11101,13 +12886,40 @@ window.__ModuleLoader__.load({
11101
12886
  */
11102
12887
  const STYLE_ID = "soulmirror-styles";
11103
12888
  const CSS = `
11104
- .sm-update-banner {
11105
- flex: none; margin: 8px 10px 0; padding: 7px 10px; border: none; border-radius: 9px;
11106
- background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary-inverted);
11107
- font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; text-align: center;
12889
+ /* New-mail toast: our own pill (the host Toast surface stays light on the
12890
+ dark theme). Fixed top-center, themed, auto-dismissing. */
12891
+ .sm-mail-toast {
12892
+ position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 90;
12893
+ display: flex; align-items: center; gap: 8px; max-width: min(420px, 80vw);
12894
+ padding: 8px 14px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
12895
+ background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2)); color: var(--dsw-alias-label-primary);
12896
+ box-shadow: var(--dsw-shadow-lv3, 0 8px 24px rgba(0,0,0,.35)); cursor: pointer;
12897
+ font: inherit; font-size: 12px; animation: sm-page-in 140ms ease-out;
12898
+ }
12899
+ .sm-mail-toast:hover { border-color: var(--dsw-alias-brand-primary); }
12900
+
12901
+ /* Icon-only update button at the right end of the SoulMirror foot row:
12902
+ invisible until a release is known, small circled arrow, tooltip carries
12903
+ the words. Sits beside the entry (the row is a flex line). */
12904
+ .sm-update-fab {
12905
+ flex: none; align-self: center; width: 24px; height: 24px; margin: 4px 2px 0 0;
12906
+ display: inline-flex; align-items: center; justify-content: center;
12907
+ border: 1px solid var(--dsw-alias-brand-primary); border-radius: 50%;
12908
+ background: transparent; color: var(--dsw-alias-brand-primary); cursor: pointer; padding: 0;
11108
12909
  }
11109
- .sm-update-banner:hover { filter: brightness(1.08); }
11110
- .sm-update-banner:disabled { opacity: .7; cursor: default; }
12910
+ .sm-update-fab:hover { background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary-inverted); }
12911
+ .sm-update-fab:disabled, .sm-update-fab.sm-busy { opacity: .8; cursor: default; }
12912
+ .sm-update-spin { animation: sm-update-spin 900ms linear infinite; }
12913
+ @keyframes sm-update-spin { to { transform: rotate(360deg); } }
12914
+ display: inline-flex; align-items: center; gap: 5px; max-width: 100%; min-width: 0;
12915
+ padding: 2px 9px; border: 1px solid var(--dsw-alias-brand-primary); border-radius: 999px;
12916
+ background: transparent; color: var(--dsw-alias-brand-primary);
12917
+ font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; white-space: nowrap;
12918
+ }
12919
+ .sm-update-chip:hover { background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary-inverted); }
12920
+ .sm-update-chip:disabled { opacity: .75; cursor: default; }
12921
+ .sm-update-chip.sm-rail { padding: 2px 6px; }
12922
+
11111
12923
 
11112
12924
  /* Native form controls follow the ACTIVE theme: the option list of a <select>
11113
12925
  (and scrollbars, checkboxes...) is OS-rendered and ignores CSS tokens - it
@@ -11116,8 +12928,8 @@ body[data-ds-dark-theme] { color-scheme: dark; }
11116
12928
  .sm-page-root select option, .sm-page-root select optgroup { background: var(--dsw-specific-menu); color: var(--dsw-alias-label-primary); }
11117
12929
 
11118
12930
  .sm-footer {
11119
- flex: none; display: flex; align-items: center; gap: 8px;
11120
- width: calc(100% + 4px); height: 42px; margin: 4px -2px 0; padding: 0 10px 0 8px;
12931
+ flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
12932
+ width: auto; height: 42px; margin: 4px -2px 0; padding: 0 10px 0 8px;
11121
12933
  box-sizing: border-box; border: none; border-radius: 12px; background: transparent;
11122
12934
  cursor: pointer; overflow: hidden; color: var(--dsw-alias-label-primary);
11123
12935
  font-family: inherit; font-size: 14px; line-height: 22px; position: relative;
@@ -11256,7 +13068,7 @@ body[data-ds-dark-theme] { color-scheme: dark; }
11256
13068
  .sm-pendbar b { color: var(--dsw-alias-state-warn-primary); }
11257
13069
  .sm-pendbar .sm-linkbtn { margin-left: auto; }
11258
13070
  .sm-thread { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 20px; display: flex; flex-direction: column; }
11259
- .sm-thread-inner { margin-top: auto; display: flex; flex-direction: column; gap: 4px; }
13071
+ .sm-thread-inner { display: flex; flex-direction: column; gap: 4px; }
11260
13072
  .sm-day { align-self: center; margin: 10px 0 6px; padding: 1px 10px; border-radius: 10px; font-size: 11px;
11261
13073
  color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-interactive-bg-hover); }
11262
13074
  .sm-msg { display: flex; flex-direction: column; max-width: 72%; }
@@ -11500,7 +13312,8 @@ body[data-ds-dark-theme] .sm-page-root {
11500
13312
  "sessions",
11501
13313
  "settingsScope",
11502
13314
  "commandUi",
11503
- "theme"
13315
+ "theme",
13316
+ "workspaces"
11504
13317
  ];
11505
13318
  /** Whether a session cwd is the SoulMirror workspace (`<home>/a2a`, e.g. `~/.soulnet/a2a`). */
11506
13319
  function isSoulmirrorCwd(cwd) {
@@ -11514,14 +13327,12 @@ body[data-ds-dark-theme] .sm-page-root {
11514
13327
  en
11515
13328
  }), "soulmirror: dictionaries");
11516
13329
  const t = ctx.locale.bind(NS);
13330
+ setDirectoryPicker(() => ctx.workspaces.pickDirectory());
11517
13331
  ctx.effect(() => {
11518
13332
  ensureStyles();
11519
13333
  return removeStyles;
11520
13334
  }, "soulmirror: inbox styles");
11521
13335
  installBranding(ctx);
11522
- const openSession = (id) => {
11523
- ctx.sessions.open(id);
11524
- };
11525
13336
  ctx.conversationEvents.register(a2aRelayDefinition);
11526
13337
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
11527
13338
  name: "conversation.chat.node",
@@ -11534,6 +13345,12 @@ body[data-ds-dark-theme] .sm-page-root {
11534
13345
  order: 0,
11535
13346
  locale: NS
11536
13347
  }, SidebarEntry));
13348
+ ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
13349
+ name: "sidebar.footer.action",
13350
+ id: "soulmirror-update",
13351
+ order: 1,
13352
+ locale: NS
13353
+ }, UpdateAction));
11537
13354
  ctx.slots.inject("sidebar.nav.primary", () => {
11538
13355
  navSeatStore.set(true);
11539
13356
  const dispose = ctx.slots.register({
@@ -11548,10 +13365,7 @@ body[data-ds-dark-theme] .sm-page-root {
11548
13365
  };
11549
13366
  });
11550
13367
  const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
11551
- const pageInjected = () => ({
11552
- openSession,
11553
- scope
11554
- });
13368
+ const pageInjected = () => ({ scope });
11555
13369
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
11556
13370
  name: "shell.overlay",
11557
13371
  id: "soulmirror-page",
@@ -11599,18 +13413,30 @@ body[data-ds-dark-theme] .sm-page-root {
11599
13413
  clearInterval(timer);
11600
13414
  };
11601
13415
  });
11602
- const settingsInjected = () => ({
11603
- openSession,
11604
- scope
13416
+ const settingsInjected = () => ({ scope });
13417
+ ctx.slots.inject("settings.section", () => {
13418
+ const make = () => ctx.slots.register({
13419
+ name: "settings.section",
13420
+ id: "soulmirror",
13421
+ order: 80,
13422
+ locale: NS,
13423
+ label: () => upgradeStore.getSnapshot().hasUpdate ? `${t("settings.nav")} ●` : t("settings.nav"),
13424
+ inject: settingsInjected
13425
+ }, SoulmirrorSettingsSection);
13426
+ let hadUpdate = upgradeStore.getSnapshot().hasUpdate;
13427
+ let disposeSection = make();
13428
+ const unsubscribe = upgradeStore.subscribe(() => {
13429
+ const now = upgradeStore.getSnapshot().hasUpdate;
13430
+ if (now === hadUpdate) return;
13431
+ hadUpdate = now;
13432
+ disposeSection();
13433
+ disposeSection = make();
13434
+ });
13435
+ return () => {
13436
+ unsubscribe();
13437
+ disposeSection();
13438
+ };
11605
13439
  });
11606
- ctx.slots.inject("settings.section", () => ctx.slots.register({
11607
- name: "settings.section",
11608
- id: "soulmirror",
11609
- order: 80,
11610
- locale: NS,
11611
- label: () => upgradeStore.getSnapshot().hasUpdate ? `${t("settings.nav")} ●` : t("settings.nav"),
11612
- inject: settingsInjected
11613
- }, SoulmirrorSettingsSection));
11614
13440
  ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
11615
13441
  name: "settings.onboarding",
11616
13442
  id: "soulmirror-identity",