soulnet-dsh 0.1.9 → 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 (44) hide show
  1. package/lib/client.js +2341 -612
  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/alter-card.d.ts +3 -5
  24. package/lib/types/client/alter-ui.d.ts +2 -1
  25. package/lib/types/client/api.d.ts +134 -3
  26. package/lib/types/client/dir-picker.d.ts +3 -0
  27. package/lib/types/client/inbox-state.d.ts +4 -2
  28. package/lib/types/client/locales.d.ts +89 -4
  29. package/lib/types/client/page-state.d.ts +4 -5
  30. package/lib/types/client/page-store.d.ts +2 -2
  31. package/lib/types/friend-settings.d.ts +3 -0
  32. package/lib/types/group-contract.d.ts +37 -0
  33. package/lib/types/group-settings.d.ts +4 -0
  34. package/lib/types/index.d.ts +4 -0
  35. package/lib/types/memory/extract.d.ts +39 -0
  36. package/lib/types/memory/store.d.ts +67 -0
  37. package/lib/types/network/paygate.d.ts +61 -0
  38. package/lib/types/network/soulnet.d.ts +14 -0
  39. package/lib/types/network/types.d.ts +85 -4
  40. package/lib/types/persona.d.ts +2 -0
  41. package/lib/types/sessions/index.d.ts +42 -0
  42. package/lib/types/settings.d.ts +29 -0
  43. package/lib/types-zwvjR1n5.js.map +1 -1
  44. 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
