dsh-prime-memory 0.20.0-beta.7 → 0.20.0-beta.9

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.
package/CHANGELOG.md CHANGED
@@ -10,12 +10,27 @@
10
10
 
11
11
  ### 新增
12
12
 
13
- - **长任务模式(`longTask.*`,默认关)——「长跑会话」的三件套:上下文计量、任务参考、尾部压缩,以及一个非阻断的浮动建议面板。**
13
+ - **长任务模式(`longTask.*`,默认关)——「长跑会话」的三件套:上下文计量、任务参考、尾部压缩。**
14
14
  - **上下文使用量(`store/context-usage.ts`)**:监听 `assistant/message` 官方 `usage` 的输入侧(input + cache 读/写),取**最近值**语义(每轮输入侧已含全部历史,不累加);`compaction/summary` 后归零重计。纯内存 per-session Map——重启后首条 assistant 消息即可重建,持久化只会让过期样本诈尸。
15
15
  - **todo 参考(`store/todo-ref.ts` + `hooks/long-task.ts`)**:监听宿主结构化 `todo/write` 事件(全量快照;包类型到 0.2.0-rc.1 未声明,插件以 declare module 自扩充——同 §E 的 dsh-compaction 先例)。**仅长任务会话**:快照刷进一张 pinned `todo` 槽位(`SlotStore.refreshByTitle` 受管刷新——同一主体反复写必须原地更新,否则 ≤8 槽上限里堆出关闭槽位尾迹),经 slot-recall 常驻注入——压缩把旧上下文逐出后,任务北极星仍在。相邻快照做词面 Jaccard 漂移检测(同快照重复写入不稀释信号);body ≤512 字节,截断明示。
16
16
  - **尾部 turn 压缩(`longtask-compress-tail` 端点 + `compressTailTurns`)**:把(压缩水位线, 最新已捕获轮] 内最近 N 轮立即 `enqueue(force:true)` 蒸馏入记忆——立即把尾部对话落成可检索记忆,而不是等蒸馏阈值。水位线(`SessionModeStore.lastTailCompressedTurn`)只前进,重复按压天然幂等;off 档完全隐身;无锚点老数据只在首次压缩(水位线 0)时纳入——宁少收不重蒸。
17
- - **建议状态(`longtask-hint-get`)+ 浮动球(`client/src/longtask-fab.ts`)**:ego-browser 式 vanilla overlay(FAB+面板,拖拽/位置持久化/视口夹紧),**建议不阻断**——仅当「上下文占用达 `contextThresholdPct`」或「todo 漂移达 `driftThreshold`」且长任务未开启时点亮呼吸光晕;面板内:长任务开关(临时,per-session,**不带 mode**——切开关不重置用户档位)、占用/漂移进度条、手动尾部压缩按钮(长任务开启才可用)、任务计数。轮询忙 2s/闲 5s,RPC 失败静默;座位契约不变(settings.section 恰一个,body 单例不占任何座位)。
17
+ - **建议状态(`longtask-hint-get`)以纯端点提供**:判定 = 上下文占用达 `contextThresholdPct` 或 todo 漂移达 `driftThreshold`(部署配置可调);配合 `longtask-compress-tail` 作手动压缩入口。
18
+ - **撤下浮动球 UI(`client/src/longtask-fab.ts`;beta.1 起曾随包发布)**:悬浮建议面板的归属本在 context-compression 侧(「建议启动压缩」是其领域),pm 属误载——2026-10-02 用户裁定撤下。撤下不违座位契约(settings.section 恰一个 + 输入栏 pill,body 单例不占座位),但全应用级 overlay 会与其他插件的悬浮 UI 抢占屏幕角落;长任务开关与手动压缩入口待长任务定版时落记忆设置分节。
18
19
  - **`session-mode-set` 的 `mode` 转为可选**:与 `longTask` 二选一必传——纯切长任务开关不再被迫发送档位(旧客户端恒传 mode,行为不变)。响应 `mode` 改回当前生效值。
20
+ ## [0.20.0-beta.9] — 2026-10-02
21
+
22
+ > 修复面板 RPC 全链路的 deps 漏带:`roomRegistry` 与 `longTaskDeps` 在 rpcHandler 组装 sources 时丢失,面板侧 Room 管理/长任务端点恒"未装配"(工具面直传不受影响,故 beta.4 起工具可用而面板注册一直报错)。
23
+
24
+ ### 修复
25
+
26
+ - **`registerMemoryRpc` 的 rpcHandler 漏带 `roomRegistry`/`longTask`**(beta.4 引入):HTTP 分发统一走 `buildEndpointDeps`,但 sources 只带了 7 项——`rooms-get` 不下发 registry、`room-register`/`room-admin` 抛「Room 注册表未装配(需要插件重载以初始化)」、`longtask-hint-get`/`longtask-compress-tail` 恒 disabled。现随 sources 下发;回归测试钉死 HTTP 面全链路(register → rooms-get 合并 → room-admin,rpc.test.ts)。
27
+ ## [0.20.0-beta.8] — 2026-10-02
28
+
29
+ > 撤下浮动球 UI:悬浮建议面板的归属本在 context-compression 侧(「建议启动压缩」是其领域),pm 属误载——beta.1 起随包发布的 FAB 至本版移除。
30
+
31
+ ### 移除
32
+
33
+ - **浮动球 + 建议卡片(`client/src/longtask-fab.ts`,beta.1 起随包)**:整个撤下,客户端恢复两座——`settings.section`(记忆面板)+ `conversation.input.left`(档位 pill),不再有全应用级悬浮 UI。长任务模式本体(上下文计量/todo 参考/尾部 turn 压缩)不受影响;`longtask-hint-get`/`longtask-compress-tail` 端点保留为后端面,长任务开关与手动压缩入口待定版时落记忆设置分节。
19
34
 
20
35
  ## [0.20.0-beta.7] — 2026-10-02
21
36
 
package/dist/client.js CHANGED
@@ -5645,245 +5645,6 @@ var __defProp = Object.defineProperty;
5645
5645
  ] });
5646
5646
  }
5647
5647
 
