dsh-lite-memory 0.2.4 → 0.2.6

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/README.md CHANGED
@@ -16,17 +16,21 @@ Lightweight memory for the DeepSeek Harness. Nothing in the system prompt, nothi
16
16
  | 场景 | 行为 |
17
17
  | --- | --- |
18
18
  | **L0** 你明确要求「记住这个」 | 立即写盘,一条确认行 |
19
- | **L1** 它发现有跨会话价值的经验 | 一行灰字 `memory · keep "X"?` + 一个 `keep` 按钮;不写问句、不用你打字,不点就永远不写,48 小时内不重复 |
19
+ | **L1** 它发现有跨会话价值的经验 | 一行灰字 `memory · keep? "X" — 要记的内容` + 一个 `keep` 按钮;标题加一句话,看得懂才点,不点就永远不写,48 小时内不重复 |
20
20
  | **L2** 常识、一次性参数、凭据的值 | 永不写入 |
21
21
 
22
+ 这些行只属于产生它的那一轮:重进会话就没了,历史里只留正文。
23
+
22
24
  触发词:`记住`、`记一下`、`上次`、`规范`、`约定`、`路径`、`凭据怎么取`。
23
25
 
24
26
  | Case | Behaviour |
25
27
  | --- | --- |
26
28
  | **L0** you ask it to remember | written immediately, one confirmation line |
27
- | **L1** it spots something worth keeping | one muted line `memory · keep "X"?` with a single `keep` button — never a question, no typing, nothing written unless you click, at most once per two days |
29
+ | **L1** it spots something worth keeping | one muted line `memory · keep? "X" — what would be remembered` with a single `keep` button — readable before you click, nothing written unless you click, at most once per two days |
28
30
  | **L2** common sense, one-off parameters, credential values | never written |
29
31
 
32
+ Those lines belong to the turn that made them: reopening the session drops them, leaving only the prose in history.
33
+
30
34
  ## 点一下就存 / One click, no turn
31
35
 
32
36
  `keep` 是同源直写:不产生消息、不动输入框、不打断当前回复。按钮变 `kept` 就结束了。自动写入的行尾有个 `✕`,点它就把刚才那条移入归档。
package/TRIGGERS.md CHANGED
@@ -22,15 +22,17 @@
22
22
  每次调用只占一行、灰色、12px:
23
23
 
24
24
  ```
25
- · memory · 2 hits · 96 chars
25
+ · memory · 2 hits
26
26
  · memory · no hits
27
- · memory · saved "插件市场条目规范" ✕
28
- · memory · keep "提交前先跑文案 lint" [keep]
27
+ · memory · saved "插件市场条目字段" — 目录条目要写应用名、一句话作用、图标地址 ✕
28
+ · memory · keep? "发布前先跑文案检查" — 界面文字只留标签、值和动作,交付前跑脚本清零 [keep]
29
29
  · memory · skipped
30
- · memory · dropped "插件市场条目规范"
30
+ · memory · dropped "插件市场条目字段"
31
31
  ```
32
32
 
33
- `keep` 按钮只在建议行出现。点一下直接写盘:不产生消息、不动输入框、不占用下一轮对话,按钮变 `kept` 并禁用。同源写盘通道不通时才退回旧路——插入 `保留记忆「X」` 并排队,至少不丢这条建议。自动写入的行尾带 `✕`,点了把那一条移入归档(不删文件,可从归档取回)。不点就什么都不发生,那行过一会儿只是一段灰色历史。
33
+ `keep` 按钮只在建议行出现。点一下直接写盘:不产生消息、不动输入框、不占用下一轮对话,按钮变 `kept` 并禁用。同源写盘通道不通时才退回旧路——插入 `保留记忆「X」` 并排队,至少不丢这条建议。自动写入的行尾带 `✕`,点了把那一条移入归档(不删文件,可从归档取回)。
34
+
35
+ 这些行只属于产生它的那一轮:重进会话时不再显示,历史里只留正文。行出现在调用本身的位置——一轮正在跑时,那就是对话的最下面,不会悬浮在输入框上方。
34
36
 
35
37
  ## 频率与退让
36
38
 