@@ -2333,6 +2766,40 @@ 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",
@@ -2464,13 +2931,12 @@ window.__ModuleLoader__.load({
2464
2931
  "page.composer.instructing": "Sending…",
2465
2932
  "page.composer.direct": "Debug: direct to {name} · end-to-end encrypted · your alter does not see it",
2466
2933
  "alter.me": "My alter",
2934
+ "alter.wallet": "Wallet",
2467
2935
  "alter.row.hint": "Talk to your alter directly",
2468
2936
  "alter.row.drafts": "{n} draft(s) waiting for you",
2469
2937
  "alter.status.running": "Your alter is working…",
2470
2938
  "alter.status.idle": "Ready · one memory shared across every friend",
2471
2939
  "alter.status.noSession": "Starting the alter session…",
2472
- "alter.openDsh": "Open in dsh",
2473
- "alter.openDsh.hint": "The same conversation as a native dsh session (trajectory, tools, approvals).",
2474
2940
  "alter.pendbar": "{n} draft(s) of your alter wait for your decision",
2475
2941
  "alter.pendbar.go": "Review in {name}'s thread →",
2476
2942
  "alter.empty.title": "Tell your alter what to do",
@@ -2481,6 +2947,8 @@ window.__ModuleLoader__.load({
2481
2947
  "alter.thinking": "Your alter is working…",
2482
2948
  "alter.item.inbound": "Mail from {name}",
2483
2949
  "alter.item.view": "open thread",
2950
+ "alter.item.groupInbound": "Group message · {name}",
2951
+ "alter.item.viewGroup": "view in {name}",
2484
2952
  "alter.item.send": "To {name}",
2485
2953
  "alter.item.send.pending": "sending…",
2486
2954
  "alter.item.send.sent": "sent",
@@ -2498,6 +2966,7 @@ window.__ModuleLoader__.load({
2498
2966
  "draft.title": "Your alter wants to send this",
2499
2967
  "draft.title.to": "Your alter wants to send this to {name}",
2500
2968
  "draft.openThread": "open thread",
2969
+ "draft.openGroup": "view in group",
2501
2970
  "draft.reason.draft-tier": "Held because this friend's reply tier is \"draft\" (you review before anything goes out).",
2502
2971
  "draft.reason.notify-tier": "Held because this friend's reply tier is \"notify\".",
2503
2972
  "draft.reason.rate-limited": "Held because the hourly auto-reply cap for this friend is reached.",
@@ -2536,6 +3005,7 @@ window.__ModuleLoader__.load({
2536
3005
  "tier.short.auto": "auto",
2537
3006
  "friend.settings.title": "How your alter handles {name}",
2538
3007
  "friend.tier": "Reply tier",
3008
+ "friend.mute": "Mute (do not disturb)",
2539
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).",
2540
3010
  "friend.protocol": "Protocol override for this friend",
2541
3011
  "friend.protocol.placeholder": "Rules for this friend only (they win over the global protocol on conflict). Empty = none.",
@@ -2558,8 +3028,24 @@ window.__ModuleLoader__.load({
2558
3028
  "settings.alter.alterMode.full": "Full dsh (shell + files, like a normal session)",
2559
3029
  "settings.alter.alterMode.hint": "Applies to the next alter session — restart dsh (or reopen the alter session) to switch.",
2560
3030
  "settings.alter.openPage": "Open SoulMirror",
2561
- "settings.alter.openSession": "Open the alter session in dsh",
2562
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.",
2563
3049
  "group.section": "Groups",
2564
3050
  "group.create": "New group",
2565
3051
  "group.create.name": "Group name",
@@ -2576,6 +3062,8 @@ window.__ModuleLoader__.load({
2576
3062
  "group.leave.owner": "You are the owner — a group lives and dies with its owner for now.",
2577
3063
  "group.left": "Left the group.",
2578
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",
2579
3067
  "group.sender.me": "me",
2580
3068
  "group.composer.placeholder": "Message {name}…",
2581
3069
  "group.composer.hint": "Sent as yourself, end-to-end encrypted (sender key).",
@@ -2602,6 +3090,14 @@ window.__ModuleLoader__.load({
2602
3090
  "group.form.join.invite": "invite only",
2603
3091
  "group.form.join.apply": "strangers may apply",
2604
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).",
2605
3101
  "group.form.wake": "Alter wake-up",
2606
3102
  "group.form.wake.mention": "on mention",
2607
3103
  "group.form.wake.always": "always",
@@ -2649,6 +3145,7 @@ window.__ModuleLoader__.load({
2649
3145
  "group.speak.whoOwner": "Only the owner posts in this group.",
2650
3146
  "group.speak.whoAdmins": "Only the owner and admins post in this group.",
2651
3147
  "group.alter.toggle": "My alter participates",
3148
+ "group.mute": "Mute (do not disturb)",
2652
3149
  "group.mode.mention": "replies when @mentioned",
2653
3150
  "group.mode.always": "active — may reply to anything",
2654
3151
  "group.by.alter": "alter",
@@ -2665,6 +3162,7 @@ window.__ModuleLoader__.load({
2665
3162
  "settings.agents.none": "No agents yet — add one below.",
2666
3163
  "settings.agents.name": "Name (used as @mention)",
2667
3164
  "settings.agents.cwd": "Working directory - the folder it reads, writes and runs commands in (optional)",
3165
+ "settings.agents.cwd.browse": "Browse",
2668
3166
  "settings.agents.cwdHint": "Leave empty for its own default folder",
2669
3167
  "settings.agents.preset": "Capability preset",
2670
3168
  "settings.agents.preset.default": "standard coding agent (default)",
@@ -2681,7 +3179,6 @@ window.__ModuleLoader__.load({
2681
3179
  "settings.agents.remove": "Remove",
2682
3180
  "settings.agents.saved": "Agent \"{name}\" saved.",
2683
3181
  "settings.agents.removed": "Removed \"{name}\".",
2684
- "settings.agents.open": "Open session",
2685
3182
  "settings.agents.status.running": "working…",
2686
3183
  "settings.agents.status.idle": "idle",
2687
3184
  "agents.section": "My agents",
@@ -2702,15 +3199,34 @@ window.__ModuleLoader__.load({
2702
3199
  "agent.thinking.done": "Thought",
2703
3200
  "group.join.section": "Join a group",
2704
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.",
2705
3216
  "group.join.apply": "Apply",
2706
3217
  "group.join.applied": "Application sent.",
2707
3218
  "group.apps.badge": "{n} application(s) waiting",
3219
+ "col2.messages": "Messages",
2708
3220
  "col2.contacts": "Contacts",
2709
3221
  "col2.agents": "Agents",
2710
3222
  "col2.groups": "Groups",
2711
3223
  "col2.contacts.section": "You",
2712
3224
  "col2.agents.empty": "No agents yet — create one to give it a job on your behalf.",
2713
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",
2714
3230
  "groups.section": "Groups",
2715
3231
  "pane.chat": "Chat",
2716
3232
  "pane.announce": "Bulletin",
@@ -2719,6 +3235,7 @@ window.__ModuleLoader__.load({
2719
3235
  "pane.admin": "Manage",
2720
3236
  "pane.info": "Info",
2721
3237
  "pane.settings": "Settings",
3238
+ "pane.memory": "Memory",
2722
3239
  "friend.home.profile": "Profile",
2723
3240
  "friend.home.fp": "Fingerprint",
2724
3241
  "friend.home.name": "Name",
@@ -2727,6 +3244,7 @@ window.__ModuleLoader__.load({
2727
3244
  "friend.home.lastActive": "Last active",
2728
3245
  "friend.home.drafts": "Drafts waiting",
2729
3246
  "friend.home.actions": "Actions",
3247
+ "friend.home.sendMessage": "Send a message",
2730
3248
  "alter.home.profile": "Profile",
2731
3249
  "alter.home.session": "Session",
2732
3250
  "alter.home.friends": "Friends",
@@ -2766,6 +3284,40 @@ window.__ModuleLoader__.load({
2766
3284
  "inbox.add": "添加",
2767
3285
  "inbox.add.placeholder": "加好友:贴名片链接(soulmirror://card?…)",
2768
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": "该范围暂无记忆。",
2769
3321
  "bubble.in": "来自 {name}",
2770
3322
  "bubble.out": "发给 {name}",
2771
3323
  "bubble.mine": "我的分身",
@@ -2897,13 +3449,12 @@ window.__ModuleLoader__.load({
2897
3449
  "page.composer.instructing": "发送中…",
2898
3450
  "page.composer.direct": "调试:直发给 {name} · 端到端加密 · 分身看不到",
2899
3451
  "alter.me": "我的分身",
3452
+ "alter.wallet": "钱包",
2900
3453
  "alter.row.hint": "跟你的分身直接对话",
2901
3454
  "alter.row.drafts": "{n} 项等你拍板",
2902
3455
  "alter.status.running": "分身正在处理…",
2903
3456
  "alter.status.idle": "就绪 · 对所有好友共享同一记忆",
2904
3457
  "alter.status.noSession": "分身会话启动中…",
2905
- "alter.openDsh": "在 dsh 里打开",
2906
- "alter.openDsh.hint": "同一段对话的 dsh 原生会话(轨迹、工具、审批)。",
2907
3458
  "alter.pendbar": "还有 {n} 项分身拟稿等你拍板",
2908
3459
  "alter.pendbar.go": "去「{name}」里看 →",
2909
3460
  "alter.empty.title": "吩咐你的分身",
@@ -2914,6 +3465,8 @@ window.__ModuleLoader__.load({
2914
3465
  "alter.thinking": "分身正在处理…",
2915
3466
  "alter.item.inbound": "{name} 来信",
2916
3467
  "alter.item.view": "看往来",
3468
+ "alter.item.groupInbound": "群消息 · {name}",
3469
+ "alter.item.viewGroup": "在 {name} 群 中查看",
2917
3470
  "alter.item.send": "发给 {name}",
2918
3471
  "alter.item.send.pending": "发送中…",
2919
3472
  "alter.item.send.sent": "已发出",
@@ -2931,6 +3484,7 @@ window.__ModuleLoader__.load({
2931
3484
  "draft.title": "我的分身想这样回复",
2932
3485
  "draft.title.to": "我的分身想这样发给 {name}",
2933
3486
  "draft.openThread": "看往来",
3487
+ "draft.openGroup": "在群中查看",
2934
3488
  "draft.reason.draft-tier": "依据:这位好友的应答档位是「拟稿」(发出前先问你)。",
2935
3489
  "draft.reason.notify-tier": "依据:这位好友的应答档位是「只通知」。",
2936
3490
  "draft.reason.rate-limited": "依据:对这位好友的自动回复已达每小时上限。",
@@ -2969,6 +3523,7 @@ window.__ModuleLoader__.load({
2969
3523
  "tier.short.auto": "自动",
2970
3524
  "friend.settings.title": "分身如何对待 {name}",
2971
3525
  "friend.tier": "应答档位",
3526
+ "friend.mute": "免打扰",
2972
3527
  "friend.tier.hint": "只通知:来信只展示;拟稿:分身拟好回复、在本页等你拍板;自动:分身自己发(每小时最多 {n} 条)。",
2973
3528
  "friend.protocol": "对这位好友的准则补充",
2974
3529
  "friend.protocol.placeholder": "只对这位好友生效的规则(冲突时压过全局准则)。留空 = 没有。",
@@ -2991,8 +3546,24 @@ window.__ModuleLoader__.load({
2991
3546
  "settings.alter.alterMode.full": "完整 dsh(shell + 文件,像普通会话)",
2992
3547
  "settings.alter.alterMode.hint": "对下一个分身会话生效——切换后重启 dsh(或重新打开分身会话)。",
2993
3548
  "settings.alter.openPage": "打开灵镜",
2994
- "settings.alter.openSession": "在 dsh 里打开分身会话",
2995
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。",
2996
3567
  "group.section": "群",
2997
3568
  "group.create": "新建群",
2998
3569
  "group.create.name": "群名称",
@@ -3009,6 +3580,8 @@ window.__ModuleLoader__.load({
3009
3580
  "group.leave.owner": "你是群主——目前群与群主共存亡,不能退出。",
3010
3581
  "group.left": "已退群。",
3011
3582
  "group.empty": "还没有消息——在下面说点什么。",
3583
+ "group.draftReminder": "分身拟了一份回复,等待你拍板。",
3584
+ "group.draftReminder.go": "去查看",
3012
3585
  "group.sender.me": "我",
3013
3586
  "group.composer.placeholder": "发到 {name}…",
3014
3587
  "group.composer.hint": "以你本人身份发送,端到端加密(发送者密钥)。",
@@ -3035,6 +3608,14 @@ window.__ModuleLoader__.load({
3035
3608
  "group.form.join.invite": "仅邀请",
3036
3609
  "group.form.join.apply": "陌生人可申请",
3037
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": "付费进群会自动设为公开群(陌生人需要群链接才能找到并付款)。",
3038
3619
  "group.form.wake": "分身唤醒",
3039
3620
  "group.form.wake.mention": "被点名时",
3040
3621
  "group.form.wake.always": "总是",
@@ -3082,6 +3663,7 @@ window.__ModuleLoader__.load({
3082
3663
  "group.speak.whoOwner": "这个群里只有群主发言。",
3083
3664
  "group.speak.whoAdmins": "这个群里只有群主和管理员发言。",
3084
3665
  "group.alter.toggle": "我的分身参与",
3666
+ "group.mute": "免打扰",
3085
3667
  "group.mode.mention": "被@才回应",
3086
3668
  "group.mode.always": "积极参与",
3087
3669
  "group.by.alter": "分身",
@@ -3098,6 +3680,7 @@ window.__ModuleLoader__.load({
3098
3680
  "settings.agents.none": "还没有代理——在下面添加一个。",
3099
3681
  "settings.agents.name": "名字(群里 @ 它用)",
3100
3682
  "settings.agents.cwd": "工作目录——它读写文件、跑命令的文件夹(选填)",
3683
+ "settings.agents.cwd.browse": "浏览",
3101
3684
  "settings.agents.cwdHint": "留空 = 使用它自己的默认目录",
3102
3685
  "settings.agents.preset": "能力预设",
3103
3686
  "settings.agents.preset.default": "标准编码 Agent(默认)",
@@ -3114,7 +3697,6 @@ window.__ModuleLoader__.load({
3114
3697
  "settings.agents.remove": "移除",
3115
3698
  "settings.agents.saved": "代理「{name}」已保存。",
3116
3699
  "settings.agents.removed": "已移除「{name}」。",
3117
- "settings.agents.open": "打开会话",
3118
3700
  "settings.agents.status.running": "干活中…",
3119
3701
  "settings.agents.status.idle": "空闲",
3120
3702
  "agents.section": "我的 Agent",
@@ -3135,15 +3717,34 @@ window.__ModuleLoader__.load({
3135
3717
  "agent.thinking.done": "已思考",
3136
3718
  "group.join.section": "加入群",
3137
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": "已附付款证明并提交申请,等待群主验证。",
3138
3734
  "group.join.apply": "申请",
3139
3735
  "group.join.applied": "申请已发出。",
3140
3736
  "group.apps.badge": "{n} 条进群申请待处理",
3737
+ "col2.messages": "消息",
3141
3738
  "col2.contacts": "通讯录",
3142
3739
  "col2.agents": "智能体",
3143
3740
  "col2.groups": "群聊",
3144
3741
  "col2.contacts.section": "我",
3145
3742
  "col2.agents.empty": "还没有智能体——创建一个,让它替你做点事。",
3146
3743
  "col2.groups.empty": "还没有群——建一个,或用群链接加入。",
3744
+ "agents.expand": "展开 {n} 个",
3745
+ "agents.collapse": "收起",
3746
+ "messages.friends": "好友",
3747
+ "messages.section": "会话",
3147
3748
  "groups.section": "群聊",
3148
3749
  "pane.chat": "聊天",
3149
3750
  "pane.announce": "公告",
@@ -3152,6 +3753,7 @@ window.__ModuleLoader__.load({
3152
3753
  "pane.admin": "管理",
3153
3754
  "pane.info": "信息",
3154
3755
  "pane.settings": "设置",
3756
+ "pane.memory": "记忆",
3155
3757
  "friend.home.profile": "名片",
3156
3758
  "friend.home.fp": "指纹",
3157
3759
  "friend.home.name": "名字",
@@ -3160,6 +3762,7 @@ window.__ModuleLoader__.load({
3160
3762
  "friend.home.lastActive": "最近活跃",
3161
3763
  "friend.home.drafts": "待审稿",
3162
3764
  "friend.home.actions": "操作",
3765
+ "friend.home.sendMessage": "去发消息",
3163
3766
  "alter.home.profile": "档案",
3164
3767
  "alter.home.session": "会话",
3165
3768
  "alter.home.friends": "好友",
@@ -3641,6 +4244,7 @@ window.__ModuleLoader__.load({
3641
4244
  const [voices, setVoices] = (0, react.useState)(void 0);
3642
4245
  const [voiceCommanders, setVoiceCommanders] = (0, react.useState)({});
3643
4246
  const [duty, setDuty] = (0, react.useState)("");
4247
+ const [muted, setMuted] = (0, react.useState)(group.muted === true);
3644
4248
  const [agentNames, setAgentNames] = (0, react.useState)([]);
3645
4249
  const [agentsSheetOpen, setAgentsSheetOpen] = (0, react.useState)(false);
3646
4250
  const [mention, setMention] = (0, react.useState)(void 0);
@@ -3654,6 +4258,7 @@ window.__ModuleLoader__.load({
3654
4258
  const following = (0, react.useRef)(true);
3655
4259
  const prevFirstSeq = (0, react.useRef)(void 0);
3656
4260
  const prevHeight = (0, react.useRef)(0);
4261
+ const groupDrafts = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot).inbox.drafts.filter((d) => d.gid === gid);
3657
4262
  (0, react.useEffect)(() => {
3658
4263
  setText("");
3659
4264
  setVoices(void 0);
@@ -3729,6 +4334,7 @@ window.__ModuleLoader__.load({
3729
4334
  setVoices(map);
3730
4335
  setVoiceCommanders(cmds);
3731
4336
  setDuty(settings.duty ?? "");
4337
+ if (settings.muted !== void 0) setMuted(settings.muted === true);
3732
4338
  };
3733
4339
  (0, react.useEffect)(() => {
3734
4340
  if (!showAlterChip) return;
@@ -3771,6 +4377,16 @@ window.__ModuleLoader__.load({
3771
4377
  setDuty(prev);
3772
4378
  });
3773
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
+ };
3774
4390
  /** Load one of MY agents' work-session trace for this group (local; persists in the timeline). */
3775
4391
  const loadFeed = (name) => {
3776
4392
  api.agentHistory(name, 80, gid).then((h) => {
@@ -4128,6 +4744,18 @@ window.__ModuleLoader__.load({
4128
4744
  ]
4129
4745
  })
4130
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,
4131
4759
  mutedHint !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4132
4760
  className: "sm-mutebar",
4133
4761
  "data-soulmirror-group-composer": true,
@@ -4136,120 +4764,147 @@ window.__ModuleLoader__.load({
4136
4764
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4137
4765
  className: "sm-composer",
4138
4766
  "data-soulmirror-group-composer": true,
4139
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4140
- className: "sm-composer-box",
4141
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4767
+ children: [
4768
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4142
4769
  style: {
4143
- position: "relative",
4144
- flex: 1,
4145
- display: "flex"
4770
+ display: "flex",
4771
+ alignItems: "center",
4772
+ gap: 14,
4773
+ padding: "0 16px 6px"
4146
4774
  },
4147
- 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", {
4148
4795
  style: {
4149
- position: "absolute",
4150
- bottom: "100%",
4151
- left: 0,
4152
- marginBottom: 6,
4153
- zIndex: 20,
4154
- minWidth: 180,
4155
- maxHeight: 220,
4156
- overflowY: "auto",
4157
- borderRadius: 8,
4158
- border: "1px solid rgba(127,127,127,.35)",
4159
- background: "var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2, #fff))",
4160
- boxShadow: "0 6px 24px rgba(0,0,0,.18)",
4161
- padding: 4,
4162
- display: "grid"
4796
+ position: "relative",
4797
+ flex: 1,
4798
+ display: "flex"
4163
4799
  },
4164
- "data-soulmirror-mention-pop": true,
4165
- children: mentionMatches.map((p, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4166
- type: "button",
4167
- className: "sm-linkbtn",
4800
+ children: [mention !== void 0 && mentionMatches.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4168
4801
  style: {
4169
- textAlign: "left",
4170
- padding: "6px 8px",
4171
- borderRadius: 6,
4172
- ...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"
4173
4816
  },
4174
- onMouseEnter: () => {
4175
- 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);
4176
4858
  },
4177
- onMouseDown: (e) => {
4178
- e.preventDefault();
4179
- pickMention(p.name);
4859
+ onBlur: () => {
4860
+ setTimeout(() => {
4861
+ setMention(void 0);
4862
+ }, 120);
4180
4863
  },
4181
- children: [
4182
- p.owner !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4183
- "aria-hidden": true,
4184
- style: { marginRight: 4 },
4185
- children: "🤖"
4186
- }) : null,
4187
- "@",
4188
- p.name,
4189
- p.owner !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4190
- className: "sm-statepill",
4191
- style: { marginLeft: 6 },
4192
- children: p.owner
4193
- }) : null
4194
- ]
4195
- }, `${p.name}|${p.owner ?? ""}`))
4196
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4197
- ref: inputRef,
4198
- className: "sm-textarea",
4199
- rows: 1,
4200
- value: text,
4201
- placeholder: t("group.composer.placeholder", { name: group.name }),
4202
- onChange: (e) => {
4203
- setText(e.target.value);
4204
- detectMention(e.target.value, e.target.selectionStart ?? e.target.value.length);
4205
- },
4206
- onBlur: () => {
4207
- setTimeout(() => {
4208
- setMention(void 0);
4209
- }, 120);
4210
- },
4211
- onKeyDown: (e) => {
4212
- if (e.key === "Escape" && mention !== void 0) {
4213
- e.preventDefault();
4214
- e.stopPropagation();
4215
- setMention(void 0);
4216
- return;
4217
- }
4218
- if (mention !== void 0 && mentionMatches.length > 0 && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
4219
- e.preventDefault();
4220
- const len = mentionMatches.length;
4221
- setMentionIdx((i) => (Math.min(i, len - 1) + (e.key === "ArrowDown" ? 1 : len - 1)) % len);
4222
- return;
4223
- }
4224
- if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
4225
- e.preventDefault();
4226
- if (mention !== void 0 && mentionMatches.length > 0) pickMention(mentionMatches[Math.min(mentionIdx, mentionMatches.length - 1)].name);
4227
- else send();
4228
- }
4229
- },
4230
- "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")
4231
4893
  })]
4232
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4233
- variant: "primary",
4234
- size: "sm",
4235
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline16, { size: 14 }),
4236
- disabled: text.trim() === "",
4237
- onClick: send,
4238
- "data-soulmirror-group-send": true,
4239
- children: t("group.send")
4240
- })]
4241
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4242
- className: "sm-composer-hint",
4243
- style: {
4244
- display: "flex",
4245
- alignItems: "center",
4246
- gap: 10
4247
- },
4248
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4249
- style: { flex: 1 },
4250
- children: t("group.composer.hint")
4251
- }), alterSwitch]
4252
- })]
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
+ ]
4253
4908
  }),
4254
4909
  agentsSheetOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupAgentsSheet, {
4255
4910
  t,
@@ -4525,35 +5180,143 @@ window.__ModuleLoader__.load({
4525
5180
  })]
4526
5181
  })] });
4527
5182
  }
4528
- function SettingField({ label, field, scope, values, user, writable, type = "text", options, optionLabel, min }) {
4529
- const current = values?.[field] ?? "";
4530
- 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
+ }, []);
4531
5195
  (0, react.useEffect)(() => {
4532
- setDraft(String(current));
4533
- }, [current]);
4534
- const commit = () => {
4535
- if (draft === String(current)) return;
4536
- const value = type === "number" ? Math.max(min ?? 0, Math.floor(Number(draft) || 0)) : draft;
4537
- scope.set(field, value).catch(() => {});
4538
- };
4539
- const overridden = field in user;
4540
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4541
- style: {
4542
- display: "grid",
4543
- gap: 4,
4544
- fontSize: "0.85em"
4545
- },
4546
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4547
- style: { opacity: .8 },
4548
- children: [label, overridden ? " *" : ""]
4549
- }), type === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
4550
- value: draft,
4551
- disabled: !writable,
4552
- onChange: (e) => {
4553
- setDraft(e.target.value);
4554
- scope.set(field, e.target.value).catch(() => {});
4555
- },
4556
- 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,
4557
5320
  "data-soulmirror-setting": field,
4558
5321
  children: (options ?? []).map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4559
5322
  value: o,
@@ -4561,7 +5324,7 @@ window.__ModuleLoader__.load({
4561
5324
  }, o))
4562
5325
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4563
5326
  style: input,
4564
- type: type === "number" ? "number" : "text",
5327
+ type: type === "password" ? "password" : type === "number" ? "number" : "text",
4565
5328
  min,
4566
5329
  value: draft,
4567
5330
  disabled: !writable,
@@ -4701,7 +5464,7 @@ window.__ModuleLoader__.load({
4701
5464
  const key = BINARY_SOURCE_KEYS[source];
4702
5465
  return key === void 0 ? source : t(key);
4703
5466
  }
4704
- function SoulmirrorSettingsSection({ openSession, scope, t }) {
5467
+ function SoulmirrorSettingsSection({ scope, t }) {
4705
5468
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
4706
5469
  const subscribeScope = (0, react.useCallback)((listener) => scope.subscribe(listener), [scope]);
4707
5470
  const readScope = (0, react.useCallback)(() => scope.getSnapshot(), [scope]);
@@ -4942,28 +5705,17 @@ window.__ModuleLoader__.load({
4942
5705
  }),
4943
5706
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4944
5707
  style: rowStyle,
4945
- children: [
4946
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4947
- type: "button",
4948
- onClick: () => {
4949
- pageStore.open("alter");
4950
- },
4951
- "data-soulmirror-settings-open-page": true,
4952
- children: t("settings.alter.openPage")
4953
- }),
4954
- state?.alter?.sessionId != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4955
- type: "button",
4956
- onClick: () => {
4957
- openSession(state.alter.sessionId);
4958
- },
4959
- "data-soulmirror-settings-open-alter-session": true,
4960
- children: t("settings.alter.openSession")
4961
- }) : null,
4962
- state?.alter?.legacyFriendSessions !== void 0 && Object.keys(state.alter.legacyFriendSessions).length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4963
- style: small$1,
4964
- children: t("settings.alter.legacy", { n: Object.keys(state.alter.legacyFriendSessions).length })
4965
- }) : null
4966
- ]
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]
4967
5719
  }),
4968
5720
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4969
5721
  style: {
@@ -4984,6 +5736,87 @@ window.__ModuleLoader__.load({
4984
5736
  })
4985
5737
  ]
4986
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
+ }),
4987
5820
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4988
5821
  style: card,
4989
5822
  children: [
@@ -5554,14 +6387,36 @@ window.__ModuleLoader__.load({
5554
6387
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5555
6388
  style: { opacity: .8 },
5556
6389
  children: t("settings.agents.cwd")
5557
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5558
- style: field,
5559
- value: cwd,
5560
- placeholder: t("settings.agents.cwdHint"),
5561
- onChange: (e) => {
5562
- setCwd(e.target.value);
6390
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6391
+ style: {
6392
+ display: "flex",
6393
+ gap: 6
5563
6394
  },
5564
- "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
+ })]
5565
6420
  })]
5566
6421
  }),
5567
6422
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -5689,15 +6544,377 @@ window.__ModuleLoader__.load({
5689
6544
  });
5690
6545
  }
5691
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
5692
6910
  //#region src/client/AgentPane.tsx
5693
6911
  /**
5694
6912
  * Right pane for one NAMED seat agent: the owner talks to it directly here
5695
6913
  * (composer → `agent.instruct`), watches its transcript (`agent.history`,
5696
6914
  * same fold as the alter's — owner bubbles right, the agent's notes left,
5697
6915
  * the group messages that woke it as compact cards, its group posts as send
5698
- * lines) and reaches its settings (the sheet) and its native dsh session
5699
- * from the header. Live: SSE `agent` frames for this name trigger a
5700
- * debounced refetch.
6916
+ * lines) and reaches its settings (the sheet). Live: SSE `agent` frames for
6917
+ * this name trigger a debounced refetch.
5701
6918
  */
5702
6919
  const FOLLOW_SLACK$2 = 48;
5703
6920
  const HISTORY_LIMIT = 200;
@@ -5706,7 +6923,7 @@ window.__ModuleLoader__.load({
5706
6923
  const d = new Date(ts);
5707
6924
  return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
5708
6925
  }
5709
- function AgentPane({ t, agent, onOpenSession, onRemoved }) {
6926
+ function AgentPane({ t, agent, onRemoved }) {
5710
6927
  const name = agent.name;
5711
6928
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
5712
6929
  const [history, setHistory] = (0, react.useState)(void 0);
@@ -6003,13 +7220,13 @@ window.__ModuleLoader__.load({
6003
7220
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6004
7221
  className: "sm-home-title",
6005
7222
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("agent.home.actions") })
6006
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7223
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6007
7224
  style: {
6008
7225
  display: "flex",
6009
7226
  gap: 6,
6010
7227
  flexWrap: "wrap"
6011
7228
  },
6012
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7229
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6013
7230
  type: "button",
6014
7231
  className: "sm-ghostbtn",
6015
7232
  onClick: () => {
@@ -6020,18 +7237,7 @@ window.__ModuleLoader__.load({
6020
7237
  " ",
6021
7238
  t("agent.settings")
6022
7239
  ]
6023
- }), sessionId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6024
- type: "button",
6025
- className: "sm-ghostbtn",
6026
- onClick: () => {
6027
- onOpenSession(sessionId);
6028
- },
6029
- children: [
6030
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6031
- " ",
6032
- t("alter.openDsh")
6033
- ]
6034
- }) : null]
7240
+ })
6035
7241
  })]
6036
7242
  })
6037
7243
  ]
@@ -6075,9 +7281,9 @@ window.__ModuleLoader__.load({
6075
7281
  ]
6076
7282
  })]
6077
7283
  }),
6078
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6079
7285
  className: "sm-chat-head-actions",
6080
- 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, {
6081
7287
  label: t("agent.settings"),
6082
7288
  side: "bottom",
6083
7289
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -6093,23 +7299,7 @@ window.__ModuleLoader__.load({
6093
7299
  t("agent.settings")
6094
7300
  ]
6095
7301
  })
6096
- }), sessionId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6097
- label: t("alter.openDsh.hint"),
6098
- side: "bottom",
6099
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6100
- type: "button",
6101
- className: "sm-ghostbtn",
6102
- onClick: () => {
6103
- onOpenSession(sessionId);
6104
- },
6105
- "data-soulmirror-agent-open-dsh": true,
6106
- children: [
6107
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6108
- " ",
6109
- t("alter.openDsh")
6110
- ]
6111
- })
6112
- }) : null]
7302
+ })
6113
7303
  })
