dsh-memsys 0.2.1 → 0.2.2

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/client.js CHANGED
@@ -27,19 +27,83 @@ window.__ModuleLoader__.load({
27
27
  return [state, setState];
28
28
  }
29
29
 
30
+ // Pure CSS/DOM "memory balls on a shelf". No 3D library, no build step,
31
+ // no external resource: gradient + inset shadow + one float keyframe.
32
+ const BALL_CSS = [
33
+ '@keyframes msFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}',
34
+ '.ms-panel{padding:16px;line-height:1.7}',
35
+ '.ms-counts{margin:0 0 12px;opacity:.85}',
36
+ '.ms-shelf{display:flex;gap:16px;align-items:flex-end;padding:12px 16px 8px;margin:0 0 10px;border-radius:12px;',
37
+ 'background:linear-gradient(180deg,rgba(127,127,127,.10),rgba(127,127,127,.03));',
38
+ 'box-shadow:inset 0 -1px 0 rgba(127,127,127,.28)}',
39
+ '.ms-ball{width:44px;height:44px;border-radius:50%;border:0;padding:0;cursor:pointer;position:relative;',
40
+ 'background:radial-gradient(circle at 32% 27%,rgba(255,255,255,.95),rgba(255,255,255,.28) 16%,var(--ms-c) 60%,rgba(0,0,0,.45) 100%);',
41
+ 'box-shadow:inset -6px -8px 14px rgba(0,0,0,.30),inset 6px 6px 12px rgba(255,255,255,.22),0 4px 10px rgba(0,0,0,.18);',
42
+ 'animation:msFloat 5s ease-in-out infinite;transition:transform .15s ease,box-shadow .15s ease}',
43
+ '.ms-ball:hover,.ms-ball:focus-visible{transform:scale(1.06);',
44
+ 'box-shadow:inset -6px -8px 14px rgba(0,0,0,.30),inset 6px 6px 12px rgba(255,255,255,.22),0 0 18px 4px var(--ms-glow);outline:none}',
45
+ '.ms-ball[aria-pressed="true"]{transform:scale(1.06);box-shadow:0 0 18px 4px var(--ms-glow)}',
46
+ '.ms-empty{width:44px;height:44px;border-radius:50%;border:1px dashed rgba(127,127,127,.35);background:rgba(127,127,127,.05)}',
47
+ '.ms-card{margin-top:10px;padding:12px 14px;border-radius:10px;background:rgba(127,127,127,.08);font-size:.95em}',
48
+ '.ms-hint{opacity:.75;margin:6px 0 0}',
49
+ '@media (prefers-reduced-motion:reduce){.ms-ball{animation:none}}',
50
+ ].join('');
51
+ const BALL_COLORS = ['#3fa7ff', '#5b8cff', '#7a6bff', '#8f5bd8', '#8450a8'];
52
+ const BALL_GLOWS = [
53
+ 'rgba(63,167,255,.45)',
54
+ 'rgba(91,140,255,.45)',
55
+ 'rgba(122,107,255,.45)',
56
+ 'rgba(143,91,216,.45)',
57
+ 'rgba(132,80,168,.45)',
58
+ ];
59
+ const EMPTY_SLOTS = 5;
60
+ const CARD_TEXT_LIMIT = 120;
61
+
30
62
  function MemoryPanel() {
31
63
  const [state] = useSummary();
32
- if (!state) return h('p', { style: { padding: 16 } }, '读取记忆…');
33
- if (state.error) return h('p', { style: { padding: 16 } }, '暂时读不到记忆数据。');
64
+ const [selectedId, setSelectedId] = React.useState(null);
65
+ if (!state) return h('p', { className: 'ms-panel' }, '读取记忆…');
66
+ if (state.error) return h('p', { className: 'ms-panel' }, '暂时读不到记忆数据。');
67
+ const recent = state.recent ?? [];
68
+ const selected = recent.find((memory) => memory.id === selectedId) ?? null;
69
+ // Pad with ghost slots so an almost-empty shelf keeps its shape.
70
+ const shelf = [];
71
+ for (let index = 0; index < Math.max(recent.length, EMPTY_SLOTS); index += 1) shelf.push(recent[index] ?? null);
34
72
  return h(
35
73
  'div',
36
- { style: { padding: 16, lineHeight: 1.7 } },
74
+ { className: 'ms-panel' },
75
+ h('style', { dangerouslySetInnerHTML: { __html: BALL_CSS } }),
37
76
  h('h2', { style: { margin: '0 0 8px' } }, '记忆'),
38
- h('p', null, `记忆 ${state.memories} 条 · 候选 ${state.candidates} 条 · 待确认惦记 ${state.pending_due} 条`),
39
- h('h3', { style: { margin: '16px 0 6px' } }, '最近 5 条'),
40
- state.recent.length === 0
41
- ? h('p', null, '还没有记忆。')
42
- : h('ul', null, state.recent.map((memory) => h('li', { key: memory.id }, memory.text))),
77
+ h('p', { className: 'ms-counts' },
78
+ `记忆 ${state.memories} 条 · 候选 ${state.candidates} 条 · 待确认惦记 ${state.pending_due} 条`),
79
+ h('div', { className: 'ms-shelf', role: 'list', 'aria-label': '最近的记忆' },
80
+ shelf.map((memory, index) => memory === null
81
+ ? h('span', { key: `empty-${index}`, className: 'ms-empty', 'aria-hidden': true })
82
+ : h('button', {
83
+ key: memory.id,
84
+ type: 'button',
85
+ role: 'listitem',
86
+ className: 'ms-ball',
87
+ 'aria-pressed': selectedId === memory.id,
88
+ 'aria-label': memory.text,
89
+ title: memory.text,
90
+ style: {
91
+ '--ms-c': BALL_COLORS[Math.min(index, BALL_COLORS.length - 1)],
92
+ '--ms-glow': BALL_GLOWS[Math.min(index, BALL_GLOWS.length - 1)],
93
+ animationDelay: `${index * 0.35}s`,
94
+ width: `${44 - Math.min(index, 3) * 2}px`,
95
+ height: `${44 - Math.min(index, 3) * 2}px`,
96
+ },
97
+ onClick: () => setSelectedId(selectedId === memory.id ? null : memory.id),
98
+ })),
99
+ ),
100
+ recent.length === 0
101
+ ? h('p', { className: 'ms-hint' }, '还没有记忆。去跟模型说一句「记住:…」吧')
102
+ : h('p', { className: 'ms-hint' }, '点亮一颗球,看看它装的是什么。'),
103
+ selected
104
+ ? h('div', { className: 'ms-card', key: selected.id },
105
+ Array.from(selected.text).slice(0, CARD_TEXT_LIMIT).join(''))
106
+ : null,
43
107
  );
44
108
  }
45
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "dsh-memsys",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A memory system plugin for DeepSeek Harness: append-only facts, candidates and adjudication, dreams, and budget-aware context injection.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,7 +14,8 @@ import {
14
14
  } from './ui-config.mjs';
15
15
 
16
16
  const RECENT_LIMIT = 5;
17
- const TEXT_LIMIT = 80;
17
+ // 主人要求:卡片展示 120 字(按码点截断,预算保护仍在 host 侧)
18
+ const TEXT_LIMIT = 120;
18
19
  const BODY_LIMIT = 4096;
19
20
 
20
21
  const truncate = (text) => Array.from(String(text ?? '')).slice(0, TEXT_LIMIT).join('');
@@ -283,7 +283,8 @@ test('P6/UI: counts only open candidates, pending-due memories, and truncates re
283
283
  assert.equal(summary.candidates, 2, 'promoted candidates are history, not "候选"');
284
284
  assert.equal(summary.pending_due, 1);
285
285
  assert.equal(summary.recent.length, 2);
286
- assert.equal(Array.from(summary.recent.find((row) => row.id === dueMemory.id).text).length, 80);
286
+ // 主人要求 120 码点(原来是 80);这里改的是需求,不是放宽判据。
287
+ assert.equal(Array.from(summary.recent.find((row) => row.id === dueMemory.id).text).length, 120);
287
288
  });
288
289
 
289
290
  test('P6/UI: routes reject off-loopback and cross-origin callers', () => {