5648
- // client/src/longtask-fab.ts
5649
- var FAB_ID = "dsh-mem-longtask-fab";
5650
- var PANEL_ID = "dsh-mem-longtask-panel";
5651
- var STYLE_ID = "dsh-mem-longtask-style";
5652
- var PARASITE = "dsh-mem-parasite";
5653
- var POS_KEY = "dsh.memory.longtask.pos";
5654
- function initLongTaskFab(rpc, sessionIdOf) {
5655
- if (document.getElementById(FAB_ID) !== null) return;
5656
- const style = document.createElement("style");
5657
- style.id = STYLE_ID;
5658
- style.textContent = `
5659
- #${FAB_ID} { position: fixed; z-index: 9999; width: 42px; height: 42px; border-radius: 50%;
5660
- background: rgba(30,30,32,.72); backdrop-filter: blur(22px) saturate(180%);
5661
- border: 1px solid rgba(255,255,255,.14); box-shadow: 0 8px 24px rgba(0,0,0,.4);
5662
- color: #f5f5f7; font-size: 19px; line-height: 40px; text-align: center; cursor: grab;
5663
- touch-action: none; user-select: none; display: none; transition: transform .18s; }
5664
- #${FAB_ID}:hover { transform: scale(1.08); }
5665
- #${FAB_ID}.lit { display: block; box-shadow: 0 0 0 5px rgba(10,132,255,.22), 0 8px 24px rgba(0,0,0,.4);
5666
- animation: dsh-mem-lt-breathe 2.4s ease-in-out infinite; }
5667
- @keyframes dsh-mem-lt-breathe { 0%,100% { box-shadow: 0 0 0 4px rgba(10,132,255,.16), 0 8px 24px rgba(0,0,0,.4); }
5668
- 50% { box-shadow: 0 0 0 8px rgba(10,132,255,.30), 0 8px 24px rgba(0,0,0,.4); } }
5669
- #${PANEL_ID} { position: fixed; z-index: 9998; width: 336px; border-radius: 14px;
5670
- background: rgba(28,28,30,.78); backdrop-filter: blur(26px) saturate(180%);
5671
- border: 1px solid rgba(255,255,255,.12); box-shadow: 0 14px 44px rgba(0,0,0,.55);
5672
- color: #f5f5f7; font-size: 12.5px; opacity: 0; pointer-events: none;
5673
- transform: translateY(10px) scale(.92); transform-origin: 88% 100%;
5674
- transition: opacity .22s cubic-bezier(.16,.8,.3,1.05), transform .22s cubic-bezier(.16,.8,.3,1.1); }
5675
- #${PANEL_ID}.open { opacity: 1; pointer-events: auto; transform: none; }
5676
- #${PANEL_ID} h4 { margin: 0; padding: 10px 14px 8px; font-size: 13px; cursor: grab;
5677
- touch-action: none; user-select: none; border-bottom: 1px solid rgba(255,255,255,.09); }
5678
- #${PANEL_ID} .row { display: flex; align-items: center; gap: 8px; padding: 7px 14px; }
5679
- #${PANEL_ID} .muted { color: rgba(245,245,247,.55); }
5680
- #${PANEL_ID} button { font: inherit; border-radius: 8px; border: 1px solid rgba(255,255,255,.18);
5681
- background: rgba(10,132,255,.85); color: #fff; padding: 4px 10px; cursor: pointer; }
5682
- #${PANEL_ID} button.ghost { background: transparent; }
5683
- #${PANEL_ID} button:disabled { opacity: .45; cursor: default; }
5684
- #${PANEL_ID} .bar { flex: 1; height: 6px; border-radius: 3px; background: rgba(255,255,255,.14); overflow: hidden; }
5685
- #${PANEL_ID} .bar > i { display: block; height: 100%; background: #0a84ff; }
5686
- #${PANEL_ID} .bar.hot > i { background: #ff9f0a; }
5687
- #${PANEL_ID} .todos { max-height: 168px; overflow: auto; padding: 2px 14px 6px; color: rgba(245,245,247,.8); white-space: pre-wrap; }
5688
- #${PANEL_ID} .hint { padding: 0 14px 8px; color: #ffd60a; display: none; }
5689
- #${PANEL_ID} .hint.show { display: block; }
5690
- `;
5691
- document.head.appendChild(style);
5692
- const fab = document.createElement("div");
5693
- fab.id = FAB_ID;
5694
- fab.className = PARASITE;
5695
- fab.textContent = "⏳";
5696
- fab.title = "长任务助手";
5697
- const panel = document.createElement("div");
5698
- panel.id = PANEL_ID;
5699
- panel.className = PARASITE;
5700
- panel.hidden = true;
5701
- panel.innerHTML = `
5702
- <h4>长任务助手</h4>
5703
- <div class="row"><label style="flex:1">长任务模式</label><button data-toggle>关闭</button></div>
5704
- <div class="row muted" data-why style="display:none"></div>
5705
- <div class="row"><span style="width:86px">上下文占用</span><div class="bar"><i></i></div><span data-ctx class="muted">–</span></div>
5706
- <div class="row"><span style="width:86px">任务漂移</span><div class="bar" data-driftbar><i></i></div><span data-drift class="muted">–</span></div>
5707
- <div class="hint" data-hint>建议开启长任务模式:临近上下文压缩高风险区 / 任务内容已大幅漂移。</div>
5708
- <div class="row"><button data-compress>压缩最近轮次入记忆</button><span class="muted" data-tail></span></div>
5709
- <div class="todos" data-todos>尚无任务快照</div>
5710
- `;
5711
- document.body.append(fab, panel);
5712
- let pos;
5713
- try {
5714
- pos = JSON.parse(localStorage.getItem(POS_KEY) ?? "null") ?? {
5715
- x: window.innerWidth - 66,
5716
- y: window.innerHeight - 140
5717
- };
5718
- } catch {
5719
- pos = { x: window.innerWidth - 66, y: window.innerHeight - 140 };
5720
- }
5721
- const place = () => {
5722
- pos.x = Math.max(4, Math.min(window.innerWidth - 46, pos.x));
5723
- pos.y = Math.max(4, Math.min(window.innerHeight - 46, pos.y));
5724
- fab.style.left = `${pos.x}px`;
5725
- fab.style.top = `${pos.y}px`;
5726
- };
5727
- place();
5728
- const makeDrag = (el, key) => {
5729
- let sx = 0;
5730
- let sy = 0;
5731
- let bx = 0;
5732
- let by = 0;
5733
- let active = false;
5734
- let dragged = false;
5735
- el.addEventListener("pointerdown", (e) => {
5736
- if (e.button !== 0 || e.target.closest?.("button, input")) return;
5737
- active = true;
5738
- dragged = false;
5739
- sx = e.clientX;
5740
- sy = e.clientY;
5741
- bx = pos.x;
5742
- by = pos.y;
5743
- el.setPointerCapture(e.pointerId);
5744
- });
5745
- el.addEventListener("pointermove", (e) => {
5746
- if (!active) return;
5747
- const dx = e.clientX - sx;
5748
- const dy = e.clientY - sy;
5749
- if (!dragged && Math.abs(dx) + Math.abs(dy) > 5) dragged = true;
5750
- if (dragged) {
5751
- pos.x = bx + dx;
5752
- pos.y = by + dy;
5753
- place();
5754
- }
5755
- });
5756
- el.addEventListener("pointerup", () => {
5757
- active = false;
5758
- if (dragged) {
5759
- try {
5760
- localStorage.setItem(POS_KEY, JSON.stringify(pos));
5761
- } catch {
5762
- }
5763
- if (key === "fab") suppressClick = true;
5764
- }
5765
- });
5766
- el.addEventListener("pointercancel", () => {
5767
- active = false;
5768
- });
5769
- };
5770
- let suppressClick = false;
5771
- makeDrag(fab, "fab");
5772
- let open = false;
5773
- fab.addEventListener("click", () => {
5774
- if (suppressClick) {
5775
- suppressClick = false;
5776
- return;
5777
- }
5778
- open = !open;
5779
- if (open) {
5780
- panel.hidden = false;
5781
- const px = Math.max(8, Math.min(window.innerWidth - 344, pos.x - 300));
5782
- const py = pos.y - 300 < 8 ? pos.y + 46 : Math.max(8, pos.y - 300);
5783
- panel.style.left = `${px}px`;
5784
- panel.style.top = `${py}px`;
5785
- void panel.offsetHeight;
5786
- panel.classList.add("open");
5787
- } else {
5788
- panel.classList.remove("open");
5789
- setTimeout(() => {
5790
- panel.hidden = true;
5791
- }, 240);
5792
- }
5793
- });
5794
- makeDrag(panel.querySelector("h4"), "panel");
5795
- let last;
5796
- const setToggle = (on, busy) => {
5797
- const btn2 = panel.querySelector("[data-toggle]");
5798
- btn2.textContent = on ? "开启中" : "关闭";
5799
- btn2.disabled = busy;
5800
- };
5801
- const render = (h) => {
5802
- last = h;
5803
- fab.classList.toggle("lit", Boolean(h.enabled && h.suggest && !h.longTask));
5804
- setToggle(h.longTask, false);
5805
- const ctxBar = panel.querySelector(".row .bar > i");
5806
- const ctxPctEl = panel.querySelector("[data-ctx]");
5807
- const pct = h.contextPct;
5808
- if (pct === null) {
5809
- ctxBar.style.width = "0%";
5810
- ctxPctEl.textContent = "–";
5811
- } else {
5812
- ctxBar.style.width = `${Math.min(100, pct)}%`;
5813
- ctxBar.parentElement?.classList.toggle("hot", pct >= h.contextThresholdPct);
5814
- ctxPctEl.textContent = `${pct}%`;
5815
- }
5816
- const driftBar = panel.querySelector("[data-driftbar] > i");
5817
- const driftEl = panel.querySelector("[data-drift]");
5818
- if (h.drift === null) {
5819
- driftBar.style.width = "0%";
5820
- driftEl.textContent = "–";
5821
- } else {
5822
- driftBar.style.width = `${Math.min(100, Math.round(h.drift * 100))}%`;
5823
- driftEl.textContent = `${Math.round(h.drift * 100)}%`;
5824
- }
5825
- const hint = panel.querySelector("[data-hint]");
5826
- hint.classList.toggle("show", Boolean(h.suggest && !h.longTask));
5827
- const tail = panel.querySelector("[data-tail]");
5828
- tail.textContent = h.pendingTailTurns === null ? "" : h.pendingTailTurns > 0 ? `待压缩 ${h.pendingTailTurns} 轮` : "已全部入忆";
5829
- panel.querySelector("[data-compress]").disabled = !h.longTask;
5830
- };
5831
- const fetchTodos = async () => {
5832
- return "";
5833
- };
5834
- void fetchTodos;
5835
- const tick = async () => {
5836
- const sid = sessionIdOf();
5837
- if (!sid) {
5838
- setTimeout(tick, 5e3);
5839
- return;
5840
- }
5841
- try {
5842
- const r = await rpc("dsh-memory/longtask-hint-get", { sessionId: sid });
5843
- if (r?.sessionId === sid) {
5844
- render(r);
5845
- if (String(r.longTask) !== panel.dataset.lt) {
5846
- panel.dataset.lt = String(r.longTask);
5847
- setToggle(r.longTask, false);
5848
- }
5849
- const todos = panel.querySelector("[data-todos]");
5850
- todos.textContent = r.todoCount > 0 ? `任务 ${r.todoCount} 条 · 漂移 ${r.drift === null ? "–" : `${Math.round((r.drift ?? 0) * 100)}%`}
5851
- (完整清单见会话 todo 面板;开启长任务后清单自动驻留上下文)` : "尚无任务快照";
5852
- }
5853
- } catch {
5854
- }
5855
- setTimeout(tick, last?.suggest ? 2e3 : 5e3);
5856
- };
5857
- setTimeout(tick, 1500);
5858
- panel.querySelector("[data-toggle]")?.addEventListener("click", async () => {
5859
- const sid = sessionIdOf();
5860
- if (!sid) return;
5861
- const btn2 = panel.querySelector("[data-toggle]");
5862
- const next = !(last?.longTask ?? false);
5863
- setToggle(next, true);
5864
- try {
5865
- const r = await rpc("dsh-memory/session-mode-set", { sessionId: sid, longTask: next });
5866
- if (last) last.longTask = Boolean(r.longTask ?? next);
5867
- setToggle(Boolean(r.longTask ?? next), false);
5868
- } catch {
5869
- setToggle(!next, false);
5870
- }
5871
- });
5872
- panel.querySelector("[data-compress]")?.addEventListener("click", async () => {
5873
- const sid = sessionIdOf();
5874
- if (!sid || !last?.longTask) return;
5875
- const btn2 = panel.querySelector("[data-compress]");
5876
- btn2.disabled = true;
5877
- try {
5878
- const r = await rpc("dsh-memory/longtask-compress-tail", { sessionId: sid });
5879
- const tail = panel.querySelector("[data-tail]");
5880
- tail.textContent = `已入队 ${r.enqueued ?? 0} 条消息蒸馏`;
5881
- } catch {
5882
- }
5883
- btn2.disabled = false;
5884
- });
5885
- }
5886
-
5887
5648
  // client/src/entry.tsx