6114
7304
  ]
6115
7305
  }),
@@ -6119,7 +7309,17 @@ window.__ModuleLoader__.load({
6119
7309
  onChange: pageStore.setPaneTab,
6120
7310
  t
6121
7311
  }),
6122
- 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: [
6123
7323
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6124
7324
  ref: scroller,
6125
7325
  className: "sm-thread",
@@ -6248,12 +7448,17 @@ window.__ModuleLoader__.load({
6248
7448
  default: return t("draft.reason.unknown-trigger");
6249
7449
  }
6250
7450
  }
6251
- function DraftCard({ draft, t, showTarget, onGoFriend }) {
7451
+ function DraftCard({ draft, t, showTarget, onGoFriend, onGoGroup }) {
6252
7452
  const busy = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot).deciding.includes(draft.id);
6253
7453
  const [mode, setMode] = (0, react.useState)("idle");
6254
7454
  const [text, setText] = (0, react.useState)(draft.body);
6255
7455
  const [feedback, setFeedback] = (0, react.useState)("");
6256
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;
6257
7462
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6258
7463
  className: "sm-draft",
6259
7464
  "data-soulmirror-draft": draft.id,
@@ -6267,13 +7472,11 @@ window.__ModuleLoader__.load({
6267
7472
  children: t("draft.tag")
6268
7473
  }),
6269
7474
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: showTarget === true ? t("draft.title.to", { name: draft.name }) : t("draft.title") }),
6270
- 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", {
6271
7476
  type: "button",
