sage-mem 0.9.10 → 0.9.11

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 (2) hide show
  1. package/lib/client.js +32 -4
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -2579,6 +2579,30 @@ const StarmapRuntime = (function (require, _react) {
2579
2579
  ? h("button", { type: "button", className: "smem-btn", key: "close", onClick: props.onClose }, "收起")
2580
2580
  : null);
2581
2581
 
2582
+ /**
2583
+ * 稳定的 remote 引用。
2584
+ *
2585
+ * 起因(issue #3,2026-10-10 外部报告):面板在**渲染期**取 `ctx.get("remote.x")`,
2586
+ * 又把结果放进 `useEffect` 的依赖数组 —— 而真实宿主里 `ctx.get()` 每次返回一个
2587
+ * **新的包装对象**,于是
2588
+ * 渲染 → effect 重跑 → 6 个 RPC + setState → 再渲染 → …
2589
+ * 滚成无限请求风暴(报告者实测每秒上千次;本机观察到的形态是「组件一直在 setState」)。
2590
+ *
2591
+ * 为什么本机测试一直没抓到:mock 的 `ctx.get` 每次返回**同一个**对象 —— 假的安全。
2592
+ * 测试里现在有 `unstableRemoteRefs` 开关来复现真实行为。
2593
+ *
2594
+ * 同一个 ctx 永远拿到同一个 remote。用 WeakMap 而不是普通 Map:ctx 是长生命周期对象,
2595
+ * WeakMap 不会因为这张缓存把它拖住。
2596
+ */
2597
+ const REMOTE_CACHE = new WeakMap();
2598
+ function stableRemote(ctx, name) {
2599
+ if (!ctx || typeof ctx.get !== "function") return null;
2600
+ let byName = REMOTE_CACHE.get(ctx);
2601
+ if (!byName) { byName = new Map(); REMOTE_CACHE.set(ctx, byName); }
2602
+ if (!byName.has(name)) byName.set(name, ctx.get(name) || null);
2603
+ return byName.get(name);
2604
+ }
2605
+
2582
2606
  /**
2583
2607
  * 「体检」结果面板。
2584
2608
  *
@@ -2818,9 +2842,11 @@ const StarmapRuntime = (function (require, _react) {
2818
2842
  * (比如「记忆 + 会话记录」会把私密对话送进模型),默认全是最保守的那一档。
2819
2843
  */
2820
2844
  function AutodreamPanel(props) {
2821
- const remote = props.ctx && props.ctx.get ? props.ctx.get("remote.autodream") : null;
2845
+ // ⚠️ 必须走 stableRemote:真实宿主里 `ctx.get()` 每次返回**新对象**,
2846
+ // 而下面那个 effect 依赖 remote —— 直接用会让 effect 无限重跑(issue #3)。
2847
+ const remote = stableRemote(props.ctx, "remote.autodream");
2822
2848
  // 注入设置走 memory 那半边(autodream 只管整理那趟)
2823
- const memRemote = props.ctx && props.ctx.get ? props.ctx.get("remote.memory") : null;
2849
+ const memRemote = stableRemote(props.ctx, "remote.memory");
2824
2850
  const sCfg = react.useState(null); const cfg = sCfg[0], setCfg = sCfg[1];
2825
2851
  const sSt = react.useState(null); const st = sSt[0], setSt = sSt[1];
2826
2852
  const sErr = react.useState(null); const err = sErr[0], setErr = sErr[1];
@@ -3483,7 +3509,7 @@ const StarmapRuntime = (function (require, _react) {
3483
3509
  * 保存后**一定回读**(`setConfig` 对非法值是「忽略并保留原值」,只信返回值会骗人)。
3484
3510
  */
3485
3511
  function ArchiveSettingsPanel(props) {
3486
- const remote = props.ctx && props.ctx.get ? props.ctx.get("remote.autodream") : null;
3512
+ const remote = stableRemote(props.ctx, "remote.autodream");
3487
3513
  const sCfg = react.useState(null); const cfg = sCfg[0], setCfg = sCfg[1];
3488
3514
  const sDraft = react.useState(null); const draft = sDraft[0], setDraft = sDraft[1];
3489
3515
  const sErr = react.useState(null); const err = sErr[0], setErr = sErr[1];
@@ -3644,7 +3670,9 @@ const StarmapRuntime = (function (require, _react) {
3644
3670
  ]);
3645
3671
  }
3646
3672
  function Section(props) {
3647
- const remote = props.ctx.get("remote.memory");
3673
+ // 走 stableRemote:真实宿主里 `ctx.get()` 每次返回新对象(issue #3 的成因)。
3674
+ // 这个面板的 effect 依赖是 `[]`、暂时不会爆,但引用稳定是**所有**面板的共同前提。
3675
+ const remote = stableRemote(props.ctx, "remote.memory");
3648
3676
  const sItems = react.useState([]);
3649
3677
  const items = sItems[0], setItems = sItems[1];
3650
3678
  const sLoading = react.useState(false);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sage-mem",
3
3
  "description": "File-based memory plugin for DeepSeek Harness (DSH) — cross-session memory in plain markdown files, with a memory star-map view, type/tag filtering, retrieval aliases, and an optional autodream pass that consolidates memories on demand or on a schedule — with per-run rollback points, a per-file change declaration (what changed and why), and a separately selectable model for the consolidation pass",
4
- "version": "0.9.10",
4
+ "version": "0.9.11",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {