dsh-layered-memory 0.5.3

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/assets/readme/hero.svg +58 -0
  4. package/cordis.patch.yml +16 -0
  5. package/dist/client.js +1066 -0
  6. package/dist/config.d.ts +263 -0
  7. package/dist/config.js +65 -0
  8. package/dist/hooks/capture.d.ts +16 -0
  9. package/dist/hooks/capture.js +160 -0
  10. package/dist/hooks/recall.d.ts +31 -0
  11. package/dist/hooks/recall.js +201 -0
  12. package/dist/index.d.ts +190 -0
  13. package/dist/index.js +234 -0
  14. package/dist/llm.d.ts +32 -0
  15. package/dist/llm.js +128 -0
  16. package/dist/pipeline/l1.d.ts +19 -0
  17. package/dist/pipeline/l1.js +226 -0
  18. package/dist/pipeline/l2.d.ts +13 -0
  19. package/dist/pipeline/l2.js +77 -0
  20. package/dist/pipeline/l3.d.ts +15 -0
  21. package/dist/pipeline/l3.js +72 -0
  22. package/dist/pipeline/runner.d.ts +44 -0
  23. package/dist/pipeline/runner.js +132 -0
  24. package/dist/prompts/l1-dedup.d.ts +20 -0
  25. package/dist/prompts/l1-dedup.js +244 -0
  26. package/dist/prompts/l1-extraction.d.ts +18 -0
  27. package/dist/prompts/l1-extraction.js +434 -0
  28. package/dist/prompts/persona.d.ts +23 -0
  29. package/dist/prompts/persona.js +234 -0
  30. package/dist/prompts/scene.d.ts +33 -0
  31. package/dist/prompts/scene.js +408 -0
  32. package/dist/settings.d.ts +27 -0
  33. package/dist/settings.js +73 -0
  34. package/dist/stats.d.ts +46 -0
  35. package/dist/stats.js +234 -0
  36. package/dist/store/bm25.d.ts +19 -0
  37. package/dist/store/bm25.js +62 -0
  38. package/dist/store/embedding.d.ts +68 -0
  39. package/dist/store/embedding.js +133 -0
  40. package/dist/store/io.d.ts +13 -0
  41. package/dist/store/io.js +75 -0
  42. package/dist/store/l0.d.ts +28 -0
  43. package/dist/store/l0.js +148 -0
  44. package/dist/store/l1.d.ts +70 -0
  45. package/dist/store/l1.js +239 -0
  46. package/dist/store/persona.d.ts +15 -0
  47. package/dist/store/persona.js +60 -0
  48. package/dist/store/scenes.d.ts +23 -0
  49. package/dist/store/scenes.js +150 -0
  50. package/dist/store/search-utils.d.ts +22 -0
  51. package/dist/store/search-utils.js +71 -0
  52. package/dist/store/session-modes.d.ts +24 -0
  53. package/dist/store/session-modes.js +102 -0
  54. package/dist/store/sqlite.d.ts +121 -0
  55. package/dist/store/sqlite.js +922 -0
  56. package/dist/store/state.d.ts +35 -0
  57. package/dist/store/state.js +62 -0
  58. package/dist/tools/index.d.ts +21 -0
  59. package/dist/tools/index.js +169 -0
  60. package/dist/types.d.ts +86 -0
  61. package/dist/types.js +7 -0
  62. package/dist/util/filelog.d.ts +4 -0
  63. package/dist/util/filelog.js +43 -0
  64. package/dist/util/sanitize.d.ts +10 -0
  65. package/dist/util/sanitize.js +66 -0
  66. package/dist/util/text.d.ts +11 -0
  67. package/dist/util/text.js +43 -0
  68. package/package.json +80 -0