package/client.js CHANGED
@@ -13,7 +13,7 @@ window.__ModuleLoader__.load({
13
13
  memory_forget: 'memory · dropping',
14
14
  };
15
15
 
16
- const KEEP = { idle: 'keep', saving: 'saving', saved: 'kept', kept: 'kept' };
16
+ const KEEP = { idle: 'keep', saving: 'saving', saved: 'kept' };
17
17
  const ROW = { display: 'flex', alignItems: 'center', gap: '6px', padding: '1px 0', fontSize: '12px', lineHeight: '1.6', opacity: 0.7 };
18
18
  const DOT = { flex: '0 0 auto', opacity: 0.8 };
19
19
  const LABEL = { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
@@ -41,6 +41,13 @@ window.__ModuleLoader__.load({
41
41
  return match ? match[1] : null;
42
42
  };
43
43
 
44
+ /** 只在产生这行的那一轮显示。 */
45
+ const useLive = (phase) => {
46
+ const live = React.useRef(phase !== 'result');
47
+ if (phase !== 'result') live.current = true;
48
+ return live.current;
49
+ };
50
+
44
51
  const post = (payload) => {
45
52
  if (typeof fetch !== 'function') return Promise.resolve(null);
46
53
  return fetch('/lite-memory/keep', {
@@ -53,37 +60,24 @@ window.__ModuleLoader__.load({
53
60
  };
54
61
 
55
62
  const Row = (props) => {
63
+ if (!useLive(props.phase)) return null;
56
64
  const line = props.phase === 'preparing' ? null : firstLine(settledText(props.block));
57
65
  return h('div', { 'data-tool': props.toolName, style: ROW }, h('span', { style: DOT }, '·'), h('span', { style: LABEL }, line ?? RUNNING[props.toolName] ?? 'memory'));
58
66
  };
59
67
 
60
68
  /** 建议行:一个「keep」按钮,点了直接写盘。 */
61
69
  const SuggestRow = (props) => {
70
+ if (!useLive(props.phase)) return null;
62
71
  const [state, setState] = React.useState('idle');
63
72
  const text = props.phase === 'preparing' ? null : settledText(props.block);
64
73
  const label = firstLine(text) ?? RUNNING.memory_suggest;
65
74
  const title = quoted(text);
66
75
  const cwd = typeof props.cwd === 'string' ? props.cwd : '';
67
- const actions = props.inputActions;
68
76
  const canKeep = Boolean(title) && Boolean(cwd) && state === 'idle';
69
77
  const keep = () => {
70
78
  if (!canKeep) return;
71
79
  setState('saving');
72
- /** 写盘通道不通时退回一条普通消息,至少不丢这条建议。 */
73
- const fallback = () => {
74
- try {
75
- const message = `保留记忆「${title}」`;
76
- const span = actions && typeof actions.captureInsertion === 'function' ? actions.captureInsertion() : null;
77
- const inserted = span && actions && typeof actions.insertText === 'function' ? actions.insertText(message, span) : false;
78
- if (inserted === false && actions && typeof actions.setDraft === 'function') actions.setDraft(message);
79
- if (actions && typeof actions.submit === 'function') actions.submit();
80
- setState('kept');
81
- } catch {
82
- setState('idle');
83
- }
84
- };
85
- if (typeof fetch !== 'function') return fallback();
86
- post({ op: 'keep', title, cwd }).then((value) => setState(value && value.ok ? 'saved' : 'kept'));
80
+ post({ op: 'keep', title, cwd }).then((value) => setState(value && value.ok ? 'saved' : 'idle'));
87
81
  };
88
82
  const button = title
89
83
  ? h('button', { type: 'button', onClick: keep, disabled: !canKeep, 'aria-label': '保留这条记忆', style: canKeep ? KEEP_ON : KEEP_OFF }, KEEP[state] ?? KEEP.idle)
@@ -93,6 +87,7 @@ window.__ModuleLoader__.load({
93
87
 
94
88
  /** 自动写入行:一个「✕」,点了把它移入归档。 */
95
89
  const SavedRow = (props) => {
90
+ if (!useLive(props.phase)) return null;
96
91
  const [state, setState] = React.useState('idle');
97
92
  const text = props.phase === 'preparing' ? null : settledText(props.block);
98
93
  const title = quoted(text);
package/index.js CHANGED
@@ -111,8 +111,8 @@ function keepHandler(webServer) {
111
111
  const SAVE_PARAMS = {
112
112
  type: 'object',
113
113
  properties: {
114
- title: { type: 'string', description: `标题,名词短语,不超过 ${LIMITS.titleChars} 字` },
115
- summary: { type: 'string', description: `一句话摘要,名词短语,不超过 ${LIMITS.summaryChars} 字` },
114
+ title: { type: 'string', description: `标题:名词短语,用户一眼能认出是哪件事,不超过 ${LIMITS.titleChars} 字` },
115
+ summary: { type: 'string', description: `一句话说清要记的内容本身——用户只看这一行决定留不留,名词短语,不超过 ${LIMITS.summaryChars} 字` },
116
116
  body: { type: 'string', description: '正文 markdown。只写指针与要点,不抄录长文档' },
117
117
  kind: { type: 'string', enum: ['preference', 'convention', 'procedure', 'pointer', 'pitfall', 'credential_hint', 'note'], description: '条目类型' },
118
118
  tags: { type: 'array', items: { type: 'string' }, description: '最多 5 个标签' },
@@ -164,20 +164,21 @@ export function apply(ctx) {
164
164
  properties: {
165
165
  id: { type: 'string' },
166
166
  title: { type: 'string' },
167
+ summary: { type: 'string' },
167
168
  merged: { type: 'boolean' },
168
169
  },
169
- required: ['id', 'title', 'merged'],
170
+ required: ['id', 'title', 'summary', 'merged'],
170
171
  additionalProperties: false,
171
172
  },
172
173
  render: (value) => [{
173
174
  type: 'text',
174
- text: value.merged ? `memory · merged "${value.title}"` : `memory · saved "${value.title}"`,
175
+ text: `${value.merged ? 'memory · merged' : 'memory · saved'} "${value.title}"${value.summary ? ` — ${value.summary}` : ''}`,
175
176
  }],
176
177
  },
177
178
  execute: async (args, execution) => {
178
179
  const scope = scopeOf(cwdOf(execution));
179
180
  const saved = upsert(scope, args);
180
- return { id: saved.record.id, title: saved.record.title, merged: saved.merged };
181
+ return { id: saved.record.id, title: saved.record.title, summary: saved.record.summary, merged: saved.merged };
181
182
  },
182
183
  }), 'lite-memory: memory_save');
183
184
 
@@ -191,22 +192,26 @@ export function apply(ctx) {
191
192
  properties: {
192
193
  asked: { type: 'boolean' },
193
194
  title: { type: 'string' },
195
+ summary: { type: 'string' },
194
196
  reason: { type: 'string' },
195
197
  },
196
- required: ['asked', 'title'],
198
+ required: ['asked', 'title', 'summary'],
197
199
  additionalProperties: false,
198
200
  },
199
201
  render: (value) => [{
200
202
  type: 'text',
201
- text: value.asked ? `memory · keep "${value.title}"?` : 'memory · skipped',
203
+ text: value.asked
204
+ ? `memory · keep? "${value.title}"${value.summary ? ` — ${value.summary}` : ''}`
205
+ : 'memory · skipped',
202
206
  }],
203
207
  },
204
208
  execute: async (args, execution) => {
205
209
  const scope = scopeOf(cwdOf(execution));
206
210
  const gate = suggestionGate(scope, args.title);
207
- if (!gate.ok) return { asked: false, title: String(args.title ?? ''), reason: gate.reason };
211
+ const summary = String(args.summary ?? '');
212
+ if (!gate.ok) return { asked: false, title: String(args.title ?? ''), summary, reason: gate.reason };
208
213
  noteSuggestion(scope, args, 'asked');
209
- return { asked: true, title: String(args.title ?? '') };
214
+ return { asked: true, title: String(args.title ?? ''), summary };
210
215
  },
211
216
  }), 'lite-memory: memory_suggest');
212
217
 
@@ -227,7 +232,7 @@ export function apply(ctx) {
227
232
  },
228
233
  render: (value) => [{
229
234
  type: 'text',
230
- text: value.count === 0 ? 'memory · no hits' : `memory · ${value.count} hits · ${value.chars} chars\n${value.index}`,
235
+ text: value.count === 0 ? 'memory · no hits' : `memory · ${value.count} hits\n${value.index}`,
231
236
  }],
232
237
  },
233
238
  execute: async (args, execution) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-lite-memory",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "On-demand memory for the DeepSeek Harness: nothing in the system prompt, one muted line only when something is saved, hit or suggested.",
5
5
  "type": "module",
6
6
  "main": "index.js",