6272
7477
  className: "sm-linkbtn",
6273
- onClick: () => {
6274
- onGoFriend(draft.fp);
6275
- },
6276
- children: t("draft.openThread")
7478
+ onClick: openTarget,
7479
+ children: draft.gid !== void 0 ? t("draft.openGroup") : t("draft.openThread")
6277
7480
  }) : null,
6278
7481
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6279
7482
  className: "sm-row-time",
@@ -6440,7 +7643,7 @@ window.__ModuleLoader__.load({
6440
7643
  const d = new Date(ts);
6441
7644
  return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
6442
7645
  }
6443
- function AlterPane({ t, onOpenSession, onGoFriend, renderCards, scope }) {
7646
+ function AlterPane({ t, onGoFriend, onGoGroup, renderCards, scope }) {
6444
7647
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
6445
7648
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
6446
7649
  const alter = page.alter;
@@ -6448,6 +7651,23 @@ window.__ModuleLoader__.load({
6448
7651
  const drafts = net.inbox.drafts;
6449
7652
  const running = alter.status === "running" || alter.chat.running || alter.instructing;
6450
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
+ }, []);
6451
7671
  const scroller = (0, react.useRef)(null);
6452
7672
  const textarea = (0, react.useRef)(null);
6453
7673
  const following = (0, react.useRef)(true);
@@ -6464,7 +7684,7 @@ window.__ModuleLoader__.load({
6464
7684
  if (el === null) return;
6465
7685
  if (firstPaint.current && items.length > 0) {
6466
7686
  firstPaint.current = false;
6467
- el.scrollTop = 0;
7687
+ el.scrollTop = el.scrollHeight;
6468
7688
  return;
6469
7689
  }
6470
7690
  if (following.current) el.scrollTop = el.scrollHeight;
@@ -6572,31 +7792,33 @@ window.__ModuleLoader__.load({
6572
7792
  })]
6573
7793
  });
6574
7794
  case "inbound": {
6575
- 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
+ };
6576
7801
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6577
7802
  className: "sm-citem sm-wide",
6578
- "data-soulmirror-alter-item": "inbound",
7803
+ "data-soulmirror-alter-item": group !== void 0 ? "group" : "inbound",
6579
7804
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6580
7805
  className: "sm-inmail",
6581
7806
  role: "button",
6582
7807
  tabIndex: 0,
6583
- onClick: () => {
6584
- onGoFriend(item.fp);
6585
- },
7808
+ onClick: open,
6586
7809
  onKeyDown: (e) => {
6587
- if (e.key === "Enter") onGoFriend(item.fp);
7810
+ if (e.key === "Enter") open();
6588
7811
  },
6589
7812
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6590
7813
  className: "sm-inmail-head",
6591
7814
  children: [
6592
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.item.inbound", { name: "" }) }),
6593
- /* @__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 })] }),
6594
7816
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", formatClock(item.ts)] }),
6595
7817
  item.auto ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", t("bubble.auto")] }) : null,
6596
7818
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6597
7819
  style: { marginLeft: "auto" },
6598
7820
  className: "sm-linkbtn",
6599
- children: t("alter.item.view")
7821
+ children: group !== void 0 ? t("alter.item.viewGroup", { name }) : t("alter.item.view")
6600
7822
  })
6601
7823
  ]
6602
7824
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6729,110 +7951,88 @@ window.__ModuleLoader__.load({
6729
7951
  "data-soulmirror-alter-home": true,
6730
7952
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6731
7953
  className: "sm-home-inner",
6732
- children: [
6733
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6734
- 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
+ },
6735
7966
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6736
- className: "sm-avatar sm-avatar-alter sm-avatar-lg",
6737
- "aria-hidden": true,
6738
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 18 })
6739
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6740
- style: {
6741
- display: "grid",
6742
- gap: 2,
6743
- minWidth: 0
6744
- },
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",
6745
7971
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6746
- className: "sm-home-id-name",
6747
- children: t("alter.me")
6748
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6749
- className: "sm-home-id-sub",
6750
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6751
- className: `sm-livedot${running ? " sm-busy" : ""}`,
6752
- "aria-hidden": true
6753
- }), /* @__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") })]
6754
- })]
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") })]
6755
7975
  })]
6756
- }),
6757
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6758
- className: "sm-home-card",
6759
- children: [
6760
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6761
- className: "sm-home-title",
6762
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.home.profile") })
6763
- }),
6764
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6765
- className: "sm-home-line",
6766
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6767
- className: "sm-home-line-key",
6768
- children: t("alter.home.session")
6769
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6770
- className: "sm-home-line-val",
6771
- children: alter.sessionId ?? t("alter.home.none")
6772
- })]
6773
- }),
6774
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6775
- className: "sm-home-line",
6776
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6777
- className: "sm-home-line-key",
6778
- children: t("alter.home.friends")
6779
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6780
- className: "sm-home-line-val",
6781
- children: friends.length
6782
- })]
6783
- }),
6784
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6785
- className: "sm-home-line",
6786
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6787
- className: "sm-home-line-key",
6788
- children: t("alter.home.drafts")
6789
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6790
- className: "sm-home-line-val",
6791
- children: drafts.length
6792
- })]
6793
- }),
6794
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6795
- className: "sm-home-line",
6796
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6797
- className: "sm-home-line-key",
6798
- children: t("alter.home.defaultTier")
6799
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6800
- className: "sm-home-line-val",
6801
- children: t(`tier.short.${net.state?.alter?.defaultTier ?? "draft"}`)
6802
- })]
6803
- }),
6804
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6805
- className: "sm-home-line",
6806
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6807
- className: "sm-home-line-key",
6808
- children: t("alter.home.perHour")
6809
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6810
- className: "sm-home-line-val",
6811
- children: net.state?.alter?.autoReplyPerHour ?? 20
6812
- })]
6813
- })
6814
- ]
6815
- }),
6816
- alter.sessionId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6817
- className: "sm-home-card",
6818
- 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", {
6819
7981
  className: "sm-home-title",
6820
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("alter.home.actions") })
6821
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6822
- type: "button",
6823
- className: "sm-ghostbtn",
6824
- onClick: () => {
6825
- onOpenSession(alter.sessionId);
6826
- },
6827
- "data-soulmirror-alter-open-dsh": true,
6828
- children: [
6829
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6830
- " ",
6831
- t("alter.openDsh")
6832
- ]
6833
- })]
6834
- }) : null
6835
- ]
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
+ })]
6836
8036
  })
6837
8037
  });
6838
8038
  const cardProps = {
@@ -6840,10 +8040,7 @@ window.__ModuleLoader__.load({
6840
8040
  sessionId: alter.sessionId,
6841
8041
  status: alter.status
6842
8042
  },
6843
- scope,
6844
- openSession: () => {
6845
- if (alter.sessionId !== void 0) onOpenSession(alter.sessionId);
6846
- }
8043
+ scope
6847
8044
  };
6848
8045
  const alterSettings = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6849
8046
  className: "sm-home",
@@ -6861,58 +8058,86 @@ window.__ModuleLoader__.load({
6861
8058
  children: [
6862
8059
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
6863
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
+ },
6864
8095
  children: [
6865
8096
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6866
- className: "sm-avatar sm-avatar-alter sm-avatar-lg",
6867
- "aria-hidden": true,
6868
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SoulMirrorIcon, { size: 18 })
8097
+ style: { opacity: .7 },
8098
+ children: t("alter.wallet")
6869
8099
  }),
6870
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6871
8101
  style: {
8102
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
6872
8103
  flex: 1,
6873
8104
  minWidth: 0,
6874
- display: "grid"
8105
+ overflow: "hidden",
8106
+ textOverflow: "ellipsis",
8107
+ whiteSpace: "nowrap"
6875
8108
  },
6876
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6877
- className: "sm-chat-head-name",
6878
- children: t("alter.me")
6879
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6880
- className: "sm-chat-head-sub",
6881
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6882
- className: `sm-livedot${running ? " sm-busy" : ""}`,
6883
- "aria-hidden": true
6884
- }), alter.sessionId === void 0 ? t("alter.status.noSession") : running ? t("alter.status.running") : t("alter.status.idle")]
6885
- })]
8109
+ title: wallet.address,
8110
+ "data-soulmirror-alter-wallet-address": true,
8111
+ children: wallet.address
6886
8112
  }),
6887
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6888
- className: "sm-chat-head-actions",
6889
- children: alter.sessionId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6890
- label: t("alter.openDsh.hint"),
6891
- side: "bottom",
6892
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6893
- type: "button",
6894
- className: "sm-ghostbtn",
6895
- onClick: () => {
6896
- onOpenSession(alter.sessionId);
6897
- },
6898
- "data-soulmirror-alter-open-dsh": true,
6899
- children: [
6900
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline14, { size: 14 }),
6901
- " ",
6902
- t("alter.openDsh")
6903
- ]
6904
- })
6905
- }) : null
6906
- })
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}` : ""
8119
+ }),
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
6907
8128
  ]
6908
- }),
8129
+ }) : null,
6909
8130
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContentTabs, {
6910
8131
  tabs,
6911
8132
  active: paneTab,
6912
8133
  onChange: pageStore.setPaneTab,
6913
8134
  t
6914
8135
  }),
6915
- 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: [
6916
8141
  drafts.length > 0 && firstDraft !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6917
8142
  className: "sm-pendbar",
6918
8143
  "data-soulmirror-alter-pendbar": drafts.length,
@@ -6969,7 +8194,8 @@ window.__ModuleLoader__.load({
6969
8194
  draft: d,
6970
8195
  t,
6971
8196
  showTarget: true,
6972
- onGoFriend
8197
+ onGoFriend,
8198
+ onGoGroup
6973
8199
  }, d.id)),
6974
8200
  running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6975
8201
  className: "sm-citem sm-alter",
@@ -7032,6 +8258,36 @@ window.__ModuleLoader__.load({
7032
8258
  });
7033
8259
  }
7034
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
7035
8291
  //#region src/client/group-templates.ts
7036
8292
  const base = (id, patch) => ({
7037
8293
  id,
@@ -7103,7 +8359,7 @@ window.__ModuleLoader__.load({
7103
8359
  * The dialog owns the form state; the caller owns the actual creation
7104
8360
  * (`onCreate`) and the busy flag.
7105
8361
  */
7106
- function GroupCreateDialog({ t, friends, busy, onCreate, onClose }) {
8362
+ function GroupCreateDialog({ t, friends, busy, walletAddress, onCreate, onClose }) {
7107
8363
  const std = GROUP_TEMPLATES[0].profile;
7108
8364
  const [name, setName] = (0, react.useState)("");
7109
8365
  const [sel, setSel] = (0, react.useState)({});
@@ -7114,6 +8370,7 @@ window.__ModuleLoader__.load({
7114
8370
  speakAgents: std.speakAgents,
7115
8371
  speakWho: std.speakWho ?? "all",
7116
8372
  join: std.join ?? "invite",
8373
+ joinPrice: "",
7117
8374
  agentWake: std.agentWake ?? "mention",
7118
8375
  agentTier: std.agentTier ?? "draft",
7119
8376
  rules: "",
@@ -7130,10 +8387,12 @@ window.__ModuleLoader__.load({
7130
8387
  };
7131
8388
  }, [onClose]);
7132
8389
  const members = Object.entries(sel).filter(([, v]) => v).map(([k]) => k);
7133
- 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;
7134
8392
  const submit = () => {
7135
8393
  if (!ready) return;
7136
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() !== "";
7137
8396
  const profile = templateProfile(tpl, {
7138
8397
  speakHumans: adv.speakHumans,
7139
8398
  speakAgents: adv.speakAgents,
@@ -7142,6 +8401,10 @@ window.__ModuleLoader__.load({
7142
8401
  agentWake: adv.agentWake,
7143
8402
  agentTier: adv.agentTier,
7144
8403
  ...adv.rules.trim() === "" ? {} : { rules: adv.rules },
8404
+ ...paid ? {
8405
+ joinPrice: adv.joinPrice.trim(),
8406
+ joinAddr: walletAddress
8407
+ } : {},
7145
8408
  ...adv.isPublic ? { public: true } : {},
7146
8409
  ...tags.length === 0 ? {} : { tags }
7147
8410
  });
@@ -7305,31 +8568,92 @@ window.__ModuleLoader__.load({
7305
8568
  }),
7306
8569
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7307
8570
  className: "sm-field",
7308
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.join") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7309
- className: "sm-select",
7310
- value: adv.join,
7311
- onChange: (e) => {
7312
- setAdv({
7313
- ...adv,
7314
- join: e.target.value
7315
- });
7316
- },
7317
- children: [
7318
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7319
- value: "invite",
7320
- children: t("group.form.join.invite")
7321
- }),
7322
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7323
- value: "apply",
7324
- children: t("group.form.join.apply")
7325
- }),
7326
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7327
- value: "open",
7328
- children: t("group.form.join.open")
7329
- })
7330
- ]
7331
- })]
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
+ ]
7332
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,
7333
8657
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7334
8658
  className: "sm-field",