package/dist/client.js ADDED
@@ -0,0 +1,1066 @@
1
+ /**
2
+ * dsh-layered-memory — browser half(手写 plain-JS bundle,无打包器)。
3
+ *
4
+ * 1. 输入栏(conversation.input.left,模式选择器右侧):会话记忆档位 pill
5
+ * (关闭/chat/自动/work 四态),点击展开 macOS 风格滑动选择器;
6
+ * 2. 设置 → 记忆:多 Tab 记忆浏览器(概览+开关 / L1 记忆 / L2 场景 / L3 画像 / 运行日志,
7
+ * 两族混合视图)。
8
+ * 数据通道:ctx.connection.rpc.call('/rpc', 'dsh-memory/*', payload) → Host 侧 RPC 端点。
9
+ *
10
+ * 结构对齐官方 client bundle 的 handoff 协议:
11
+ * window.__ModuleLoader__.load({ id, factory }),
12
+ * factory(require) 返回 { apply, inject }。
13
+ */
14
+ window.__ModuleLoader__.load({
15
+ id: "dsh-layered-memory",
16
+ factory: (require) => {
17
+ var module = { exports: {} };
18
+ var exports = module.exports;
19
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
20
+ var react = require("react");
21
+
22
+ var inject = ["slots", "connection"];
23
+
24
+ // ── 通用样式 ──
25
+ var S = {
26
+ section: { padding: "0 4px" },
27
+ heading: { fontSize: 15, fontWeight: 600, margin: "0 0 4px" },
28
+ intro: { fontSize: 13, color: "var(--dsw-alias-label-tertiary, #888)", margin: "0 0 12px" },
29
+ badge: {
30
+ display: "inline-block",
31
+ padding: "2px 10px",
32
+ borderRadius: 10,
33
+ fontSize: 12,
34
+ fontWeight: 600,
35
+ marginLeft: 8,
36
+ verticalAlign: "middle",
37
+ },
38
+ badgeOk: { background: "var(--dsw-alias-interactive-bg-primary, #1a7f37)", color: "#fff" },
39
+ badgeErr: { background: "var(--dsw-alias-state-error-primary, #cf222e)", color: "#fff" },
40
+ tabbar: { display: "flex", gap: 4, borderBottom: "1px solid var(--dsw-alias-border-secondary, #e0e0e0)", marginBottom: 12 },
41
+ tab: {
42
+ padding: "6px 14px",
43
+ fontSize: 13,
44
+ border: "none",
45
+ background: "none",
46
+ cursor: "pointer",
47
+ color: "var(--dsw-alias-label-secondary, #666)",
48
+ borderBottom: "2px solid transparent",
49
+ marginBottom: -1,
50
+ },
51
+ tabActive: {
52
+ fontWeight: 600,
53
+ color: "var(--dsw-alias-label-primary, #111)",
54
+ borderBottom: "2px solid var(--dsw-alias-interactive-bg-primary, #1a7f37)",
55
+ },
56
+ table: {
57
+ width: "100%",
58
+ borderCollapse: "collapse",
59
+ fontSize: 13,
60
+ fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
61
+ },
62
+ td: { padding: "6px 10px", borderBottom: "1px solid var(--dsw-alias-border-secondary, #eee)" },
63
+ tdKey: { width: 180, color: "var(--dsw-alias-label-tertiary, #888)", whiteSpace: "nowrap" },
64
+ error: {
65
+ marginTop: 10,
66
+ fontSize: 13,
67
+ color: "var(--dsh-alias-state-error-primary, #cf222e)",
68
+ whiteSpace: "pre-wrap",
69
+ },
70
+ hint: { marginTop: 12, fontSize: 12, color: "var(--dsw-alias-label-tertiary, #999)" },
71
+ toolbar: { display: "flex", gap: 8, marginBottom: 12, flexWrap: "wrap", alignItems: "center" },
72
+ input: {
73
+ flex: 1,
74
+ minWidth: 160,
75
+ padding: "5px 10px",
76
+ fontSize: 13,
77
+ borderRadius: 6,
78
+ border: "1px solid var(--dsw-alias-border-secondary, #ccc)",
79
+ background: "var(--dsw-alias-bg-primary, #fff)",
80
+ color: "var(--dsw-alias-label-primary, #111)",
81
+ },
82
+ select: { padding: "5px 6px", fontSize: 13, borderRadius: 6, border: "1px solid var(--dsw-alias-border-secondary, #ccc)" },
83
+ button: {
84
+ padding: "5px 14px",
85
+ fontSize: 13,
86
+ borderRadius: 6,
87
+ border: "1px solid var(--dsw-alias-border-secondary, #ccc)",
88
+ background: "var(--dsw-alias-bg-primary, #fff)",
89
+ cursor: "pointer",
90
+ },
91
+ card: {
92
+ border: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
93
+ borderRadius: 8,
94
+ padding: "10px 12px",
95
+ marginBottom: 8,
96
+ fontSize: 13,
97
+ },
98
+ cardHead: { display: "flex", alignItems: "center", gap: 8, marginBottom: 4 },
99
+ tag: {
100
+ display: "inline-block",
101
+ padding: "1px 8px",
102
+ borderRadius: 8,
103
+ fontSize: 11,
104
+ fontWeight: 600,
105
+ color: "#fff",
106
+ whiteSpace: "nowrap",
107
+ },
108
+ muted: { color: "var(--dsw-alias-label-tertiary, #999)", fontSize: 12 },
109
+ content: { lineHeight: 1.5, wordBreak: "break-word" },
110
+ detail: {
111
+ marginTop: 8,
112
+ paddingTop: 8,
113
+ borderTop: "1px dashed var(--dsw-alias-border-secondary, #ddd)",
114
+ fontSize: 12,
115
+ fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
116
+ color: "var(--dsw-alias-label-secondary, #666)",
117
+ whiteSpace: "pre-wrap",
118
+ },
119
+ pre: {
120
+ margin: 0,
121
+ padding: "10px 12px",
122
+ background: "var(--dsw-alias-bg-secondary, #f6f8fa)",
123
+ borderRadius: 8,
124
+ fontSize: 12,
125
+ fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
126
+ whiteSpace: "pre-wrap",
127
+ wordBreak: "break-word",
128
+ lineHeight: 1.6,
129
+ maxHeight: 480,
130
+ overflow: "auto",
131
+ },
132
+ switchRow: { display: "flex", alignItems: "center", gap: 10, padding: "8px 0" },
133
+ switchLabel: { fontSize: 13, fontWeight: 600, minWidth: 72 },
134
+ switchDesc: { fontSize: 12, color: "var(--dsw-alias-label-tertiary, #999)" },
135
+ switch: {
136
+ width: 36,
137
+ height: 20,
138
+ borderRadius: 10,
139
+ position: "relative",
140
+ cursor: "pointer",
141
+ transition: "background .15s",
142
+ flexShrink: 0,
143
+ },
144
+ switchOn: { background: "var(--dsw-alias-interactive-bg-primary, #1a7f37)" },
145
+ switchOff: { background: "var(--dsw-alias-border-secondary, #ccc)" },
146
+ switchDisabled: { opacity: 0.4, cursor: "not-allowed" },
147
+ knob: {
148
+ position: "absolute",
149
+ top: 2,
150
+ width: 16,
151
+ height: 16,
152
+ borderRadius: "50%",
153
+ background: "#fff",
154
+ transition: "left .15s",
155
+ boxShadow: "0 1px 2px rgba(0,0,0,.2)",
156
+ },
157
+ switchPanel: {
158
+ border: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
159
+ borderRadius: 8,
160
+ padding: "4px 12px",
161
+ marginBottom: 14,
162
+ },
163
+ flexRow: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
164
+ grow: { flex: 1 },
165
+ sceneHead: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6, flexWrap: "wrap" },
166
+ sceneTitle: { fontSize: 13, fontWeight: 600, fontFamily: "ui-monospace, Consolas, monospace" },
167
+ };
168
+
169
+ var TYPE_COLORS = {
170
+ persona: "#8250df",
171
+ episodic: "#0969da",
172
+ instruction: "#bf8700",
173
+ work_fact: "#0969da",
174
+ work_task: "#1a7f37",
175
+ work_method: "#8250df",
176
+ work_artifact: "#bf8700",
177
+ };
178
+ var TYPE_LABELS = {
179
+ persona: "画像偏好",
180
+ episodic: "客观事件",
181
+ instruction: "全局指令",
182
+ work_fact: "工作事实",
183
+ work_task: "工作任务",
184
+ work_method: "工作方法",
185
+ work_artifact: "工作资产",
186
+ };
187
+
188
+ function fmtTime(iso) {
189
+ if (!iso) return "—";
190
+ try {
191
+ return new Date(iso).toLocaleString();
192
+ } catch (e) {
193
+ return String(iso);
194
+ }
195
+ }
196
+
197
+ // ── Switch 组件 ──
198
+ function Switch(props) {
199
+ var on = !!props.checked;
200
+ var disabled = !!props.disabled;
201
+ var base = Object.assign({}, S.switch, on ? S.switchOn : S.switchOff, disabled ? S.switchDisabled : null);
202
+ return react.createElement(
203
+ "div",
204
+ {
205
+ style: base,
206
+ onClick: function () {
207
+ if (!disabled && props.onChange) props.onChange(!on);
208
+ },
209
+ },
210
+ react.createElement("span", {
211
+ style: Object.assign({}, S.knob, { left: on ? 18 : 2 }),
212
+ }),
213
+ );
214
+ }
215
+
216
+ function SwitchRow(props) {
217
+ return react.createElement(
218
+ "div",
219
+ { style: S.switchRow },
220
+ react.createElement(Switch, {
221
+ checked: props.checked,
222
+ disabled: props.disabled,
223
+ onChange: props.onChange,
224
+ }),
225
+ react.createElement("div", null,
226
+ react.createElement("div", { style: S.switchLabel }, props.label),
227
+ react.createElement("div", { style: S.switchDesc }, props.desc || "")),
228
+ );
229
+ }
230
+
231
+ // ── 会话记忆档位控件(输入栏 pill + macOS 风格滑动选择器) ──
232
+ // 档位顺序即滑轨顺序:关闭 → chat → work → 自动(默认档"自动"居右)
233
+ var MODES = [
234
+ { key: "off", label: "关闭", color: "#6e7781" },
235
+ { key: "chat", label: "chat", color: "#1a7f37" },
236
+ { key: "work", label: "work", color: "#bf8700" },
237
+ { key: "auto", label: "自动", color: "#0969da" },
238
+ ];
239
+ var TRACK_W = 200;
240
+ var THUMB = 16;
241
+ var INNER_W = TRACK_W - THUMB;
242
+
243
+ function modeInfo(key) {
244
+ for (var i = 0; i < MODES.length; i++) if (MODES[i].key === key) return MODES[i];
245
+ return MODES[3];
246
+ }
247
+
248
+ function modeLabel(key) {
249
+ if (key === "auto") return "自动(双族)";
250
+ if (key === "chat") return "chat(个人)";
251
+ if (key === "work") return "work(工作)";
252
+ return "off(关闭)";
253
+ }
254
+
255
+ function modeIndex(key) {
256
+ for (var i = 0; i < MODES.length; i++) if (MODES[i].key === key) return i;
257
+ return 3;
258
+ }
259
+
260
+ /** 滑动选择器浮层(参考 macOS 滑动器:拖拽圆头 + 松手吸附最近档;当前档由下方标签高亮指示)。 */
261
+ function ModeSlider(props) {
262
+ var trackRef = react.useRef(null);
263
+ var dragState = react.useState(null);
264
+ var dragIdx = dragState[0];
265
+ var setDragIdx = dragState[1];
266
+
267
+ var activeIdx = dragIdx === null ? modeIndex(props.mode) : dragIdx;
268
+
269
+ var idxFromClientX = function (clientX) {
270
+ var rect = trackRef.current.getBoundingClientRect();
271
+ var x = clientX - rect.left - THUMB / 2;
272
+ if (x < 0) x = 0;
273
+ if (x > INNER_W) x = INNER_W;
274
+ return Math.round((x / INNER_W) * (MODES.length - 1));
275
+ };
276
+
277
+ var onPointerDown = function (e) {
278
+ e.preventDefault();
279
+ e.currentTarget.setPointerCapture(e.pointerId);
280
+ setDragIdx(idxFromClientX(e.clientX));
281
+ };
282
+ var onPointerMove = function (e) {
283
+ if (dragIdx === null) return;
284
+ setDragIdx(idxFromClientX(e.clientX));
285
+ };
286
+ var onPointerUp = function (e) {
287
+ if (dragIdx === null) return;
288
+ var idx = idxFromClientX(e.clientX);
289
+ setDragIdx(null);
290
+ props.onCommit(MODES[idx].key);
291
+ };
292
+
293
+ var thumbLeft = (activeIdx / (MODES.length - 1)) * INNER_W;
294
+ var info = MODES[activeIdx];
295
+
296
+ var stops = [];
297
+ for (var i = 0; i < MODES.length; i++) {
298
+ (function (i) {
299
+ var stopLeft = (i / (MODES.length - 1)) * INNER_W + THUMB / 2;
300
+ var active = i === activeIdx;
301
+ stops.push(
302
+ react.createElement(
303
+ "div",
304
+ {
305
+ key: "stop" + i,
306
+ style: {
307
+ position: "absolute",
308
+ left: stopLeft - 3,
309
+ top: 6,
310
+ width: 6,
311
+ height: 6,
312
+ borderRadius: "50%",
313
+ background: active ? MODES[i].color : "#c6cbd1",
314
+ },
315
+ },
316
+ ),
317
+ react.createElement(
318
+ "button",
319
+ {
320
+ key: "label" + i,
321
+ style: {
322
+ position: "absolute",
323
+ left: stopLeft,
324
+ top: 26,
325
+ transform: "translateX(-50%)",
326
+ fontSize: 11,
327
+ lineHeight: "16px",
328
+ padding: "0 4px",
329
+ border: "none",
330
+ background: "none",
331
+ cursor: "pointer",
332
+ whiteSpace: "nowrap",
333
+ fontWeight: active ? 600 : 400,
334
+ color: active ? MODES[i].color : "var(--dsw-alias-label-tertiary, #888)",
335
+ },
336
+ onClick: function () { if (dragIdx === null) props.onCommit(MODES[i].key); },
337
+ },
338
+ MODES[i].label,
339
+ ),
340
+ );
341
+ })(i);
342
+ }
343
+
344
+ return react.createElement(
345
+ "div",
346
+ {
347
+ style: {
348
+ position: "absolute",
349
+ bottom: "calc(100% + 8px)",
350
+ // 居中悬浮在按钮上方(水平中轴对齐 pill 中心)
351
+ left: "50%",
352
+ transform: "translateX(-50%)",
353
+ zIndex: 1000,
354
+ background: "var(--dsw-alias-bg-primary, #fff)",
355
+ border: "1px solid var(--dsw-alias-border-secondary, #e0e0e0)",
356
+ borderRadius: 10,
357
+ boxShadow: "0 8px 24px rgba(0,0,0,0.16)",
358
+ padding: "12px 16px 30px",
359
+ },
360
+ },
361
+ react.createElement(
362
+ "div",
363
+ {
364
+ ref: trackRef,
365
+ style: {
366
+ position: "relative",
367
+ width: TRACK_W + THUMB,
368
+ height: 16,
369
+ touchAction: "none",
370
+ cursor: dragIdx === null ? "pointer" : "grabbing",
371
+ },
372
+ onPointerDown: onPointerDown,
373
+ onPointerMove: onPointerMove,
374
+ onPointerUp: onPointerUp,
375
+ onPointerCancel: onPointerUp,
376
+ },
377
+ // 圆球先画、线条后画(叠在球面上)——线条从圆球中心穿过;
378
+ // 线两端延伸到两端球的边缘(对称穿过首尾停点的中心);
379
+ // 当前档由下方标签高亮指示(上方不放文字)
380
+ react.createElement("div", {
381
+ style: {
382
+ position: "absolute",
383
+ left: thumbLeft,
384
+ top: 1,
385
+ width: THUMB,
386
+ height: THUMB,
387
+ borderRadius: "50%",
388
+ background: "#fff",
389
+ border: "1px solid " + info.color,
390
+ boxShadow: "0 1px 4px rgba(0,0,0,0.3)",
391
+ pointerEvents: "none",
392
+ transition: dragIdx === null ? "left 120ms ease" : "none",
393
+ },
394
+ }),
395
+ react.createElement("div", {
396
+ style: {
397
+ position: "absolute",
398
+ left: 0,
399
+ width: TRACK_W,
400
+ top: 7,
401
+ height: 4,
402
+ borderRadius: 999,
403
+ background: "var(--dsw-alias-border-secondary, #e1e4e8)",
404
+ pointerEvents: "none",
405
+ },
406
+ }),
407
+ stops,
408
+ ),
409
+ props.error
410
+ ? react.createElement("div", { style: { fontSize: 11, color: "#cf222e", marginTop: 20, whiteSpace: "nowrap" } }, props.error)
411
+ : null,
412
+ );
413
+ }
414
+
415
+ /** 输入栏 pill:点击展开滑动选择器;props 来自 conversation.input.left 的 zone 注入。 */
416
+ function MemoryModePill(props) {
417
+ var rpc = props.rpc;
418
+ var sessionId = props.sessionId || (props.session && props.session.sessionId);
419
+ var modeState = react.useState(null);
420
+ var mode = modeState[0];
421
+ var setMode = modeState[1];
422
+ var errState = react.useState(null);
423
+ var error = errState[0];
424
+ var setError = errState[1];
425
+ var openState = react.useState(false);
426
+ var open = openState[0];
427
+ var setOpen = openState[1];
428
+ var wrapRef = react.useRef(null);
429
+
430
+ react.useEffect(function () {
431
+ if (!sessionId || !rpc) return;
432
+ rpc("dsh-memory/session-mode-get", { sessionId: sessionId })
433
+ .then(function (r) {
434
+ if (r && r.ok && r.value) setMode(r.value.mode);
435
+ })
436
+ .catch(function () {});
437
+ }, [sessionId, rpc]);
438
+
439
+ react.useEffect(function () {
440
+ if (!open) return;
441
+ var onDown = function (e) {
442
+ if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
443
+ };
444
+ var onKey = function (e) {
445
+ if (e.key === "Escape") setOpen(false);
446
+ };
447
+ document.addEventListener("mousedown", onDown);
448
+ document.addEventListener("keydown", onKey);
449
+ return function () {
450
+ document.removeEventListener("mousedown", onDown);
451
+ document.removeEventListener("keydown", onKey);
452
+ };
453
+ }, [open]);
454
+
455
+ /** 乐观提交:立即更新 UI,RPC 失败回滚并提示。 */
456
+ var commit = function (next) {
457
+ if (!rpc || next === mode) return;
458
+ var prev = mode;
459
+ setMode(next);
460
+ setError(null);
461
+ rpc("dsh-memory/session-mode-set", { sessionId: sessionId, mode: next })
462
+ .then(function (r) {
463
+ if (!r || !r.ok) {
464
+ setMode(prev);
465
+ setError(r && r.error ? "档位写入失败:" + r.error.message : "档位写入失败");
466
+ }
467
+ })
468
+ .catch(function (e) {
469
+ setMode(prev);
470
+ setError("档位写入失败:" + String((e && e.message) || e));
471
+ });
472
+ };
473
+
474
+ if (!sessionId || !rpc) return null;
475
+ var info = modeInfo(mode);
476
+ var loaded = mode !== null;
477
+
478
+ return react.createElement(
479
+ "div",
480
+ { ref: wrapRef, style: { position: "relative", display: "inline-flex" } },
481
+ react.createElement(
482
+ "button",
483
+ {
484
+ type: "button",
485
+ title: "本会话记忆档位(点击切换)",
486
+ onClick: function () { setOpen(!open); },
487
+ style: {
488
+ display: "inline-flex",
489
+ alignItems: "center",
490
+ gap: 4,
491
+ height: 24,
492
+ padding: "0 10px",
493
+ borderRadius: 999,
494
+ fontSize: 12,
495
+ fontWeight: 500,
496
+ lineHeight: "20px",
497
+ cursor: "pointer",
498
+ border: "1px solid " + (loaded ? info.color + "66" : "var(--dsw-alias-border-secondary, #ccc)"),
499
+ background: loaded ? info.color + "14" : "var(--dsw-alias-bg-secondary, #f6f8fa)",
500
+ color: loaded ? info.color : "var(--dsh-alias-label-tertiary, #888)",
501
+ },
502
+ },
503
+ "记忆·",
504
+ react.createElement("span", null, loaded ? info.label : "…"),
505
+ ),
506
+ open
507
+ ? react.createElement(ModeSlider, {
508
+ mode: mode || "auto",
509
+ onCommit: commit,
510
+ error: error,
511
+ })
512
+ : null,
513
+ );
514
+ }
515
+
516
+ // ── Tab:概览(开关面板 + 计数) ──
517
+ function OverviewTab(props) {
518
+ var rpc = props.rpc;
519
+ var statsState = react.useState(null);
520
+ var stats = statsState[0];
521
+ var setStats = statsState[1];
522
+ var settingsState = react.useState(null);
523
+ var settingsData = settingsState[0];
524
+ var setSettingsData = settingsState[1];
525
+ var errState = react.useState(null);
526
+ var error = errState[0];
527
+ var setError = errState[1];
528
+
529
+ var load = react.useCallback(function () {
530
+ rpc("dsh-memory/stats", {})
531
+ .then(function (r) {
532
+ if (r && r.ok) setStats(r.value);
533
+ else setError(r && r.error ? r.error.message : "RPC error");
534
+ })
535
+ .catch(function (e) { setError(String((e && e.message) || e)); });
536
+ rpc("dsh-memory/settings-get", {})
537
+ .then(function (r) {
538
+ if (r && r.ok) setSettingsData(r.value);
539
+ })
540
+ .catch(function () {});
541
+ }, [rpc]);
542
+
543
+ react.useEffect(function () {
544
+ load();
545
+ var timer = setInterval(load, 5000);
546
+ return function () { clearInterval(timer); };
547
+ }, [load]);
548
+
549
+ var toggle = function (key, value) {
550
+ if (!settingsData) return;
551
+ // 乐观更新:立即翻 UI,失败回滚
552
+ var prev = settingsData;
553
+ var next = Object.assign({}, prev, {
554
+ settings: Object.assign({}, prev.settings, (function () { var o = {}; o[key] = value; return o; })()),
555
+ });
556
+ setSettingsData(next);
557
+ rpc("dsh-memory/settings-set", (function () { var o = {}; o[key] = value; return o; })())
558
+ .then(function (r) {
559
+ if (!r || !r.ok) {
560
+ setSettingsData(prev);
561
+ setError(r && r.error ? "开关写入失败:" + r.error.message : "开关写入失败");
562
+ } else {
563
+ setError(null);
564
+ }
565
+ })
566
+ .catch(function (e) {
567
+ setSettingsData(prev);
568
+ setError("开关写入失败:" + String((e && e.message) || e));
569
+ });
570
+ };
571
+
572
+ var rows = [];
573
+ if (stats) {
574
+ rows.push(["数据目录", stats.dataDir]);
575
+ rows.push(["插件版本", "v" + stats.version]);
576
+ rows.push(["默认档", modeLabel(stats.family)]);
577
+ rows.push(["L0 今日消息", String(stats.l0Today)]);
578
+ rows.push(["L1 原子记忆", stats.l1Count + " 条(累计抽取 " + stats.l1TotalExtracted + ")"]);
579
+ rows.push(["L2 场景块", String(stats.sceneCount)]);
580
+ rows.push(["L3 画像", stats.personaChars > 0 ? stats.personaChars + " 字符" : "未生成"]);
581
+ rows.push(["上次 L1 抽取", fmtTime(stats.lastExtractAt)]);
582
+ rows.push(["上次 L2 整合", fmtTime(stats.lastL2At)]);
583
+ rows.push(["上次 L3 蒸馏", fmtTime(stats.lastL3At)]);
584
+ rows.push(["待 L2 新记忆", stats.memoriesSinceL2 + " / 5"]);
585
+ rows.push(["待 L3 新记忆", stats.memoriesSinceL3 + " / 20"]);
586
+ rows.push(["待重试消息", String(stats.pendingExtract)]);
587
+ }
588
+
589
+ var master = settingsData && settingsData.settings ? settingsData.settings.enabled : true;
590
+ var ceilingNote = "";
591
+ if (settingsData && settingsData.ceilings) {
592
+ var off = [];
593
+ if (!settingsData.ceilings.capture) off.push("捕获");
594
+ if (!settingsData.ceilings.distill) off.push("蒸馏");
595
+ if (!settingsData.ceilings.recall) off.push("召回");
596
+ if (off.length > 0) ceilingNote = "注意:部署配置已停用 " + off.join("、") + "(运行时开关无法开启)";
597
+ }
598
+
599
+ return react.createElement(
600
+ "div", null,
601
+ settingsData && settingsData.supported === false
602
+ ? react.createElement("p", { style: S.hint }, "settings 服务不可用,记忆模式开关未启用(记忆保持全开)。")
603
+ : settingsData
604
+ ? react.createElement(
605
+ "div", { style: S.switchPanel },
606
+ react.createElement(SwitchRow, {
607
+ label: "记忆模式",
608
+ desc: master ? "已开启:捕获对话并蒸馏记忆" : "已关闭:不捕获、不蒸馏、不注入(数据保留)",
609
+ checked: master,
610
+ onChange: function (v) { toggle("enabled", v); },
611
+ }),
612
+ react.createElement(SwitchRow, {
613
+ label: "捕获",
614
+ desc: "L0:记录原始对话(关闭后蒸馏也无输入)",
615
+ checked: settingsData.settings.capture,
616
+ disabled: !master,
617
+ onChange: function (v) { toggle("capture", v); },
618
+ }),
619
+ react.createElement(SwitchRow, {
620
+ label: "蒸馏",
621
+ desc: "L1 抽取 + L2 场景 + L3 画像",
622
+ checked: settingsData.settings.distill,
623
+ disabled: !master,
624
+ onChange: function (v) { toggle("distill", v); },
625
+ }),
626
+ react.createElement(SwitchRow, {
627
+ label: "召回",
628
+ desc: "对话时注入相关记忆与画像",
629
+ checked: settingsData.settings.recall,
630
+ disabled: !master,
631
+ onChange: function (v) { toggle("recall", v); },
632
+ }),
633
+ ceilingNote
634
+ ? react.createElement("p", { style: S.hint }, ceilingNote)
635
+ : null,
636
+ )
637
+ : null,
638
+ error
639
+ ? react.createElement("div", { style: S.error }, "获取状态失败:" + error)
640
+ : !stats
641
+ ? react.createElement("p", { style: S.intro }, "正在读取记忆状态…")
642
+ : react.createElement(
643
+ "table",
644
+ { style: S.table },
645
+ react.createElement(
646
+ "tbody", null,
647
+ rows.map(function (row) {
648
+ return react.createElement(
649
+ "tr", { key: row[0] },
650
+ react.createElement("td", { style: S.tdKey }, row[0]),
651
+ react.createElement("td", { style: S.td }, row[1]),
652
+ );
653
+ }),
654
+ ),
655
+ ),
656
+ react.createElement(
657
+ "p", { style: S.hint },
658
+ "浏览各层记忆内容请切换上方 Tab;原始对话(L0)不入浏览器,可由模型侧 conversation_search 工具查询。",
659
+ ),
660
+ );
661
+ }
662
+
663
+ // ── Tab:L1 记忆浏览器 ──
664
+ function RecordsTab(props) {
665
+ var rpc = props.rpc;
666
+ var limit = 50;
667
+
668
+ var itemsState = react.useState([]);
669
+ var items = itemsState[0];
670
+ var setItems = itemsState[1];
671
+ var moreState = react.useState(false);
672
+ var hasMore = moreState[0];
673
+ var setHasMore = moreState[1];
674
+ var totalState = react.useState(null);
675
+ var total = totalState[0];
676
+ var setTotal = totalState[1];
677
+ var scenesState = react.useState([]);
678
+ var sceneOptions = scenesState[0];
679
+ var setSceneOptions = scenesState[1];
680
+ var loadingState = react.useState(false);
681
+ var loading = loadingState[0];
682
+ var setLoading = loadingState[1];
683
+ var errState = react.useState(null);
684
+ var error = errState[0];
685
+ var setError = errState[1];
686
+ var expandedState = react.useState(null);
687
+ var expandedId = expandedState[0];
688
+ var setExpandedId = expandedState[1];
689
+
690
+ var queryState = react.useState("");
691
+ var query = queryState[0];
692
+ var setQuery = queryState[1];
693
+ var typeState = react.useState("");
694
+ var typeFilter = typeState[0];
695
+ var setTypeFilter = typeState[1];
696
+ var sceneState = react.useState("");
697
+ var sceneFilter = sceneState[0];
698
+ var setSceneFilter = sceneState[1];
699
+
700
+ // 最近一次实际查询条件(分页累积用)
701
+ var lastState = react.useState({ query: "", type: "", scene: "" });
702
+ var last = lastState[0];
703
+ var setLast = lastState[1];
704
+
705
+ var fetchPage = react.useCallback(function (conds, offset, append) {
706
+ setLoading(true);
707
+ setError(null);
708
+ var payload = { limit: limit, offset: offset };
709
+ if (conds.query) payload.query = conds.query;
710
+ if (conds.type) payload.type = conds.type;
711
+ if (conds.scene) payload.scene = conds.scene;
712
+ rpc("dsh-memory/list-records", payload)
713
+ .then(function (r) {
714
+ setLoading(false);
715
+ if (!r || !r.ok) {
716
+ setError(r && r.error ? r.error.message : "RPC error");
717
+ return;
718
+ }
719
+ var v = r.value;
720
+ setItems(function (prev) { return append ? prev.concat(v.items) : v.items; });
721
+ setHasMore(!!v.hasMore);
722
+ setTotal(v.total === undefined || v.total === null ? null : v.total);
723
+ if (v.scenes) setSceneOptions(v.scenes);
724
+ })
725
+ .catch(function (e) {
726
+ setLoading(false);
727
+ setError(String((e && e.message) || e));
728
+ });
729
+ }, [rpc]);
730
+
731
+ var search = function () {
732
+ var conds = { query: query.trim(), type: typeFilter, scene: sceneFilter };
733
+ setLast(conds);
734
+ fetchPage(conds, 0, false);
735
+ };
736
+
737
+ react.useEffect(function () {
738
+ fetchPage({ query: "", type: "", scene: "" }, 0, false);
739
+ }, [fetchPage]);
740
+
741
+ // 混合视图:两族类型都在库里,筛选器给全 7 类
742
+ var typeChoices = [
743
+ "persona", "episodic", "instruction",
744
+ "work_fact", "work_task", "work_method", "work_artifact",
745
+ ];
746
+
747
+ var countText = total !== null ? "共 " + total + " 条" : items.length + " 条" + (hasMore ? "+" : "");
748
+
749
+ return react.createElement(
750
+ "div", null,
751
+ react.createElement(
752
+ "div", { style: S.toolbar },
753
+ react.createElement("input", {
754
+ style: S.input,
755
+ placeholder: "搜索记忆内容(BM25 关键词)…",
756
+ value: query,
757
+ onChange: function (e) { setQuery(e.target.value); },
758
+ onKeyDown: function (e) { if (e.key === "Enter") search(); },
759
+ }),
760
+ react.createElement(
761
+ "select",
762
+ {
763
+ style: S.select,
764
+ value: typeFilter,
765
+ onChange: function (e) { setTypeFilter(e.target.value); },
766
+ },
767
+ react.createElement("option", { value: "" }, "全部类型"),
768
+ typeChoices.map(function (t) {
769
+ return react.createElement("option", { key: t, value: t }, TYPE_LABELS[t] || t);
770
+ }),
771
+ ),
772
+ react.createElement(
773
+ "select",
774
+ {
775
+ style: S.select,
776
+ value: sceneFilter,
777
+ onChange: function (e) { setSceneFilter(e.target.value); },
778
+ },
779
+ react.createElement("option", { value: "" }, "全部情境"),
780
+ sceneOptions.map(function (s) {
781
+ return react.createElement("option", { key: s, value: s }, s.length > 24 ? s.slice(0, 24) + "…" : s);
782
+ }),
783
+ ),
784
+ react.createElement("button", { style: S.button, onClick: search }, "搜索"),
785
+ ),
786
+ react.createElement(
787
+ "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
788
+ react.createElement("span", { style: S.muted }, loading ? "加载中…" : countText),
789
+ react.createElement("div", { style: S.grow }),
790
+ react.createElement("button", {
791
+ style: S.button,
792
+ onClick: function () { fetchPage(last, 0, false); },
793
+ }, "刷新"),
794
+ ),
795
+ error ? react.createElement("div", { style: S.error }, error) : null,
796
+ items.length === 0 && !loading && !error
797
+ ? react.createElement("p", { style: S.intro }, "暂无记忆。对话几轮后,蒸馏管线会自动抽取记忆。")
798
+ : items.map(function (m) {
799
+ var open = expandedId === m.id;
800
+ return react.createElement(
801
+ "div",
802
+ {
803
+ key: m.id,
804
+ style: S.card,
805
+ onClick: function () { setExpandedId(open ? null : m.id); },
806
+ },
807
+ react.createElement(
808
+ "div", { style: S.cardHead },
809
+ react.createElement(
810
+ "span",
811
+ {
812
+ style: Object.assign({}, S.tag, {
813
+ background: TYPE_COLORS[m.type] || "#666",
814
+ }),
815
+ },
816
+ TYPE_LABELS[m.type] || m.type,
817
+ ),
818
+ react.createElement("span", { style: S.muted }, "优先级 " + m.priority),
819
+ m.score !== null && m.score !== undefined
820
+ ? react.createElement("span", { style: S.muted }, "相关度 " + Number(m.score).toFixed(2))
821
+ : null,
822
+ react.createElement("div", { style: S.grow }),
823
+ react.createElement("span", { style: S.muted }, fmtTime(m.updatedAt)),
824
+ ),
825
+ react.createElement("div", { style: S.content }, m.content),
826
+ open
827
+ ? react.createElement(
828
+ "div", { style: S.detail },
829
+ "id: " + m.id + "\n" +
830
+ "情境: " + (m.scene || "—") + "\n" +
831
+ "版本: v" + m.version + "(去重合并次数 " + m.version + ")\n" +
832
+ "创建: " + fmtTime(m.createdAt) + "\n" +
833
+ "活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "—") + "\n" +
834
+ (m.sourceMessageIds && m.sourceMessageIds.length > 0
835
+ ? "来源消息: " + m.sourceMessageIds.join(", ")
836
+ : "来源消息: —"),
837
+ )
838
+ : null,
839
+ );
840
+ }),
841
+ hasMore
842
+ ? react.createElement(
843
+ "div", { style: S.flexRow },
844
+ react.createElement("div", { style: S.grow }),
845
+ react.createElement("button", {
846
+ style: S.button,
847
+ onClick: function () { fetchPage(last, items.length, true); },
848
+ }, loading ? "加载中…" : "加载更多"),
849
+ )
850
+ : null,
851
+ );
852
+ }
853
+
854
+ // ── Tab:L2 场景 ──
855
+ function ScenesTab(props) {
856
+ var rpc = props.rpc;
857
+ var itemsState = react.useState(null);
858
+ var items = itemsState[0];
859
+ var setItems = itemsState[1];
860
+ var errState = react.useState(null);
861
+ var error = errState[0];
862
+ var setError = errState[1];
863
+
864
+ var load = react.useCallback(function () {
865
+ rpc("dsh-memory/scenes", {})
866
+ .then(function (r) {
867
+ if (r && r.ok) { setItems(r.value.items); setError(null); }
868
+ else setError(r && r.error ? r.error.message : "RPC error");
869
+ })
870
+ .catch(function (e) { setError(String((e && e.message) || e)); });
871
+ }, [rpc]);
872
+
873
+ react.useEffect(function () { load(); }, [load]);
874
+
875
+ return react.createElement(
876
+ "div", null,
877
+ react.createElement(
878
+ "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
879
+ react.createElement("span", { style: S.muted }, items ? items.length + " 个场景块" : "加载中…"),
880
+ react.createElement("div", { style: S.grow }),
881
+ react.createElement("button", { style: S.button, onClick: load }, "刷新"),
882
+ ),
883
+ error ? react.createElement("div", { style: S.error }, error) : null,
884
+ items && items.length === 0
885
+ ? react.createElement("p", { style: S.intro }, "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。")
886
+ : null,
887
+ (items || []).map(function (s) {
888
+ return react.createElement(
889
+ "div", { key: s.path, style: S.card },
890
+ react.createElement(
891
+ "div", { style: S.sceneHead },
892
+ react.createElement("span", { style: S.sceneTitle }, s.path),
893
+ s.heat ? react.createElement("span", { style: S.muted }, "热度 " + s.heat) : null,
894
+ react.createElement("div", { style: S.grow }),
895
+ react.createElement("span", { style: S.muted }, "更新 " + fmtTime(s.updated)),
896
+ ),
897
+ s.summary
898
+ ? react.createElement(
899
+ "div",
900
+ { style: Object.assign({}, S.muted, { marginBottom: 6 }) },
901
+ s.summary,
902
+ )
903
+ : null,
904
+ react.createElement("pre", { style: S.pre }, s.content || "(空)"),
905
+ );
906
+ }),
907
+ );
908
+ }
909
+
910
+ // ── Tab:L3 画像 ──
911
+ function PersonaTab(props) {
912
+ var rpc = props.rpc;
913
+ var contentState = react.useState(null);
914
+ var content = contentState[0];
915
+ var setContent = contentState[1];
916
+
917
+ var load = react.useCallback(function () {
918
+ rpc("dsh-memory/persona", {})
919
+ .then(function (r) { if (r && r.ok) setContent(r.value.content); })
920
+ .catch(function () {});
921
+ }, [rpc]);
922
+
923
+ react.useEffect(function () { load(); }, [load]);
924
+
925
+ return react.createElement(
926
+ "div", null,
927
+ react.createElement(
928
+ "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
929
+ react.createElement("span", { style: S.muted },
930
+ content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像"),
931
+ react.createElement("div", { style: S.grow }),
932
+ react.createElement("button", { style: S.button, onClick: load }, "刷新"),
933
+ ),
934
+ content
935
+ ? react.createElement("pre", { style: S.pre }, content)
936
+ : content === null
937
+ ? null
938
+ : react.createElement("p", { style: S.intro }, "画像尚未生成;蒸馏若干记忆后 L3 会自动产出。"),
939
+ );
940
+ }
941
+
942
+ // ── Tab:运行日志 ──
943
+ function LogTab(props) {
944
+ var rpc = props.rpc;
945
+ var linesState = react.useState(null);
946
+ var lines = linesState[0];
947
+ var setLines = linesState[1];
948
+
949
+ var load = react.useCallback(function () {
950
+ rpc("dsh-memory/log-tail", { lines: 200 })
951
+ .then(function (r) { if (r && r.ok) setLines(r.value.lines); })
952
+ .catch(function () { setLines([]); });
953
+ }, [rpc]);
954
+
955
+ react.useEffect(function () { load(); }, [load]);
956
+
957
+ return react.createElement(
958
+ "div", null,
959
+ react.createElement(
960
+ "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
961
+ react.createElement("span", { style: S.muted }, lines === null ? "加载中…" : "最近 " + lines.length + " 行(memory.log)"),
962
+ react.createElement("div", { style: S.grow }),
963
+ react.createElement("button", { style: S.button, onClick: load }, "刷新"),
964
+ ),
965
+ react.createElement("pre", { style: S.pre }, (lines || []).join("\n") || "(暂无日志)"),
966
+ );
967
+ }
968
+
969
+ // ── 主面板:Tab 框架 ──
970
+ var TABS = [
971
+ ["overview", "概览"],
972
+ ["records", "记忆"],
973
+ ["scenes", "场景"],
974
+ ["persona", "画像"],
975
+ ["log", "日志"],
976
+ ];
977
+
978
+ function MemoryPanel(props) {
979
+ var rpc = props.rpc;
980
+ var tabState = react.useState("overview");
981
+ var tab = tabState[0];
982
+ var setTab = tabState[1];
983
+
984
+ var body;
985
+ if (tab === "overview") body = react.createElement(OverviewTab, { rpc: rpc });
986
+ else if (tab === "records") body = react.createElement(RecordsTab, { rpc: rpc });
987
+ else if (tab === "scenes") body = react.createElement(ScenesTab, { rpc: rpc });
988
+ else if (tab === "persona") body = react.createElement(PersonaTab, { rpc: rpc });
989
+ else body = react.createElement(LogTab, { rpc: rpc });
990
+
991
+ return react.createElement(
992
+ "div",
993
+ { style: S.section },
994
+ react.createElement("h2", { style: S.heading }, "记忆 (Memory)"),
995
+ react.createElement(
996
+ "p", { style: S.intro },
997
+ "L0~L3 分层蒸馏记忆:浏览被记住的内容,控制记忆模式开关。",
998
+ ),
999
+ react.createElement(
1000
+ "div", { style: S.tabbar },
1001
+ TABS.map(function (t) {
1002
+ return react.createElement(
1003
+ "button",
1004
+ {
1005
+ key: t[0],
1006
+ style: Object.assign({}, S.tab, tab === t[0] ? S.tabActive : null),
1007
+ onClick: function () { setTab(t[0]); },
1008
+ },
1009
+ t[1],
1010
+ );
1011
+ }),
1012
+ ),
1013
+ body,
1014
+ );
1015
+ }
1016
+
1017
+ // ── 插件主体:注册设置页 + 输入栏档位控件 ──
1018
+ function makeRpc(ctx) {
1019
+ return function (endpoint, payload) {
1020
+ if (!ctx.connection || !ctx.connection.rpc) {
1021
+ return Promise.reject(new Error("connection 服务不可用"));
1022
+ }
1023
+ return ctx.connection.rpc.call("/rpc", endpoint, payload || {});
1024
+ };
1025
+ }
1026
+
1027
+ function apply(ctx) {
1028
+ var rpc = makeRpc(ctx);
1029
+
1030
+ // 设置 → 记忆:状态页(浏览器保持两族混合视图)
1031
+ ctx.slots.inject("settings.section", function () {
1032
+ return ctx.slots.register(
1033
+ {
1034
+ name: "settings.section",
1035
+ id: "dsh-memory",
1036
+ order: 200,
1037
+ label: "记忆",
1038
+ inject: function () {
1039
+ return { rpc: rpc };
1040
+ },
1041
+ },
1042
+ MemoryPanel,
1043
+ );
1044
+ });
1045
+
1046
+ // 输入栏(模式选择器右侧):会话记忆档位 pill + 滑动选择器
1047
+ ctx.slots.inject("conversation.input.left", function () {
1048
+ return ctx.slots.register(
1049
+ {
1050
+ name: "conversation.input.left",
1051
+ id: "dsh-memory-mode",
1052
+ order: 100,
1053
+ inject: function (sessionId) {
1054
+ return { sessionId: sessionId, rpc: rpc };
1055
+ },
1056
+ },
1057
+ MemoryModePill,
1058
+ );
1059
+ });
1060
+ }
1061
+
1062
+ exports.apply = apply;
1063
+ exports.inject = inject;
1064
+ return module.exports;
1065
+ },
1066
+ });