dsh-engramory 1.0.0 → 1.0.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/README.en.md +218 -0
- package/README.md +135 -326
- package/index.js +2692 -2563
- package/package.json +79 -78
- package/README.zh-CN.md +0 -200
package/README.md
CHANGED
|
@@ -1,326 +1,135 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# dsh-engramory
|
|
4
|
-
|
|
5
|
-
[](https://unstone.github.io/dsh-xray/registry.html#tinqiao-oss__engramory)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
nothing.
|
|
137
|
-
- **Someone else's conversation gets no memory:** no index, a note saying so, no full path
|
|
138
|
-
to the store (the skill, which every conversation reads, gives none in social mode; your
|
|
139
|
-
own conversations are told it in their runtime context), and file tools that may name
|
|
140
|
-
files only. A directory is refused (so grep, glob and directory
|
|
141
|
-
views are — any tree may hold a store), and so is any path into a store, however it is
|
|
142
|
-
spelled or linked, including a store that does not exist yet, so none can be planted
|
|
143
|
-
for you. A subagent is yours while its live parent is — read afresh on every call —
|
|
144
|
-
and nobody else has written to it directly; a forked conversation is judged by its own
|
|
145
|
-
history, the part it inherited included. A call with no session, a
|
|
146
|
-
dsh without session projections, or a network path counts as someone else's.
|
|
147
|
-
|
|
148
|
-
This keeps the user's memory out of others' context by the routes dsh's own file tools
|
|
149
|
-
take. It is **not** a security boundary: a shell tool reads any file, and a different
|
|
150
|
-
profile on the same account can still read the store's files. Keeping others away from
|
|
151
|
-
the store needs filesystem permissions or a sandbox.
|
|
152
|
-
|
|
153
|
-
### For bridge authors: marking who wrote a message
|
|
154
|
-
|
|
155
|
-
With social mode on, every message a bridge submits counts as someone else's. For the
|
|
156
|
-
owner's own chat to get the memory — their private QQ chat with the character, say —
|
|
157
|
-
put a `speaker` on the message's source:
|
|
158
|
-
|
|
159
|
-
```js
|
|
160
|
-
agent.followup(createUserMessage({
|
|
161
|
-
content: [{ type: 'text', text }],
|
|
162
|
-
source: { kind: 'plugin:your-bridge', speaker: { role: 'owner' } },
|
|
163
|
-
}))
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
| `speaker.role` | Meaning |
|
|
167
|
-
|---|---|
|
|
168
|
-
| `owner` | The dsh owner wrote it: a message from the account the user set as theirs in your bridge. |
|
|
169
|
-
| `guest` | Someone else wrote it — the same as no `speaker` at all. |
|
|
170
|
-
| `none` | Nobody wrote it: your plugin's own report, reminder or notice. It does not turn the owner's conversation into someone else's. |
|
|
171
|
-
|
|
172
|
-
Mark a message `owner` only when it comes from the owner's own account. A group's
|
|
173
|
-
conversation stays someone else's once anyone else in it speaks. dsh keeps unknown
|
|
174
|
-
source fields in the session log, so the role survives a restart.
|
|
175
|
-
|
|
176
|
-
A program that talks to dsh only through dsh web's API cannot set a source, and its
|
|
177
|
-
messages count as someone else's — unless it sends `clientTimeZone` with a prompt: dsh
|
|
178
|
-
then records exactly what its own page sends, and the message counts as the owner's. A
|
|
179
|
-
program forwarding other people's messages must leave `clientTimeZone` out.
|
|
180
|
-
|
|
181
|
-
## Settings page
|
|
182
|
-
|
|
183
|
-
On dsh's **Plugins** page, open *Engramory memory*: its page sets companion mode (a
|
|
184
|
-
switch) and, while it is on, its memory access (private or social), and the index cap.
|
|
185
|
-
The memory's language follows dsh's own (see `language` below).
|
|
186
|
-
|
|
187
|
-
Saving goes through dsh's own profile editor: it validates the whole config, writes it
|
|
188
|
-
to your profile's `cordis.patch.yml`, and reloads the plugin with it — or leaves the
|
|
189
|
-
file as it was. A save made against settings that changed elsewhere in the meantime is
|
|
190
|
-
refused, and the page reads them again. Settings put back to their defaults remove
|
|
191
|
-
their override instead of pinning it. Other fields (`indexName`, `indexPath`, …) stay
|
|
192
|
-
in the file.
|
|
193
|
-
|
|
194
|
-
Only you reach the page's route — your signed-in browser, or a program holding your
|
|
195
|
-
login: dsh answers 401 to a request without it and 403 to a cross-site one, so an agent
|
|
196
|
-
cannot call it to switch social mode off. A dsh without the profile editor shows the page read-only.
|
|
197
|
-
Companion mode and its memory access are also at the top of the memory page (below).
|
|
198
|
-
|
|
199
|
-
## Memory page
|
|
200
|
-
|
|
201
|
-
dsh's sidebar gets a **Memory** entry. Its page lists the stores recall can use — the
|
|
202
|
-
global store under `$DSH_HOME` and each workspace's project store, found as recall finds
|
|
203
|
-
one, or, with `indexPath` pinned, that index's directory alone — with each index's size
|
|
204
|
-
against the cap, its notes' names by type, and its archive. It is read-only and shows
|
|
205
|
-
names only, never a note's text: a name opens the note in your computer's default app,
|
|
206
|
-
through dsh's own opener, on the machine dsh runs on. Where dsh says it cannot open there
|
|
207
|
-
(over SSH, say), the names are plain text and the page shows where the files are.
|
|
208
|
-
*Open the index* opens the index the same way; *Open the folder* shows the index in your
|
|
209
|
-
file manager (Explorer and Finder select it in the store's folder; on Linux the folder
|
|
210
|
-
opens), so a store with no index has no such button. At the top, companion mode and its
|
|
211
|
-
memory access save as they are switched; what they mean, and the index cap, are on the
|
|
212
|
-
settings page.
|
|
213
|
-
|
|
214
|
-
The listing is bounded — 2000 entries looked at per folder, 200 workspaces, 5000 names in
|
|
215
|
-
all — and never looks into a workspace on a network share path (`\\server\share\…`), or
|
|
216
|
-
one whose folder has since become a link to somewhere else. A note that is a link is not
|
|
217
|
-
listed; what is opened is a note or the index found again, by name, directly in its store,
|
|
218
|
-
and refused if it has become a link. A relative `indexPath` follows each conversation's
|
|
219
|
-
directory, so the page cannot list it. Only you reach the page's route, as the settings
|
|
220
|
-
page's.
|
|
221
|
-
|
|
222
|
-
## Configuration
|
|
223
|
-
|
|
224
|
-
| Field | Default | Meaning |
|
|
225
|
-
|---|---|---|
|
|
226
|
-
| `indexName` | `MEMORY.md` | Basename treated as the memory index (case-insensitive; both `file_path` and `str_replace_editor`'s `path` are checked) — the name a write is called by, or the name of the file it lands on, so a symlink under another name is caught too. An empty or non-string value falls back to the default, and so does one that is not a plain file name (a path separator, a `:`, `.` or `..`). |
|
|
227
|
-
| `maxLines` | `200` | Hard line cap. Non-positive or non-finite values fall back to the default. |
|
|
228
|
-
| `maxBytes` | `25600` | Hard UTF-8 byte cap (25 KB). |
|
|
229
|
-
| `indexPath` | unset | Path of the ONE index to guard: absolute, or relative to each session's working directory (resolved there by the guard as by recall; the memory page lists only an absolute pin). Without it the guard matches on basename alone, so an unrelated `MEMORY.md` in another project is gated too; set this and only that exact file is. Compared by identity — the file the write lands on, whatever it is called (symlinks and `..` resolved; case-folded on Windows only) — and a path that does not exist yet still has its first write guarded. |
|
|
230
|
-
| `registerSkill` | `true` | Set `false` to keep the cap but skip the runtime skill. |
|
|
231
|
-
| `injectIndex` | `true` | Put the memory index into dsh's runtime context on every step (see above). Set `false` to recall by reading the file instead. |
|
|
232
|
-
| `language` | dsh's | Unset (or empty), the memory follows dsh's language — the one chosen in dsh's Settings → General, else the language of the system dsh runs on (dsh's page, with no choice made, follows the browser's, so on a remote `dsh web` the two can differ); a change applies once dsh restarts. Set it to pin one: `zh` writes the store in Chinese (SKILL.md §1.1): Chinese note names, `## 用户` / `## 反馈` / `## 项目` / `## 参考` headings, `**为什么:**` / `**怎么用:**` labels, and the skill and the injected index in Chinese. Frontmatter keys and `type:` values stay English, so every host still reads the store. The cap's refusals stay English. `zh` and its regional forms (`zh-CN`, `zh-TW`, …) select Chinese; any other string selects `en`. |
|
|
233
|
-
| `companion` | `false` | `true` turns companion mode on (above): three more types for the character's own life, and recall in character. Only the boolean `true` counts. |
|
|
234
|
-
| `audience` | `private` | With companion mode on, `social` turns social mode on (above): only your own conversations get the memory. Any other value is `private`, where every conversation does. Without companion mode it changes nothing. |
|
|
235
|
-
| `skill` | built-in | Replace the skill body with your own markdown — which must then say where the store is itself. |
|
|
236
|
-
|
|
237
|
-
## What the guard actually does
|
|
238
|
-
|
|
239
|
-
| Call | Decision |
|
|
240
|
-
|---|---|
|
|
241
|
-
| `write` (or `str_replace_editor` `create`) ending within caps | allow |
|
|
242
|
-
| `write`/`create` that GROWS the index past a cap | **deny**, naming the numbers and what to compact |
|
|
243
|
-
| `write`/`create` that SHRINKS or keeps an over-cap index | allow — incremental compaction (210 → 205 → 198) must stay possible |
|
|
244
|
-
| Edit carrying `old_str`/`new_str`: result simulated | judged by the RESULT, same grow/shrink rule as a write — sized before it is built, so a `replace_all` whose result would be too long to build is judged on lower bounds of its bytes and lines |
|
|
245
|
-
| Unsimulable partial (e.g. `insert`) on an over-cap index | **deny**, telling the agent a shrinking whole-file write passes |
|
|
246
|
-
| Unsimulable partial on a healthy index | allow |
|
|
247
|
-
| `read`, `view`, or any unknown tool | allow — recall must never be blocked, even over cap |
|
|
248
|
-
| Any write to any other file | allow — not the guard's business |
|
|
249
|
-
| Missing/unreadable current index on a whole-file write | treated as EMPTY (mirrors the Python guard) — a within-caps write passes, an over-cap first write is refused |
|
|
250
|
-
| Missing/unreadable index on a partial edit | allow — a guard must never block work over a path it cannot read |
|
|
251
|
-
| A FIFO, device, or directory under the index's name | never read — counts as unreadable (a read there would hang or flood the host, which runs guards on its main thread) |
|
|
252
|
-
| An index past the read limit (`min(16 MiB, max(1 MiB, 4 × maxBytes))`) | not read whole: a whole-file write is judged on bytes against the filesystem's size (every shrink passes; its line count is unknown, so lines are not capped there), and an edit passes unmeasured, as on an unreadable index |
|
|
253
|
-
| Malformed execution (no arguments, non-string path/content) | allow — not recognisably a write |
|
|
254
|
-
|
|
255
|
-
For measured results, deny only a dimension that is over its cap AND grew past
|
|
256
|
-
the current file. The line count ignores a trailing newline, so an index exactly
|
|
257
|
-
at the cap stays writable. This decision table is pinned by `node --test` in CI.
|
|
258
|
-
|
|
259
|
-
Relative paths use the session's `workingDirectory` service when available,
|
|
260
|
-
falling back to the process cwd. Edits match LF-normalised text and preserve the
|
|
261
|
-
file's line endings when sizing the result, including CRLF bytes.
|
|
262
|
-
|
|
263
|
-
## Versions
|
|
264
|
-
|
|
265
|
-
This package carries Engramory's version: from 1.0.0, `dsh-engramory@X.Y.Z` is the
|
|
266
|
-
same release as the repo's tag `vX.Y.Z` (before, it had its own numbers, 0.1.0–0.3.0).
|
|
267
|
-
Companion mode, social mode (its file refusals and the `speaker` stamp included), and
|
|
268
|
-
the settings and memory pages are experimental and may change in a minor version; the
|
|
269
|
-
index cap's decisions (the table above) and the other settings change incompatibly only
|
|
270
|
-
in a major one. Details in Engramory's
|
|
271
|
-
[Versioning](https://github.com/tinqiao-oss/engramory#versioning).
|
|
272
|
-
|
|
273
|
-
## Known limits
|
|
274
|
-
|
|
275
|
-
- **Unsimulable partial edits can cross the cap from below.** On a readable,
|
|
276
|
-
already-over-cap index they are denied; use a shrinking whole-file write to
|
|
277
|
-
compact. Missing/unreadable files and the read-limit exceptions are listed above.
|
|
278
|
-
Run `engramory_check.py` after index edits and `engramory_doctor.py` periodically.
|
|
279
|
-
- **Coverage is limited to known file tools:** `write`/`edit` with `file_path`,
|
|
280
|
-
`str_replace`/`insert`, and mutating `str_replace_editor` commands with `path`.
|
|
281
|
-
Unknown tools pass. **Shell tools, MCP file tools, and external editors bypass
|
|
282
|
-
the guard.**
|
|
283
|
-
- **Injection finds the stores where the installer puts them.** A store elsewhere
|
|
284
|
-
(`--memory-root`) needs `indexPath`. The project root is found by dsh's default `.git`
|
|
285
|
-
marker only (a profile's custom `projectRootMarkers` are not visible to a plugin), and
|
|
286
|
-
a project store created mid-session can take up to ten seconds to appear. A project
|
|
287
|
-
store whose real path leaves the project — a symlink out of the checkout — is not
|
|
288
|
-
read; point `indexPath` at it if that is deliberate. The global store's location comes
|
|
289
|
-
from `$DSH_HOME`, resolved the way dsh resolves it. What is injected is framed as notes,
|
|
290
|
-
not instructions, that may be stale; a host without runtime context
|
|
291
|
-
(no `ctx.systemPrompt.context`) goes without it.
|
|
292
|
-
- **Social mode knows your typing by what dsh's browser client sends.** A program that
|
|
293
|
-
sends the same request id and time zone as dsh's own page is taken for you, and a
|
|
294
|
-
browser whose time zone cannot be read leaves your messages unmarked. dsh's terminal,
|
|
295
|
-
editor (ACP) and SDK clients do not mark them either: with social mode on, only
|
|
296
|
-
dsh's own window counts.
|
|
297
|
-
- **Some of dsh's messages carry no author.** An answer to the agent's question, a
|
|
298
|
-
scheduled prompt and a message a slash command adds (`/plan <text>`, `/goal`'s
|
|
299
|
-
attachments) count as nobody's, and a queued message edited through dsh's API keeps the
|
|
300
|
-
author it had. On dsh 0.2.1 only the owner can answer, schedule or edit — in dsh's
|
|
301
|
-
window, or through a program with the owner's login, which can read the store's files
|
|
302
|
-
through dsh's own file API anyway — and a chat bridge that forwards a command runs it in
|
|
303
|
-
its own conversation, which its other messages already make someone else's.
|
|
304
|
-
- **A kind of message a later dsh adds counts as someone else's** until this plugin knows
|
|
305
|
-
it: a conversation of yours may lose the memory, never the other way round.
|
|
306
|
-
- **The memory page reads the disks as they answer.** On a filesystem whose directory
|
|
307
|
-
entries carry no type (some network mounts), Node stats each listed entry on dsh's main
|
|
308
|
-
thread, at most 2000 per folder; and a listing stuck on a drive that never answers keeps
|
|
309
|
-
reporting a timeout until it does, rather than starting another that would get stuck too.
|
|
310
|
-
- **What the memory page opens is checked by path.** dsh's opener takes a path, which it
|
|
311
|
-
resolves once more before the desktop opens it; a process changing a store's files at
|
|
312
|
-
that very moment could still redirect what the desktop opens — as with dsh's own opener
|
|
313
|
-
for any workspace file — though not to a link the page saw.
|
|
314
|
-
- dsh's plugin API can change. The plugin uses `ctx.tools.guard()` and reactive
|
|
315
|
-
`ctx.inject()` children for the skill registry, the session working directory, the
|
|
316
|
-
prompt registry, and — in social mode — dsh's session projections, where it folds
|
|
317
|
-
who has written in each session; a compatible-release declaration needs recorded host
|
|
318
|
-
evidence, not just unit tests. The recorded end-to-end runs cover the cap, the skill
|
|
319
|
-
and the injected index; companion mode, social mode, the settings page and the memory page were checked by hand on
|
|
320
|
-
a real dsh (0.2.1-rc.5) — messages typed in dsh's window, sent by `dsh headless` and by
|
|
321
|
-
a program through dsh web's API, a save through the page, and the memory page's listing,
|
|
322
|
-
a switch saved from it and a note opened from it — but are not part of that record yet.
|
|
323
|
-
|
|
324
|
-
## License
|
|
325
|
-
|
|
326
|
-
MIT — see the [Engramory repository](https://github.com/tinqiao-oss/engramory).
|
|
1
|
+
**简体中文** | [English](README.en.md)
|
|
2
|
+
|
|
3
|
+
# dsh-engramory
|
|
4
|
+
|
|
5
|
+
[](https://unstone.github.io/dsh-xray/registry.html#tinqiao-oss__engramory)
|
|
6
|
+
|
|
7
|
+
给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)用的长期记忆插件。AI 记住的东西存成一个个普通 markdown 文件,你随时能打开看、能改。它是 [Engramory](https://github.com/tinqiao-oss/engramory) 的 dsh 专用版:Engramory 本身是通用的 AI agent 记忆,Claude Code、Codex 等也能用同一个库。
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## 它做什么
|
|
12
|
+
|
|
13
|
+
- **每轮自动带上记忆索引。** 索引 `MEMORY.md` 只列笔记名字,插件每一步都把它放进上下文,AI 不用先读文件就知道自己记过什么,要细节再打开那条笔记。写入后下一轮就能看到;超出上限的部分不带上,并提醒 AI 压缩。
|
|
14
|
+
- **索引有硬上限。** 默认 200 行 / 25 KB。AI 用 dsh 的文件工具写索引时,只要插件算得出写完的样子,会超上限就在写入前拒绝,让它先压缩;压缩照样放行。细节见[上限怎么判](#上限怎么判)。
|
|
15
|
+
- **自带记忆规则。** 记什么、怎么记、什么时候清理,这套规则(skill)随插件注册,不用另外装文件。
|
|
16
|
+
- **侧栏多一个「记忆」页。** 只读,列出笔记名字,点名字用电脑上的默认程序打开。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
需要 Node.js 18+,PATH 里要有 `pnpm`(dsh 的插件命令要用)。dsh 桌面版也可以在插件页搜 `dsh-engramory` 安装。
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
dsh plugin --profile <名字> add dsh-engramory
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
卸载把 `add` 换成 `remove`。兼容的 dsh 版本见 [`package.json`](https://github.com/tinqiao-oss/engramory/blob/master/adapters/dsh/plugin/package.json) 里的 `dsh.compatibility.dshReleases`,没列出的版本未经验证。
|
|
27
|
+
|
|
28
|
+
**记忆存在哪:**
|
|
29
|
+
|
|
30
|
+
- **全局库** `~/.dsh/.engramory-memory/`(设了 `$DSH_HOME` 就在它下面)。只装插件就用这一个,AI 第一次保存时自己建。
|
|
31
|
+
- **项目库**(可选)是项目根目录下的 `.engramory-memory/`,只能由你来建:在 Engramory 仓库里运行 `python tools/engramory_init.py dsh --project-root <项目>`,它会顺手加进 `.gitignore`。有项目库时,只在这个项目成立的事记在那里,其余仍记全局库。
|
|
32
|
+
|
|
33
|
+
一个库只能有一个 AI 写,同时写会互相覆盖;想让别的 AI 也用,让它只读,见[单写者、多读者](https://github.com/tinqiao-oss/engramory/blob/master/adapters/reader/README.md)。记忆是明文,带着本机细节。放在项目的 git 仓库里必须 git-ignore;给记忆库单独开一个私有仓没问题。
|
|
34
|
+
|
|
35
|
+
**不装插件也能用:** `python tools/engramory_init.py dsh --install-skill` 把记忆规则写进 `$DSH_HOME/AGENTS.md`、把 skill 装进 `$DSH_HOME/skills/engramory/`(`$DSH_HOME` 默认 `~/.dsh`),并建好全局库。AI 照样会记,但索引要它自己去读,上限只靠它写完后自觉运行 `engramory_check.py`。
|
|
36
|
+
|
|
37
|
+
## 大肥鱼模式
|
|
38
|
+
|
|
39
|
+
给陪你聊天的角色用(社区里的大肥鱼,或任何人设)。在设置页打开「大肥鱼模式」,或在配置里写 `companion: true`。
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
- **多记三类角色自己的事**([SKILL.md §2.2](https://github.com/tinqiao-oss/engramory/blob/master/SKILL.md)):`moment`(经历)是和你一起经历的事,带日期,索引满了按月归档;`upcoming`(约定)是你将要发生的事,日子过了会问起结果,问到了再改写成经历;`self`(我自己)是角色自己的变化,比如外号。经历和我自己只有你说忘掉才删。
|
|
44
|
+
- **用角色口吻回忆。** 打招呼也可能让它想起点什么;记住了、没记上,都用角色的话说,不提文件和工具。
|
|
45
|
+
- **人设预设要留着文件工具。** 记忆总会送到,哪怕预设关掉了可选上下文(比如 dsh 的 `minimal`)。但角色要保存记忆,预设得带上 dsh 的文件工具和 skill 工具;只给了 shell 的预设读得到记忆,写入却绕过上限。
|
|
46
|
+
|
|
47
|
+
## 社交模式
|
|
48
|
+
|
|
49
|
+
大肥鱼模式里的一个选项,给别人也能聊的角色用,比如通过接入插件接到 QQ、微信、抖音、B 站的角色。在设置页「记忆权限」选「社交」,或写 `audience: social`。只有你自己跟这个 dsh 聊,就保持默认的「专属」。
|
|
50
|
+
|
|
51
|
+
打开后,**只有你本人的会话拿得到记忆**:你在 dsh 自己的窗口(桌面版或 `dsh web`)里亲手发过消息、且没有别人说过话的会话才算你的。子 agent 跟着它的父会话算。接入插件转来的消息(标明是主人的除外,见下文)、`dsh headless`、外部程序、终端和编辑器客户端发的都算别人的;一个会话只要有别人说过话,就一直算别人的,之前已经带进去的记忆仍留在对话记录里。
|
|
52
|
+
|
|
53
|
+
别人的会话拿不到索引,也不知道记忆库在哪;dsh 的文件工具只能指名文件(任何目录都不让列),指向记忆库的路径不管怎么写都拒绝。这**不是**安全边界:shell 工具照样能读任何文件,同一账户下的另一个 profile 也读得到。要真正隔离,得靠文件权限或沙箱。自定义的 `skill`(见[配置](#配置))会原样给所有会话,别人的也一样,里面写了库在哪,别人也看得到。
|
|
54
|
+
|
|
55
|
+
### 给接入插件作者
|
|
56
|
+
|
|
57
|
+
社交模式下,接入插件转进来的消息默认都算别人的。来自主人自己账号的消息(比如主人和角色的 QQ 私聊),在来源章上标 `speaker`:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
agent.followup(createUserMessage({
|
|
61
|
+
content: [{ type: 'text', text }],
|
|
62
|
+
source: { kind: 'plugin:your-bridge', speaker: { role: 'owner' } },
|
|
63
|
+
}))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| `speaker.role` | 含义 |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `owner` | 主人发的:来自用户在你插件里设为"自己"的那个账号。 |
|
|
69
|
+
| `guest` | 别人发的,和不写 `speaker` 一样。 |
|
|
70
|
+
| `none` | 不是人发的:你插件自己的汇报、提醒或通知,不会让主人的会话变成别人的。 |
|
|
71
|
+
|
|
72
|
+
群聊里只要有别人说话,那个会话就一直算别人的。只通过 dsh web 接口发消息的程序设不了来源章,消息都算别人的;它若带上 `clientTimeZone`,会被当成主人,所以替别人转发消息的程序不要带。
|
|
73
|
+
|
|
74
|
+
## 设置页与记忆页
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
- **设置页**:dsh 的「插件」页里点开「Engramory 记忆」,能改大肥鱼模式、记忆权限和索引上限。保存走 dsh 自己的配置服务,写进你 profile 的 `cordis.patch.yml` 并重新加载插件。这个接口只有登录了的你能调,AI 没法调用它关掉社交模式。
|
|
79
|
+
- **记忆页**:左侧栏「记忆」。列出全局库和各工作区的项目库(设了绝对路径的 `indexPath` 时只列那一个,相对路径则列不出),显示索引离上限还有多远、按类型分组的笔记名字和归档。只读,不显示正文;点名字、「打开索引」「打开文件夹」都在运行 dsh 的那台机器上打开;没有桌面的机器(无界面服务器、SSH 连过去的)不显示这些按钮。顶部的开关(大肥鱼模式,打开后还有记忆权限)拨了就保存。
|
|
80
|
+
|
|
81
|
+
## 配置
|
|
82
|
+
|
|
83
|
+
设置页没有的配置,写在你 profile 的 `cordis.patch.yml` 里:按 id 改已有的那一行,别再写一个 `- insert:`(那会多出第二行 engramory,旧上限照样生效)。一次 patch 会整体替换 `config`,要保留的键都列上:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
- id: engramory
|
|
87
|
+
config:
|
|
88
|
+
indexName: MEMORY.md
|
|
89
|
+
maxLines: 200
|
|
90
|
+
maxBytes: 25600
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| 字段 | 默认值 | 含义 |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `indexName` | `MEMORY.md` | 哪个文件名算索引。卡上限时不分大小写;每轮带上索引时,在区分大小写的系统(如 Linux)上要和文件名完全一致。 |
|
|
96
|
+
| `maxLines` | `200` | 索引行数上限。 |
|
|
97
|
+
| `maxBytes` | `25600` | 索引字节上限(25 KB)。 |
|
|
98
|
+
| `indexPath` | 不设 | 只认这一个索引:绝对路径,或相对会话目录。不设时,任何项目里叫 `MEMORY.md` 的文件都按索引卡上限;记忆库不在默认位置时也要设它,否则不会自动带上。 |
|
|
99
|
+
| `registerSkill` | `true` | `false`:不注册内置的记忆规则;上限和每轮带上索引照旧。 |
|
|
100
|
+
| `injectIndex` | `true` | `false`:不再每轮带上索引,改由 AI 自己读。 |
|
|
101
|
+
| `language` | 跟随 dsh | `zh`(`zh-CN` 等也算)用中文写记忆(笔记名、`## 用户` 这类标题、**为什么:**/**怎么用:**),其他字符串用英文;不是字符串或为空,当作没设。不设就跟 dsh 的语言,改了要重启 dsh。frontmatter 的键和 `type:` 取值始终是英文,别的宿主照样能读。 |
|
|
102
|
+
| `companion` | `false` | `true` 打开大肥鱼模式。 |
|
|
103
|
+
| `audience` | `private` | 大肥鱼模式下设 `social` 打开社交模式。 |
|
|
104
|
+
| `skill` | 内置 | 用你自己的 markdown 换掉内置规则,这时得自己写明记忆库在哪。社交模式下别人的会话也看得到它。 |
|
|
105
|
+
|
|
106
|
+
## 上限怎么判
|
|
107
|
+
|
|
108
|
+
| 情况 | 结果 |
|
|
109
|
+
|---|---|
|
|
110
|
+
| 写完后索引在上限内 | 放行 |
|
|
111
|
+
| 写入让索引变大,并超过上限 | **拒绝**,报出数字,提示压缩什么 |
|
|
112
|
+
| 索引已超限,写入让它变小或不变 | 放行(压缩可以一步步来:210 → 205 → 198) |
|
|
113
|
+
| 算得出结果的局部编辑:`edit`(`old_string`/`new_string`)、`str_replace_editor` 的替换(`old_str`/`new_str`)和 `insert` | 先算出改完的结果,再按上面三条判;每个工具都只按它自己会用的参数和匹配方式算 |
|
|
114
|
+
| 算不出结果的局部编辑(参数不全或认不出) | 索引已超限时拒绝,否则放行 |
|
|
115
|
+
| 索引不存在、读不到,或不是普通文件 | 整文件写入当它是空的来判;局部编辑放行 |
|
|
116
|
+
| 索引大到超过读取上限(`min(16 MiB, max(1 MiB, 4 × maxBytes))`) | 不整份读:整文件写入只按字节比大小;局部编辑放行 |
|
|
117
|
+
| 读文件、写别的文件、不认识的工具或参数 | 放行;索引超限也不挡读取 |
|
|
118
|
+
|
|
119
|
+
行数和字节分开判:只拒绝既超上限、又比原来变大的那一项,所以字节变少但行数涨过上限的写入照样被拒。行数不算末尾换行,正好卡在上限的索引还能写。这张表由 CI 里的 `node --test` 固定。
|
|
120
|
+
|
|
121
|
+
## 已知限制
|
|
122
|
+
|
|
123
|
+
- **只拦 dsh 的文件工具**:`write`、`edit`,以及有的版本和预设带的 `str_replace_editor`;别的插件提供的同名工具也照样判。shell、MCP 文件工具和外部编辑器写入都绕过上限;改完索引跑 `engramory_check.py`,定期跑 `engramory_doctor.py`。
|
|
124
|
+
- **记忆库只在默认位置自动找到。** 放在别处,或项目库是指向项目外的链接,要设 `indexPath`;项目根只按 `.git` 认;会话中途新建的项目库最多十秒后才出现。
|
|
125
|
+
- **社交模式靠 dsh 窗口发来的信息认你。** 除了接入插件标明的主人消息,只有 dsh 自己窗口里打的字算你的;浏览器读不出时区时也认不出。dsh 以后新加的消息种类,插件认识之前都算别人的:你的会话可能拿不到记忆,但不会把记忆漏给别人。
|
|
126
|
+
- **记忆页打开文件前按路径检查。** dsh 的打开功能拿到路径后还会再解析一次,如果恰好在这一刻有程序改动记忆库里的文件,桌面最终打开的可能是别的东西(dsh 自己打开工作区文件也是这样)。
|
|
127
|
+
- **实验性功能**:大肥鱼模式、社交模式、设置页和记忆页在小版本里也可能改。
|
|
128
|
+
|
|
129
|
+
## 版本
|
|
130
|
+
|
|
131
|
+
本包和 Engramory 用同一个版本号:`dsh-engramory@X.Y.Z` 就是仓库的 `vX.Y.Z`。上限判定(上面那张表)和实验性功能以外的配置,只在主版本做不兼容改动。详见[版本规则](https://github.com/tinqiao-oss/engramory#版本规则),改动历史见 [CHANGELOG](https://github.com/tinqiao-oss/engramory/blob/master/CHANGELOG.md)。
|
|
132
|
+
|
|
133
|
+
## 许可
|
|
134
|
+
|
|
135
|
+
MIT,见 [Engramory 仓库](https://github.com/tinqiao-oss/engramory)。
|