7335
8659
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.wake") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
@@ -7464,6 +8788,10 @@ window.__ModuleLoader__.load({
7464
8788
  * (card URI + note) at the bottom. The selected row is highlighted; clicking
7465
8789
  * a friend row selects it (the friend pane marks it read).
7466
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;
7467
8795
  function useCopy() {
7468
8796
  const [copied, setCopied] = (0, react.useState)(false);
7469
8797
  return [copied, (0, react.useCallback)((text) => {
@@ -7603,7 +8931,77 @@ window.__ModuleLoader__.load({
7603
8931
  })]
7604
8932
  }) });
7605
8933
  }
7606
- 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 }) {
7607
9005
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
7608
9006
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
7609
9007
  const { inbox } = net;
@@ -7617,10 +9015,26 @@ window.__ModuleLoader__.load({
7617
9015
  const [note, setNote] = (0, react.useState)(void 0);
7618
9016
  const [protocolOpen, setProtocolOpen] = (0, react.useState)(false);
7619
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]);
7620
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);
7621
9032
  /** The WeChat-style "+" menu and the small dialog it opens. */
7622
9033
  const [plusOpen, setPlusOpen] = (0, react.useState)(false);
7623
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);
7624
9038
  const [dialog, setDialog] = (0, react.useState)(void 0);