5888
5649
  var inject = ["slots"];
5889
5650
  var SETTINGS_SEAT = "settings.section";
@@ -5891,12 +5652,6 @@ var __defProp = Object.defineProperty;
5891
5652
  function apply(ctx) {
5892
5653
  const rpc = makeRpc(ctx);
5893
5654
  console.info("[dsh-prime-memory] client apply: slots 注入就绪,注册 UI 槽位");
5894
- let latestSessionId;
5895
- try {
5896
- initLongTaskFab(rpc, () => latestSessionId);
5897
- } catch (err) {
5898
- console.warn("[dsh-prime-memory] 长任务浮动球挂载失败(不影响主面板):", err);
5899
- }
5900
5655
  const SETTINGS_SEAT2 = "settings.section";
5901
5656
  let cardSeatLive = false;
5902
5657
  try {
@@ -5930,7 +5685,6 @@ var __defProp = Object.defineProperty;
5930
5685
  id: "dsh-memory-mode",
5931
5686
  order: 100,
5932
5687
  inject: (sessionId) => {
5933
- latestSessionId = sessionId;
5934
5688
  return { sessionId, rpc };
5935
5689
  }
5936
5690
  },
package/dist/stats.js CHANGED
@@ -249,7 +249,10 @@ roomRegistry) {
249
249
  // 端点处理器:HTTP 层与旧 connection.rpc 的 handler 共用同一分发。
250
250
  const rpcHandler = async (endpoint, payload) => {
251
251
  try {
252
- const value = await handleEndpoint(endpoint, payload, buildEndpointDeps({ ctx, cfg, stores, logger }, { status, live, modes, dataDir, rebuild, embedManager, sessionInfo }, ruminate));
252
+ const value = await handleEndpoint(endpoint, payload, buildEndpointDeps({ ctx, cfg, stores, logger },
253
+ // longTask/roomRegistry 必须随 sources 下发——漏带 = 面板侧 room-*/longtask-*
254
+ // 全部"未装配"(工具面直传不受影响;2026-10-02 beta.8 实测回归)
255
+ { status, live, modes, dataDir, rebuild, embedManager, sessionInfo, longTask: longTaskDeps, roomRegistry }, ruminate));
253
256
  return { ok: true, value };
254
257
  }
255
258
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-prime-memory",
3
- "version": "0.20.0-beta.7",
3
+ "version": "0.20.0-beta.9",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",