7625
9039
  (0, react.useEffect)(() => {
7626
9040
  if (query.trim() === "") return;
@@ -7661,8 +9075,15 @@ window.__ModuleLoader__.load({
7661
9075
  const nb = b.kind === "friend" ? b.f.name : b.g.name;
7662
9076
  return na.localeCompare(nb);
7663
9077
  });
7664
- const friendRows = rows.filter((r) => r.kind === "friend");
7665
- 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);
7666
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));
7667
9088
  const contentHits = q === "" ? [] : (() => {
7668
9089
  const out = [];
@@ -7729,11 +9150,65 @@ window.__ModuleLoader__.load({
7729
9150
  const submitJoin = () => {
7730
9151
  if (busy !== void 0 || joinUri.trim() === "" || identity === null) return;
7731
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
+ }
7732
9206
  const { gid } = await api.groupApply(joinUri.trim());
7733
9207
  setJoinUri("");
9208
+ setJoinCard(void 0);
7734
9209
  setDialog(void 0);
7735
9210
  if (gid !== "") {
7736
- pageStore.setCol2Tab("groups");
9211
+ pageStore.setCol2Tab("messages");
7737
9212
  onSelect(groupKey(gid));
7738
9213
  }
7739
9214
  return t("group.join.applied");
@@ -8029,7 +9504,7 @@ window.__ModuleLoader__.load({
8029
9504
  role: "menuitem",
8030
9505
  onClick: () => {
8031
9506
  setPlusOpen(false);
8032
- pageStore.setCol2Tab("groups");
9507
+ pageStore.setCol2Tab("messages");
8033
9508
  setCreateOpen(true);
8034
9509
  },
8035
9510
  "data-soulmirror-plus-new-group": true,
@@ -8051,6 +9526,7 @@ window.__ModuleLoader__.load({
8051
9526
  onClick: () => {
8052
9527
  setPlusOpen(false);
8053
9528
  setDialog("join");
9529
+ loadWallet();
8054
9530
  },
8055
9531
  "data-soulmirror-plus-join-group": true,
8056
9532
  children: t("foot.joinGroup")
@@ -8063,11 +9539,7 @@ window.__ModuleLoader__.load({
8063
9539
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8064
9540
  className: "sm-col2-tabs",
8065
9541
  "data-soulmirror-col2-tabs": true,
8066
- children: [
8067
- "contacts",
8068
- "agents",
8069
- "groups"
8070
- ].map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9542
+ children: ["messages", "contacts"].map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8071
9543
  type: "button",
8072
9544
  className: `sm-col2-tab${col2Tab === tab ? " sm-active" : ""}`,
8073
9545
  onClick: () => {
@@ -8082,7 +9554,7 @@ window.__ModuleLoader__.load({
8082
9554
  className: "sm-list-body",
8083
9555
  children: [
8084
9556
  protocolOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProtocolEditor, { t }) : null,
8085
- 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: [
8086
9558
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8087
9559
  className: "sm-section",
8088
9560
  children: t("col2.contacts.section")
@@ -8132,9 +9604,137 @@ window.__ModuleLoader__.load({
8132
9604
  className: "sm-row-preview",
8133
9605
  children: alterRunning ? t("alter.status.running") : drafts > 0 ? t("alter.row.drafts", { n: drafts }) : t("alter.row.hint")
8134
9606
  })]
8135
- })]
8136
- }) })
9607
+ })]
9608
+ }) })
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")
8137
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: [
8138
9738
  inbox.pending.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8139
9739
  className: "sm-section",
8140
9740
  children: [
@@ -8214,9 +9814,9 @@ window.__ModuleLoader__.load({
8214
9814
  })] }) : null,
8215
9815
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8216
9816
  className: "sm-section",
8217
- children: t("chats.section")
9817
+ children: t("inbox.friends")
8218
9818
  }),
8219
- friendRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9819
+ contactRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8220
9820
  className: "sm-muted",
8221
9821
  style: {
8222
9822
  margin: 0,
@@ -8224,49 +9824,37 @@ window.__ModuleLoader__.load({
8224
9824
  fontSize: 12
8225
9825
  },
8226
9826
  "data-soulmirror-page-empty-list": true,
8227
- children: identity === null ? t("page.empty.noFriends.noIdentity") : q !== "" ? t("page.empty.noMatch", { query }) : t("chats.empty")
8228
- }) : /* @__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", {
8229
9829
  style: {
8230
9830
  listStyle: "none",
8231
9831
  margin: 0,
8232
9832
  padding: 0
8233
9833
  },
8234
- "data-soulmirror-page-friends": true,
8235
- "data-soulmirror-page-chats": true,
8236
- children: friendRows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatRow, {
8237
- row: r,
8238
- typing: inbox.typing[r.f.fp] === true || r.f.typing === true,
8239
- apps: 0,
8240
- 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,
8241
9838
  t,
8242
9839
  onSelect: () => {
8243
- onSelect(r.rowKey);
9840
+ onSelectContact(f.fp);
8244
9841
  }
8245
- }, r.rowKey))
8246
- })
8247
- ] }),
8248
- col2Tab === "agents" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8249
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8250
- className: "sm-section",
8251
- style: {
8252
- display: "flex",
8253
- alignItems: "center",
8254
- 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);
8255
9849
  },
8256
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8257
- style: { flex: 1 },
8258
- children: t("agents.section")
8259
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8260
- type: "button",
8261
- className: "sm-linkbtn",
8262
- onClick: () => {
8263
- setAgentSheetOpen(true);
8264
- },
8265
- "data-soulmirror-agents-add": true,
8266
- children: ["+ ", t("settings.agents.add")]
8267
- })]
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")
8268
9856
  }),
8269
- (net.state?.agents?.length ?? 0) === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9857
+ groupContactRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8270
9858
  className: "sm-muted",
8271
9859
  style: {
8272
9860
  margin: 0,
@@ -8274,95 +9862,33 @@ window.__ModuleLoader__.load({
8274
9862
  fontSize: 12
8275
9863
  },
8276
9864
  "data-soulmirror-page-empty-list": true,
8277
- children: t("col2.agents.empty")
8278
- }) : /* @__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", {
8279
9867
  style: {
8280
9868
  listStyle: "none",
8281
9869
  margin: 0,
8282
9870
  padding: 0
8283
9871
  },
8284
- children: (net.state?.agents ?? []).map((a) => {
8285
- const key = agentKey(a.name);
8286
- const agentSelected = selected === key;
8287
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8288
- type: "button",
8289
- className: `sm-row${agentSelected ? " sm-selected" : ""}`,
8290
- onClick: () => {
8291
- onSelect(key);
8292
- },
8293
- "aria-pressed": agentSelected,
8294
- "data-soulmirror-page-agent": a.name,
8295
- "data-soulmirror-selected": agentSelected ? "true" : void 0,
8296
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8297
- className: "sm-avawrap",
8298
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8299
- className: "sm-avatar",
8300
- "aria-hidden": true,
8301
- children: "🤖"
8302
- })
8303
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8304
- className: "sm-row-body",
8305
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8306
- className: "sm-row-title",
8307
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8308
- className: "sm-row-name",
8309
- children: [
8310
- a.name,
8311
- " ",
8312
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8313
- className: `sm-livedot${a.status === "running" ? " sm-busy" : ""}`,
8314
- "aria-hidden": true
8315
- })
8316
- ]
8317
- })
8318
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8319
- className: "sm-row-preview",
8320
- children: a.status === "running" ? t("settings.agents.status.running") : a.cwd ?? t("settings.agents.status.idle")
8321
- })]
8322
- })]
8323
- }) }, a.name);
8324
- })
8325
- }),
8326
- agentSheetOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentSettingsSheet, {
8327
- t,
8328
- onClose: () => {
8329
- 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);
8330
9887
  },
8331
- onSaved: (name) => {
8332
- onSelect(agentKey(name));
8333
- }
8334
- }) : null
8335
- ] }),
8336
- col2Tab === "groups" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8337
- className: "sm-section",
8338
- children: t("groups.section")
8339
- }), groupRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8340
- className: "sm-muted",
8341
- style: {
8342
- margin: 0,
8343
- padding: "4px 10px 10px",
8344
- fontSize: 12
8345
- },
8346
- "data-soulmirror-page-empty-list": true,
8347
- children: q !== "" ? t("page.empty.noMatch", { query }) : t("col2.groups.empty")
8348
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8349
- style: {
8350
- listStyle: "none",
8351
- margin: 0,
8352
- padding: 0
8353
- },
8354
- "data-soulmirror-page-groups": true,
8355
- children: groupRows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatRow, {
8356
- row: r,
8357
- typing: false,
8358
- apps: inbox.groupApps[r.g.gid] ?? 0,
8359
- selected: selected === r.rowKey,
8360
- t,
8361
- onSelect: () => {
8362
- onSelect(r.rowKey);
8363
- }
8364
- }, r.rowKey))
8365
- })] })
9888
+ "data-soulmirror-contacts-groups-expand": true,
9889
+ children: groupsExpandedNow ? t("agents.collapse") : t("agents.expand", { n: groupContactRows.length - CONTACT_COLLAPSE })
9890
+ }) : null] })
9891
+ ] })
8366
9892
  ]
8367
9893
  }),
8368
9894
  note !== void 0 || net.error !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -8386,6 +9912,7 @@ window.__ModuleLoader__.load({
8386
9912
  t,
8387
9913
  friends: inbox.friends,
8388
9914
  busy: busy !== void 0,
9915
+ ...walletAddress === void 0 ? {} : { walletAddress },
8389
9916
  onCreate: createGroup,
8390
9917
  onClose: () => {
8391
9918
  setCreateOpen(false);
@@ -8506,6 +10033,8 @@ window.__ModuleLoader__.load({
8506
10033
  autoFocus: true,
8507
10034
  onChange: (e) => {
8508
10035
  setJoinUri(e.target.value);
10036
+ setJoinCard(void 0);
10037
+ setJoinTx("");
8509
10038
  },
8510
10039
  onKeyDown: (e) => {
8511
10040
  if (e.key === "Enter" && !e.nativeEvent.isComposing) submitJoin();
@@ -8513,22 +10042,122 @@ window.__ModuleLoader__.load({
8513
10042
  "data-soulmirror-group-join-uri": true
8514
10043
  })]
8515
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,
8516
10143
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8517
10144
  className: "sm-modal-foot",
8518
10145
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
8519
10146
  variant: "outline",
8520
10147
  size: "sm",
8521
10148
  onClick: () => {
8522
- setDialog(void 0);
10149
+ if (payConfirming) setPayConfirming(false);
10150
+ else setDialog(void 0);
8523
10151
  },
8524
- children: t("inbox.close")
10152
+ "data-soulmirror-group-join-back": true,
10153
+ children: payConfirming ? t("group.home.edit.cancel") : t("inbox.close")
8525
10154
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
8526
- variant: "outline",
10155
+ variant: "primary",
8527
10156
  size: "sm",
8528
- disabled: busy !== void 0 || joinUri.trim() === "",
10157
+ disabled: busy !== void 0 || joinUri.trim() === "" || joinCard?.price !== void 0 && walletAddress === void 0 && joinTx.trim() === "",
8529
10158
  onClick: submitJoin,
8530
10159
  "data-soulmirror-group-join-apply": true,
8531
- 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")
8532
10161
  })]
8533
10162
  })
8534
10163
  ]
@@ -8899,7 +10528,7 @@ window.__ModuleLoader__.load({
8899
10528
  variant: "primary",
8900
10529
  size: "sm",
8901
10530
  onClick: onGoAlter,
8902
- children: t("friend.actbar.goAlter")
10531
+ children: t("friend.home.sendMessage")
8903
10532
  })
8904
10533
  ]
8905
10534
  })]
@@ -8910,6 +10539,7 @@ window.__ModuleLoader__.load({
8910
10539
  tier: friend.tier,
8911
10540
  tierExplicit: friend.tierExplicit === true,
8912
10541
  protocol: friend.protocol,
10542
+ muted: friend.muted === true,
8913
10543
  defaultTier: alterConfig?.defaultTier ?? "draft",
8914
10544
  perHour: alterConfig?.autoReplyPerHour ?? 20,
8915
10545
  t,
@@ -8984,7 +10614,17 @@ window.__ModuleLoader__.load({
8984
10614
  onChange: pageStore.setPaneTab,
8985
10615
  t
8986
10616
  }),
8987
- 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: [
8988
10628
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8989
10629
  className: "sm-banner",
8990
10630
  role: "note",
@@ -9159,6 +10799,7 @@ window.__ModuleLoader__.load({
9159
10799
  tier: friend.tier,
9160
10800
  tierExplicit: friend.tierExplicit === true,
9161
10801
  protocol: friend.protocol,
10802
+ muted: friend.muted === true,
9162
10803
  defaultTier: alterConfig?.defaultTier ?? "draft",
9163
10804
  perHour: alterConfig?.autoReplyPerHour ?? 20,
9164
10805
  t,
@@ -9321,6 +10962,8 @@ window.__ModuleLoader__.load({
9321
10962
  */
9322
10963
  /** Roster info survives pane switches, so revisiting a group paints names instantly. */
9323
10964
  const infoCache = /* @__PURE__ */ new Map();
10965
+ /** 很久没来群(未读 >= 此阈值)进群时,触发一次群记忆提炼(避免每条消息都总结)。 */
10966
+ const GROUP_MEMORY_MIN_UNREAD = 20;
9324
10967
  const templateLabel = (t, id) => {
9325
10968
  switch (id) {
9326
10969
  case "standard": return t("template.standard");
@@ -9331,7 +10974,7 @@ window.__ModuleLoader__.load({
9331
10974
  default: return id ?? "";
9332
10975
  }
9333
10976
  };
9334
- 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");
9335
10978
  const wakeLabel = (t, wake) => wake === "always" ? t("group.form.wake.always") : wake === "never" ? t("group.form.wake.never") : t("group.form.wake.mention");
9336
10979
  const tierLabel = (t, tier) => tier === "notify" ? t("tier.short.notify") : tier === "auto" ? t("tier.short.auto") : t("tier.short.draft");
9337
10980
  const whoLabel = (t, who) => who === "owner" ? t("group.form.speakWho.owner") : who === "admins" ? t("group.form.speakWho.admins") : t("group.form.speakWho.all");
@@ -9349,6 +10992,12 @@ window.__ModuleLoader__.load({
9349
10992
  const [inviteFp, setInviteFp] = (0, react.useState)("");
9350
10993
  const [copied, setCopied] = (0, react.useState)(false);
9351
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
+ }, []);
9352
11001
  const [edit, setEdit] = (0, react.useState)({
9353
11002
  speakHumans: true,
9354
11003
  speakAgents: true,
@@ -9356,6 +11005,7 @@ window.__ModuleLoader__.load({
9356
11005
  join: "invite",
9357
11006
  agentWake: "mention",
9358
11007
  agentTier: "draft",
11008
+ joinPrice: "",
9359
11009
  rules: "",
9360
11010
  isPublic: false,
9361
11011
  tags: ""
@@ -9416,6 +11066,10 @@ window.__ModuleLoader__.load({
9416
11066
  visible,
9417
11067
  thread.entries.length
9418
11068
  ]);
11069
+ (0, react.useEffect)(() => {
11070
+ if (group.unread < GROUP_MEMORY_MIN_UNREAD) return;
11071
+ api.memorySummarize(gid).catch(() => {});
11072
+ }, [gid]);
9419
11073
  const run = async (key, action) => {
9420
11074
  setBusy(key);
9421
11075
  setError(void 0);
@@ -9478,7 +11132,8 @@ window.__ModuleLoader__.load({
9478
11132
  speakHumans: profile?.speakHumans ?? true,
9479
11133
  speakAgents: profile?.speakAgents ?? true,
9480
11134
  speakWho: profile?.speakWho ?? "all",
9481
- 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 ?? "",
9482
11137
  agentWake: profile?.agentWake ?? "mention",
9483
11138
  agentTier: profile?.agentTier ?? "draft",
9484
11139
  rules: profile?.rules ?? "",
@@ -9489,6 +11144,14 @@ window.__ModuleLoader__.load({
9489
11144
  };
9490
11145
  const saveProfile = () => {
9491
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
+ }
9492
11155
  run("profile.save", async () => {
9493
11156
  await api.groupSetProfile(gid, {
9494
11157
  ...profile ?? {},
@@ -9499,6 +11162,10 @@ window.__ModuleLoader__.load({
9499
11162
  agentWake: edit.agentWake,
9500
11163
  agentTier: edit.agentTier,
9501
11164
  rules: edit.rules,
11165
+ ...edit.join === "paid" && edit.joinPrice.trim() !== "" && walletAddress !== void 0 ? {
11166
+ joinPrice: edit.joinPrice.trim(),
11167
+ joinAddr: walletAddress
11168
+ } : {},
9502
11169
  public: edit.isPublic,
9503
11170
  tags
9504
11171
  });
@@ -9518,7 +11185,7 @@ window.__ModuleLoader__.load({
9518
11185
  };
9519
11186
  const chips = profile === void 0 ? [] : [
9520
11187
  templateLabel(t, profile.template),
9521
- joinLabel(t, profile.join),
11188
+ joinLabel(t, profile.join === "paid" ? "paid" : profile.join, profile.join === "paid" ? true : paidJoinFromRules(profile.rules) !== void 0),
9522
11189
  whoLabel(t, profile.speakWho),
9523
11190
  `${t("group.form.wake")} · ${wakeLabel(t, profile.agentWake)}`,
9524
11191
  `${t("group.form.tier")} · ${tierLabel(t, profile.agentTier)}`,
@@ -9592,7 +11259,7 @@ window.__ModuleLoader__.load({
9592
11259
  "data-soulmirror-group-profile-edit": true,
9593
11260
  children: t("group.home.edit")
9594
11261
  }) : null,
9595
- profile?.public === true && relay !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11262
+ relay !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9596
11263
  type: "button",
9597
11264
  className: "sm-ghostbtn",
9598
11265
  onClick: copyUri,
@@ -9669,31 +11336,71 @@ window.__ModuleLoader__.load({
9669
11336
  }),
9670
11337
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9671
11338
  className: "sm-field",
9672
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.join") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
9673
- className: "sm-select",
9674
- value: edit.join,
9675
- onChange: (e) => {
9676
- setEdit({
9677
- ...edit,
9678
- join: e.target.value
9679
- });
9680
- },
9681
- children: [
9682
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9683
- value: "invite",
9684
- children: t("group.form.join.invite")
9685
- }),
9686
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9687
- value: "apply",
9688
- children: t("group.form.join.apply")
9689
- }),
9690
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9691
- value: "open",
9692
- children: t("group.form.join.open")
9693
- })
9694
- ]
9695
- })]
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
+ ]
9696
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,
9697
11404
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9698
11405
  className: "sm-field",
9699
11406
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("group.form.wake") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
@@ -9827,10 +11534,10 @@ window.__ModuleLoader__.load({
9827
11534
  className: "sm-statepill",
9828
11535
  children: c
9829
11536
  }, `${c}-${i}`))
9830
- }), 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", {
9831
11538
  className: "sm-home-rules",
9832
11539
  "data-soulmirror-group-rules": true,
9833
- children: profile.rules
11540
+ children: rulesWithoutPaidJoin(profile.rules)
9834
11541
  }) : null] })]
9835
11542
  }),
9836
11543
  pins.length > 0 || canAdmin ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -10555,7 +12262,7 @@ window.__ModuleLoader__.load({
10555
12262
  className: "sm-home-card",
10556
12263
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10557
12264
  className: "sm-home-title",
10558
- 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", {
10559
12266
  type: "button",
10560
12267
  className: "sm-ghostbtn",
10561
12268
  onClick: copyUri,
@@ -10886,7 +12593,17 @@ window.__ModuleLoader__.load({
10886
12593
  ...roomOwner,
10887
12594
  t
10888
12595
  })] })
10889
- }) : 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
10890
12607
  ]
10891
12608
  });
10892
12609
  }
@@ -10931,7 +12648,7 @@ window.__ModuleLoader__.load({
10931
12648
  node = parent;
10932
12649
  }
10933
12650
  }
10934
- function SoulmirrorPage({ openSession, scope, t, renderSlot }) {
12651
+ function SoulmirrorPage({ scope, t, renderSlot }) {
10935
12652
  const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.getSnapshot);
10936
12653
  const net = (0, react.useSyncExternalStore)(networkStore.subscribe, networkStore.getSnapshot);
10937
12654
  const subscribeScope = (0, react.useCallback)((listener) => scope.subscribe(listener), [scope]);
@@ -10992,6 +12709,10 @@ window.__ModuleLoader__.load({
10992
12709
  if (!target.isConnected) return;
10993
12710
  if (rootEl.contains(target)) return;
10994
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;
10995
12716
  pageStore.close();
10996
12717
  };
10997
12718
  document.addEventListener("mousedown", onDown);
@@ -11027,9 +12748,12 @@ window.__ModuleLoader__.load({
11027
12748
  const goFriend = (fp) => {
11028
12749
  pageStore.select(fp);
11029
12750
  };
11030
- const openAlterSession = (sessionId) => {
11031
- openSession(sessionId);
11032
- pageStore.close();
12751
+ const goGroup = (gid) => {
12752
+ pageStore.select(groupKey(gid));
12753
+ };
12754
+ const openContact = (fp) => {
12755
+ pageStore.select(fp);
12756
+ pageStore.setPaneTab("home");
11033
12757
  };
11034
12758
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11035
12759
  ref: root,
@@ -11045,6 +12769,7 @@ window.__ModuleLoader__.load({
11045
12769
  onSelect: (key) => {
11046
12770
  pageStore.select(key);
11047
12771
  },
12772
+ onSelectContact: openContact,
11048
12773
  onAccepted: (fp) => {
11049
12774
  pageStore.select(fp);
11050
12775
  },
@@ -11066,12 +12791,11 @@ window.__ModuleLoader__.load({
11066
12791
  }) : seatAgent !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentPane, {
11067
12792
  t,
11068
12793
  agent: seatAgent,
11069
- onOpenSession: openAlterSession,
11070
12794
  onRemoved: goAlter
11071
12795
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlterPane, {
11072
12796
  t,
11073
- onOpenSession: openAlterSession,
11074
12797
  onGoFriend: goFriend,
12798
+ onGoGroup: goGroup,
11075
12799
  renderCards: renderSlot,
11076
12800
  scope
11077
12801
  })]
@@ -11162,6 +12886,18 @@ window.__ModuleLoader__.load({
11162
12886
  */
11163
12887
  const STYLE_ID = "soulmirror-styles";
11164
12888
  const CSS = `
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
+
11165
12901
  /* Icon-only update button at the right end of the SoulMirror foot row:
11166
12902
  invisible until a release is known, small circled arrow, tooltip carries
11167
12903
  the words. Sits beside the entry (the row is a flex line). */
@@ -11332,7 +13068,7 @@ body[data-ds-dark-theme] { color-scheme: dark; }
11332
13068
  .sm-pendbar b { color: var(--dsw-alias-state-warn-primary); }
11333
13069
  .sm-pendbar .sm-linkbtn { margin-left: auto; }
11334
13070
  .sm-thread { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 20px; display: flex; flex-direction: column; }
11335
- .sm-thread-inner { margin-top: auto; display: flex; flex-direction: column; gap: 4px; }
13071
+ .sm-thread-inner { display: flex; flex-direction: column; gap: 4px; }
11336
13072
  .sm-day { align-self: center; margin: 10px 0 6px; padding: 1px 10px; border-radius: 10px; font-size: 11px;
11337
13073
  color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-interactive-bg-hover); }
11338
13074
  .sm-msg { display: flex; flex-direction: column; max-width: 72%; }
@@ -11576,7 +13312,8 @@ body[data-ds-dark-theme] .sm-page-root {
11576
13312
  "sessions",
11577
13313
  "settingsScope",
11578
13314
  "commandUi",
11579
- "theme"
13315
+ "theme",
13316
+ "workspaces"
11580
13317
  ];
11581
13318
  /** Whether a session cwd is the SoulMirror workspace (`<home>/a2a`, e.g. `~/.soulnet/a2a`). */
11582
13319
  function isSoulmirrorCwd(cwd) {
@@ -11590,14 +13327,12 @@ body[data-ds-dark-theme] .sm-page-root {
11590
13327
  en
11591
13328
  }), "soulmirror: dictionaries");
11592
13329
  const t = ctx.locale.bind(NS);
13330
+ setDirectoryPicker(() => ctx.workspaces.pickDirectory());
11593
13331
  ctx.effect(() => {
11594
13332
  ensureStyles();
11595
13333
  return removeStyles;
11596
13334
  }, "soulmirror: inbox styles");
11597
13335
  installBranding(ctx);
11598
- const openSession = (id) => {
11599
- ctx.sessions.open(id);
11600
- };
11601
13336
  ctx.conversationEvents.register(a2aRelayDefinition);
11602
13337
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
11603
13338
  name: "conversation.chat.node",
@@ -11630,10 +13365,7 @@ body[data-ds-dark-theme] .sm-page-root {
11630
13365
  };
11631
13366
  });
11632
13367
  const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
11633
- const pageInjected = () => ({
11634
- openSession,
11635
- scope
11636
- });
13368
+ const pageInjected = () => ({ scope });
11637
13369
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
11638
13370
  name: "shell.overlay",
11639
13371
  id: "soulmirror-page",
@@ -11681,10 +13413,7 @@ body[data-ds-dark-theme] .sm-page-root {
11681
13413
  clearInterval(timer);
11682
13414
  };
11683
13415
  });
11684
- const settingsInjected = () => ({
11685
- openSession,
11686
- scope
11687
- });
13416
+ const settingsInjected = () => ({ scope });
11688
13417
  ctx.slots.inject("settings.section", () => {
11689
13418
  const make = () => ctx.slots.register({
11690
13419
  name: "settings.section",