dsh-trivium 0.4.8 → 0.4.10
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 +78 -40
- package/README.zh-CN.md +79 -40
- package/lib/client.js +160 -77
- package/lib/index.js +30 -20
- package/lib/purge.js +116 -0
- package/lib/server.js +11 -1
- package/lib/settings.js +69 -9
- package/lib/store.js +6 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
In-process graph memory for DeepSeek Harness. One `.tdb` per workspace. No extra server.
|
|
8
8
|
|
|
9
|
-
DSH
|
|
9
|
+
The kernel stays close to DSH: no extra process, no extra nav by default. Pin memory fragments with optional chip inject; the plot canvas is an optional extra, loaded from Settings.
|
|
10
10
|
|
|
11
|
-
> **Quick install**: `dsh plugin --profile web add dsh-trivium` → restart **dsh web** → open a workspace (Settings shows **Trivium memory**; the
|
|
11
|
+
> **Quick install**: `dsh plugin --profile web add dsh-trivium` → restart **dsh web** → open a workspace (Settings shows **Trivium memory**; the **Chips** tab stays off until you turn it on). Details in [Installation](#installation).
|
|
12
12
|
|
|
13
13
|
[**English**](README.md) | [中文版](README.zh-CN.md)
|
|
14
14
|
|
|
@@ -47,14 +47,48 @@ dsh plugin --profile web add dsh-trivium
|
|
|
47
47
|
|
|
48
48
|
Restart **dsh web**. For a source checkout, `git pull` then run `node scripts/link-dsh.mjs` again.
|
|
49
49
|
|
|
50
|
+
## Uninstall
|
|
51
|
+
|
|
52
|
+
Stop **dsh web** first so the `.tdb` is not locked, then:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
dsh plugin --profile web remove dsh-trivium
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`remove` deletes this plugin’s files: `~/.dsh/trivium.json`, and in every workspace the plugin has opened, `.dsh/trivium.tdb` plus `trivium-pending.json`. Other files under `.dsh/` stay. Updating with `dsh plugin add dsh-trivium` does **not** wipe memory.
|
|
59
|
+
|
|
60
|
+
Restart **dsh web** afterward: Settings no longer shows **Trivium memory**, the title bar no longer shows **Chips**, and the four tools are unregistered.
|
|
61
|
+
|
|
62
|
+
Workspaces never opened after this version may still have an old `.tdb`; delete that file by hand if you find one. Disable without uninstall (data stays): in that profile’s `cordis.patch.yml` add:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
- id: dsh-trivium
|
|
66
|
+
disabled: true
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then restart `dsh web`.
|
|
70
|
+
|
|
71
|
+
## Permissions and data
|
|
72
|
+
|
|
73
|
+
The plugin loads inside the `dsh web` process. It does not open its own port or extra service.
|
|
74
|
+
|
|
75
|
+
| Access | Default | Notes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| Workspace `.dsh/trivium.tdb` | read/write | Memory store. Do not open the same file from two Node processes at once |
|
|
78
|
+
| `~/.dsh/trivium.json` | read/write | Settings and chip pins; may contain an embedding API key you typed |
|
|
79
|
+
| `.dsh/trivium-pending.json` | read/write | Local queue when extract fails |
|
|
80
|
+
| Network | off | Chat text is not sent out. Embedding outbound happens only if you turn it on and fill a URL. Settings **Check for updates** fetches the npm registry (version only, no transcript) |
|
|
81
|
+
| Transcript | stays local | Extract and search run on this machine; with embedding on, retrieved text is sent to the URL you filled |
|
|
82
|
+
|
|
83
|
+
The official DeepSeek chat API has no embeddings endpoint. Keyword + graph walk still work without it. Restart `dsh web` after changing settings or embedding.
|
|
84
|
+
|
|
50
85
|
## AI-era installation
|
|
51
86
|
|
|
52
87
|
Copy this to your assistant:
|
|
53
88
|
|
|
54
89
|
```text
|
|
55
90
|
On DeepSeek Harness, run: dsh plugin --profile web add dsh-trivium
|
|
56
|
-
Then restart dsh web. Settings will show "Trivium memory"
|
|
57
|
-
will show "Session graph" next to Conversation / Trajectory (use "Create checkpoint" to split a box by hand).
|
|
91
|
+
Then restart dsh web. Settings will show "Trivium memory". The Chips tab is off by default; turn it on under Settings → Trivium memory if you want to pin fragments into the next turn.
|
|
58
92
|
```
|
|
59
93
|
|
|
60
94
|
---
|
|
@@ -64,8 +98,8 @@ will show "Session graph" next to Conversation / Trajectory (use "Create checkpo
|
|
|
64
98
|
Trivium is a memory kernel, not a journal, calendar, or chat companion. It stores **nodes and edges**, injects as little as possible, and lets you correct mistakes.
|
|
65
99
|
|
|
66
100
|
- **Cross-session graph** — session A stores “auth goes in header X”; session B calls `ctx_find("auth")` and gets the hit plus who it links to (`about` / `decided` / `broke` / `fixed`).
|
|
67
|
-
- **Default quiet** — a new session injects one short map (≤400 tokens). The model calls tools when it needs more. Nothing is dumped every step.
|
|
68
|
-
- **
|
|
101
|
+
- **Default quiet** — a new session injects one short map (≤400 tokens). The model calls tools when it needs more. Nothing is dumped every step. No extra title-bar tab until you turn **Chips** on.
|
|
102
|
+
- **Chips (off by default)** — Settings → Trivium memory. When on, the title bar shows **Chips**; checked items pin into the next turn (L0, ≤300 tokens). Under it, **Session layer** (also off) draws compaction / forks as boxes.
|
|
69
103
|
- **Human-editable** — chips can add, archive, or delete; Settings search, rename, merge, import/export, and global switches.
|
|
70
104
|
- **Strict extraction** — chitchat, one-off file edits, and secrets are **not** auto-written from the transcript. Chip “Add” stores exactly what you typed (one paste = one node), still through the write hygiene gate.
|
|
71
105
|
- **Write hygiene** — `ctx_remember`, chip add, extract, and external import refuse mojibake, stutter loops, JSON envelopes, base64 residue, and secrets. Dirty nodes already in `.tdb` stay visible in Settings so you can archive them; they are hidden from find / short map / chips.
|
|
@@ -74,11 +108,11 @@ Trivium is a memory kernel, not a journal, calendar, or chat companion. It store
|
|
|
74
108
|
### Under the hood
|
|
75
109
|
|
|
76
110
|
- **In-process, one file** — TriviumDB (vectors + JSON payload + directed weighted graph) opens with the DSH process. No sidecar HTTP/Python service.
|
|
77
|
-
- **Four tools only** — `ctx_find` / `ctx_read` / `ctx_remember` / `ctx_link`.
|
|
111
|
+
- **Four tools only** — `ctx_find` / `ctx_read` / `ctx_remember` / `ctx_link`. Chips / session layer do not add a fifth.
|
|
78
112
|
- **Injection via `agent.inject()`** — not the system prompt, so `persona.complete: true` cannot silently drop the map.
|
|
79
113
|
- **Recall carries paths** — every hit says which node it came from and along which edge.
|
|
80
114
|
- **Failures never block the agent** — store / embedding / extract errors are logged; the main loop continues.
|
|
81
|
-
- **UI follows the host language** — Settings and the
|
|
115
|
+
- **UI follows the host language** — Settings and the Chips tab switch with DSH `locale/change` (`zh` / `en`).
|
|
82
116
|
- **First-turn map** — the short map is injected once per session. If `session-start` loses a race with the first model step, `pre-step` fills it in; it is not rewritten every step (prefix-cache friendly).
|
|
83
117
|
|
|
84
118
|
## Features
|
|
@@ -89,32 +123,11 @@ Nodes are `entity` / `preference` / `decision` / `experience`. Business edges ar
|
|
|
89
123
|
|
|
90
124
|
`ctx_find` returns L0 summaries plus edge paths (incoming as `<-label-id`). If the query names an existing entity, unexpired `about` / `decided` / `broke` / `fixed` neighbors come along even when the neighbor text does not contain the query. Decisions with `until` stay hidden after they expire, unless the query itself is asking about that deadline (e.g. “Friday”).
|
|
91
125
|
|
|
92
|
-
###
|
|
93
|
-
|
|
94
|
-
Entry: current session title bar **Conversation / Trajectory / Session graph**. This is a plot of compaction and forks, not the memory editor. Rename, merge, and global switches stay on the Settings page. The chip strip can add, archive, and delete.
|
|
95
|
-
|
|
96
|
-
**Boxes**
|
|
97
|
-
|
|
98
|
-
- Never compacted: one “Next” box.
|
|
99
|
-
- After a successful compaction: historical checkpoints on the left (summaries from DSH), “Next” still on the right.
|
|
100
|
-
- **Create checkpoint**: fold the current “Next” into a left box; the right side stays “Next”. Plot only — **does not** compact the model window. Clicking again in the same turn does not double-write.
|
|
101
|
-
- Sessions that compacted or forked before the plugin was installed: **Update checkpoints** backfills boxes from compression markers in the transcript and sidebar forks (safe to click again). No compaction still means only “Next” — a long session with many tools is not the same as DSH having compacted (auto-compact is around 80% of the window). To compact context, type `/compact` in the conversation.
|
|
102
|
-
- Boxes themselves do not summarize. Quality comes from summaries DSH already wrote; fork does not re-summarize.
|
|
103
|
-
- “Rename” changes the box title only. Click a box to jump to that checkpoint in the conversation, or open Trajectory.
|
|
104
|
-
|
|
105
|
-
**Forks**
|
|
106
|
-
|
|
107
|
-
The fork icon on a box opens “Fork as new session”. That name appears in the left session list (default: source title plus `(1)`).
|
|
108
|
-
|
|
109
|
-
Confirm runs the host `session.fork`: the child shows up in the sidebar, prefix (including already-compacted summaries) is copied by DSH. With a checkpoint, the cut is at that checkpoint; with only “Next”, the edge leaves the box you clicked.
|
|
110
|
-
|
|
111
|
-
Forks created from the conversation page also show up on the session graph. Archive a child in the sidebar and its fork box disappears; unarchive brings it back.
|
|
126
|
+
### Chips (optional)
|
|
112
127
|
|
|
113
|
-
|
|
128
|
+
Off by default. Turn on **Chips tab** in Settings, then restart `dsh web`. The title bar shows **Conversation / Trajectory / Chips**. This is a pin strip, not the memory editor. Rename, merge, and global switches stay on the Settings page.
|
|
114
129
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Expand the strip at the top of the session graph. Lists unarchived preference / decision / entity (experience stays out, to keep noise down).
|
|
130
|
+
Lists unarchived preference / decision / entity (experience stays out). Check to pin into the next turn (L0, ≤300 tokens). Add / archive / delete on the strip.
|
|
118
131
|
|
|
119
132
|
| Action | When it takes effect |
|
|
120
133
|
|---|---|
|
|
@@ -125,12 +138,16 @@ Expand the strip at the top of the session graph. Lists unarchived preference /
|
|
|
125
138
|
| Select → Delete | Removed from `.tdb`, not recoverable |
|
|
126
139
|
| Turns already sent | Not rewritten |
|
|
127
140
|
|
|
128
|
-
Chips are per session. New sessions and forked children start unchecked. Inherit pins
|
|
141
|
+
Chips are per session. New sessions and forked children start unchecked. Inherit pins when the session layer fork dialog is on, or check them again on the child. The short map still arrives at session-start and does not use a chip slot.
|
|
142
|
+
|
|
143
|
+
**Session layer** is a nested switch, also off. When on, the same tab draws compaction / fork boxes (Create checkpoint, Update checkpoints, fork from a box). Episode nodes are not recalled by `ctx_find`. Turning the layer off stops writing episodes; existing `.tdb` nodes stay.
|
|
129
144
|
|
|
130
145
|
### Settings (Trivium memory)
|
|
131
146
|
|
|
132
|
-
Settings → **Trivium memory** manages the store
|
|
147
|
+
Settings → **Trivium memory** manages the store and the two optional surfaces.
|
|
133
148
|
|
|
149
|
+
- **Chips tab (default off)** — title-bar **Chips** + pin inject. Restart `dsh web` after save.
|
|
150
|
+
- **Session layer (default off, under Chips)** — plot canvas. Requires the Chips tab.
|
|
134
151
|
- **Injection (pick one, default off)** — Off: short map at start only. autoRecall: at most 3 L0 hits when the step has user text. Entity-name middle path: 1-hop business neighbors only when the utterance names an existing entity. Checked chips share the budget and win first.
|
|
135
152
|
- **Extraction** — default after `compaction/end` and a short idle; failures go to pending and replay on the next session-start. Per batch: body ≤3000 chars, at most 24 items.
|
|
136
153
|
- **Entries** — search, filter by type, expand business-edge neighbors, “only nodes hanging on this one”, hide expired decisions by default. Rename / edit body / aliases / until, merge same type, archive or delete.
|
|
@@ -147,7 +164,7 @@ Settings → **Trivium memory** manages the store. It does not replace the sessi
|
|
|
147
164
|
| `ctx_remember` | Manual write |
|
|
148
165
|
| `ctx_link` | Directed edge between two nodes |
|
|
149
166
|
|
|
150
|
-
The kernel is still these four.
|
|
167
|
+
The kernel is still these four. Chips / session layer do not add a fifth tool.
|
|
151
168
|
|
|
152
169
|
---
|
|
153
170
|
|
|
@@ -173,11 +190,11 @@ Session-start injects the short map; the model calls `ctx_find` and hits “auth
|
|
|
173
190
|
|
|
174
191
|
<img width="560" alt="Entry list" src="docs/screenshots/05-settings-list.png">
|
|
175
192
|
|
|
176
|
-
###
|
|
193
|
+
### Chips tab — optional, next to Conversation / Trajectory
|
|
177
194
|
|
|
178
|
-
<img width="560" alt="
|
|
195
|
+
<img width="560" alt="Chips / session layer" src="docs/screenshots/06-session-map.png">
|
|
179
196
|
|
|
180
|
-
|
|
197
|
+
Off by default. After you turn **Chips** on in Settings and restart, the title bar shows **Chips**. With **Session layer** also on, a never-compacted session has one “Next” box; chips sit on top (unchecked by default). **Create checkpoint** folds “Next” into a left box. After compaction or `/compact`, historical boxes appear on the left; forks leave a box toward a child session.
|
|
181
198
|
|
|
182
199
|
---
|
|
183
200
|
|
|
@@ -187,7 +204,7 @@ The title bar shows **Session graph**. A session that has never compacted has on
|
|
|
187
204
|
- Extraction misses things; dirty data can be archived / deleted on the chip strip or edited / merged in Settings.
|
|
188
205
|
- Do not open the same `.tdb` from two Node processes at once (one `dsh web` is enough).
|
|
189
206
|
- Markdown export is for humans; it cannot be parsed back. External import (WorkBuddy / Claude Code / Codex) is one-shot, not two-way sync, and does not ingest session jsonl dumps.
|
|
190
|
-
- The session
|
|
207
|
+
- The session layer (when on) projects compaction and fork; it does not split boxes by message count. If the window has not reached DSH’s compact line, **Update checkpoints** will not invent history boxes — use **Create checkpoint**.
|
|
191
208
|
- Plugin setting changes need a `dsh web` restart.
|
|
192
209
|
- After the host moved to rc.8, DSH’s own older session store may not open (official SQLite format change). The workspace `.tdb` graph is still there; leftover boxes on unmatched old sessions are plot residue and do not affect `ctx_find`.
|
|
193
210
|
|
|
@@ -195,6 +212,10 @@ The title bar shows **Session graph**. A session that has never compacted has on
|
|
|
195
212
|
|
|
196
213
|
## Changelog
|
|
197
214
|
|
|
215
|
+
**0.4.10** — `dsh plugin remove` deletes known workspace `.tdb` files and `~/.dsh/trivium.json`. Plugin update does not wipe. Disable-in-place still keeps data.
|
|
216
|
+
|
|
217
|
+
**0.4.9** — Chips tab and session-layer canvas are both off by default. Title bar shows **Chips** only after the Settings switch; session boxes sit under that switch. Kernel (four tools, short map, in-process `.tdb`) unchanged.
|
|
218
|
+
|
|
198
219
|
**0.4.8** — Write + inject hygiene gate (secrets, mojibake, stutter, JSON envelopes, base64 residue). Settings / session graph follow DSH language. Settings can check npm for updates. One-shot strict import also discovers Claude Code `CLAUDE.md` and Codex `AGENTS.md`. First-turn short map is guaranteed if `session-start` races the first step.
|
|
199
220
|
|
|
200
221
|
**0.4.7** — Storage bump to `triviumdb@0.7.6` (`searchExact` / `searchBatch` on the engine). find / session graph behavior unchanged.
|
|
@@ -213,12 +234,29 @@ The title bar shows **Session graph**. A session that has never compacted has on
|
|
|
213
234
|
|
|
214
235
|
**0.4.0** — Session graph, memory chips, fork from a checkpoint.
|
|
215
236
|
|
|
237
|
+
## Troubleshooting
|
|
238
|
+
|
|
239
|
+
| Symptom | What to do |
|
|
240
|
+
|---|---|
|
|
241
|
+
| Settings has no **Trivium memory** | Confirm the **web** profile, then **restart** `dsh web` and open a workspace. Reloading the browser is not enough. |
|
|
242
|
+
| Title bar has no **Chips** | Expected. Turn on **Chips tab** in Settings, save, then restart `dsh web`. |
|
|
243
|
+
| Injection / extract / embedding / chip tab changes have no effect | Restart `dsh web` as well. |
|
|
244
|
+
| Session layer shows only a **Next** box | Expected when the nested session layer is on. Boxes follow DSH compaction; a long session is not the same as already compacted (auto-compact is around 80% of the window). To split: `/compact` in the conversation, or **Create checkpoint** (plot only, does not compact the window). Older sessions can use **Update checkpoints** to backfill past compaction / forks. |
|
|
245
|
+
| `ctx_find` returns nothing | A new session injects the short map only; the model must call the tool. You can also **Add** on the chip strip or tell the model “remember: …”. Chitchat and one-off file edits are not auto-written. |
|
|
246
|
+
| `.tdb` is locked / will not open | Run one `dsh web`. Do not run two Node processes that both linked this plugin. |
|
|
247
|
+
| Embedding is filled but search is no more accurate | Failures fall back to keyword + graph. Check that `~/.dsh/trivium.json` has an OpenAI-compatible URL (the `/v1` kind). Restart after changing it. |
|
|
248
|
+
| Memory still there after uninstall | Stop `dsh web` before `dsh plugin remove`. Leftover `.tdb` is a workspace never opened on 0.4.10+; delete `.dsh/trivium.tdb` in that folder. Disable-in-place does not wipe. |
|
|
249
|
+
| Check for updates fails | That request only hits the npm registry for the latest version. Memory is unaffected; try again later. |
|
|
250
|
+
| Old conversations will not open after rc.8 | That is DSH’s own session-store format change, not a broken `.tdb`. Graph memory still works via `ctx_find`; leftover boxes on unmatched old sessions are plot residue. |
|
|
251
|
+
|
|
252
|
+
Local source checkout: `npm install`, then `node scripts/link-dsh.mjs`, then restart `dsh web`. `triviumdb` is a native module; if it will not install, check Node against `package.json` `engines` (`^22.19.0 || >=24`).
|
|
253
|
+
|
|
216
254
|
---
|
|
217
255
|
|
|
218
256
|
## Release info
|
|
219
257
|
|
|
220
258
|
- GitHub: https://github.com/QWQcool/dsh-trivium
|
|
221
|
-
- npm: [`dsh-trivium@0.4.
|
|
259
|
+
- npm: [`dsh-trivium@0.4.10`](https://www.npmjs.com/package/dsh-trivium)
|
|
222
260
|
- Tested host: `@deepseek-ai/dsh@0.1.1-rc.2` (also `0.1.0-rc.8`)
|
|
223
261
|
- License: MIT (depends on [TriviumDB](https://github.com/YoKONCy/TriviumDB), Apache-2.0)
|
|
224
262
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<img width="820" alt="dsh-trivium 封面" src="docs/cover.png">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
DeepSeek Harness
|
|
7
|
+
DeepSeek Harness 的进程内图记忆插件:按节点和边记,默认少注入,设置页可改可归档。内核贴近 DSH:不另起进程、默认不加导航。要钉住某些记忆片段,再在设置里打开 **芯片** 注入;情节画布是可选的自嗨层,嵌在芯片开关下。每个工作区一个 `.dsh/trivium.tdb`,不另起服务。
|
|
8
8
|
|
|
9
|
-
> **快速安装**:`dsh plugin --profile web add dsh-trivium` → 重启 **dsh web** → 打开工作区(设置里出现「Trivium
|
|
9
|
+
> **快速安装**:`dsh plugin --profile web add dsh-trivium` → 重启 **dsh web** → 打开工作区(设置里出现「Trivium 记忆」;**芯片**标签默认关)。完整步骤见 [安装](#安装)。
|
|
10
10
|
|
|
11
11
|
[**English**](README.md) | [中文版](README.zh-CN.md)
|
|
12
12
|
|
|
@@ -45,13 +45,48 @@ dsh plugin --profile web add dsh-trivium
|
|
|
45
45
|
|
|
46
46
|
装完重启 **dsh web**。源码联调则在仓库里 `git pull` 后再跑一次 `node scripts/link-dsh.mjs`。
|
|
47
47
|
|
|
48
|
+
## 卸载
|
|
49
|
+
|
|
50
|
+
先停掉 **dsh web**,避免 `.tdb` 被占用,然后:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
dsh plugin --profile web remove dsh-trivium
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`remove` 会删掉本插件的文件:`~/.dsh/trivium.json`,以及它打开过的每个工作区里的 `.dsh/trivium.tdb` 和 `trivium-pending.json`。`.dsh/` 下其它文件不动。用 `dsh plugin add dsh-trivium` 升级**不会**清记忆。
|
|
57
|
+
|
|
58
|
+
然后再重启 **dsh web**:设置里不再有「Trivium 记忆」,标题栏不再有「芯片」,四个工具不再注册。
|
|
59
|
+
|
|
60
|
+
从未用本版打开过的工作区,可能还留着旧 `.tdb`;看到就手动删那个文件。只想停用、不卸载(数据还在):在该 profile 的 `cordis.patch.yml` 里加:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
- id: dsh-trivium
|
|
64
|
+
disabled: true
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
再重启 `dsh web`。
|
|
68
|
+
|
|
69
|
+
## 权限与数据
|
|
70
|
+
|
|
71
|
+
插件随 `dsh web` 进程加载,不另起端口、不另起服务。
|
|
72
|
+
|
|
73
|
+
| 访问 | 默认 | 说明 |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| 工作区 `.dsh/trivium.tdb` | 读写 | 记忆库。同一文件不要两个 Node 进程同时打开 |
|
|
76
|
+
| `~/.dsh/trivium.json` | 读写 | 设置和芯片钉选;可含你手填的 embedding API key |
|
|
77
|
+
| `.dsh/trivium-pending.json` | 读写 | 抽取失败时的本地队列 |
|
|
78
|
+
| 网络 | 关 | 对话不外发。打开 embedding 并填了 URL 才会把被检索文本发到你填的地址。设置页点「检查更新」会请求 npm registry(只拿版本号,不含对话) |
|
|
79
|
+
| 对话原文 | 不外发 | 抽取和检索都在本机;embedding 开启后,被检索的文本会发到你填的地址 |
|
|
80
|
+
|
|
81
|
+
官方 DeepSeek 对话接口没有 embeddings。不开 embedding 也能用:关键词 + 图遍历。改完设置或 embedding 后需重启 `dsh web`。
|
|
82
|
+
|
|
48
83
|
## AI 时代安装
|
|
49
84
|
|
|
50
85
|
把下面这句话复制给助手即可:
|
|
51
86
|
|
|
52
87
|
```text
|
|
53
88
|
请在 DeepSeek Harness 上执行:dsh plugin --profile web add dsh-trivium
|
|
54
|
-
然后重启 dsh web。设置里会出现「Trivium
|
|
89
|
+
然后重启 dsh web。设置里会出现「Trivium 记忆」。芯片标签默认关;若要钉住某些记忆片段,在设置「Trivium 记忆」里打开「芯片标签」后再重启。
|
|
55
90
|
```
|
|
56
91
|
|
|
57
92
|
---
|
|
@@ -61,9 +96,9 @@ dsh plugin --profile web add dsh-trivium
|
|
|
61
96
|
Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点和边**记,默认少注入,记错了人能改。
|
|
62
97
|
|
|
63
98
|
- **跨会话** — 会话 A 记下「鉴权走 header X」,会话 B 调用 `ctx_find("鉴权")` 命中,并带上它连着谁(`about` / `decided` / `broke` / `fixed`)。
|
|
64
|
-
- **默认安静** — 新会话只注入一张短地图(≤400 token
|
|
65
|
-
-
|
|
66
|
-
- **人能改错** —
|
|
99
|
+
- **默认安静** — 新会话只注入一张短地图(≤400 token)。模型要用再调工具,不会每一步灌满。标题栏默认也不加第三标签。
|
|
100
|
+
- **芯片(默认关)** — 设置里打开后,标题栏出现 **芯片**;勾选的条目从下一轮注入(L0,≤300 token)。其下还有 **会话层**(也默认关):把 compaction / 分叉画成方框。
|
|
101
|
+
- **人能改错** — 芯片条可新增、归档、删除;设置页管搜、改名 / 正文、合并、导入导出、全局开关。
|
|
67
102
|
- **抽取偏严** — 闲聊、一次性改文件、密钥**不会从对话自动入库**。芯片「新增」按你写的存(点一次整段一条),同样过写入卫生闸门。
|
|
68
103
|
- **写入卫生** — `ctx_remember`、芯片新增、抽取、外部导入会拒绝乱码、复读、JSON envelope、base64 残骸和密钥。已经进 `.tdb` 的脏节点仍可在设置里看见以便归档;find / 短地图 / 芯片不再带它们。
|
|
69
104
|
- **可选 embedding** — 默认关。官方 DeepSeek 对话接口没有 embeddings;可填 OpenAI 兼容地址。不开也能用:关键词 + 图遍历。
|
|
@@ -71,11 +106,11 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
71
106
|
### 底层工程
|
|
72
107
|
|
|
73
108
|
- **进程内、一个文件** — TriviumDB(向量 + JSON payload + 有向带权图)随 DSH 进程打开。不另起 HTTP / Python 服务。
|
|
74
|
-
- **只有四个工具** — `ctx_find` / `ctx_read` / `ctx_remember` / `ctx_link
|
|
109
|
+
- **只有四个工具** — `ctx_find` / `ctx_read` / `ctx_remember` / `ctx_link`。芯片 / 会话层不加第五个。
|
|
75
110
|
- **注入走 `agent.inject()`** — 不写 system prompt,避免 `persona.complete: true` 把地图静默丢掉。
|
|
76
111
|
- **召回带路径** — 每条命中都说明从哪个节点、沿哪条边过来。
|
|
77
112
|
- **失败不挡主循环** — 存储 / embedding / 抽取出错只记日志,Agent 继续。
|
|
78
|
-
- **界面跟随宿主语言** —
|
|
113
|
+
- **界面跟随宿主语言** — 设置页和芯片标签随 DSH `locale/change` 切换(`zh` / `en`)。
|
|
79
114
|
- **首轮短地图** — 每个会话只注入一次。若 `session-start` 和第一步赛跑输了,`pre-step` 会补上;不会每步重写(前缀缓存友好)。
|
|
80
115
|
|
|
81
116
|
## 功能
|
|
@@ -86,32 +121,11 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
86
121
|
|
|
87
122
|
`ctx_find` 返回 L0 摘要和图路径(入边写成 `<-label-id`)。查询点到已有实体名时,还会带上未过期的 about/decided/broke/fixed 邻居,即使邻居正文不含查询词。带 `until` 的过期决策默认不出现,除非查询本身在问期限(如「周五」)。
|
|
88
123
|
|
|
89
|
-
###
|
|
124
|
+
### 芯片(可选)
|
|
90
125
|
|
|
91
|
-
|
|
126
|
+
默认关。在设置里打开 **芯片标签**,然后重启 `dsh web`。标题栏出现 **对话 / 轨迹 / 芯片**。这是钉选条,不是记忆编辑器;改名、合并、全局开关仍走设置页。
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- 没压过上下文:只有一个「后续」方框。
|
|
96
|
-
- 一次成功的 compaction 之后:左边历史检查点(摘要来自 DSH),右边仍是「后续」。
|
|
97
|
-
- 点「生成检查点」:把当前「后续」收成左边一格,右边继续当后续。只切情节图,**不**压缩模型窗口。同一轮次再点不会双写。
|
|
98
|
-
- 装插件之前已经压过 / 分过叉的旧会话:点「更新检查点」,从当前对话里的压缩标记和侧边栏分叉补方框(可重复点,不会双写)。没压过就仍只有「后续」——会话长、工具多不等于 DSH 已经压缩(默认约窗口 80% 才自动压)。要压上下文请在对话里输入 `/compact`。
|
|
99
|
-
- 方框自己不压缩。质量来自 DSH 已经做的摘要替换;fork 时不会再总结一遍。
|
|
100
|
-
- 「改名」只改方框标题,不动 DSH 摘要。点方框可跳回对话里对应的检查点,或打开轨迹。
|
|
101
|
-
|
|
102
|
-
**分叉**
|
|
103
|
-
|
|
104
|
-
点方框上的分叉图标,会弹出「分叉为新会话」。这个名字会出现在左侧会话列表里(默认是源标题加 `(1)`)。
|
|
105
|
-
|
|
106
|
-
确认后走宿主的 `session.fork`:子会话出现在侧边栏,前缀(含当时已经压过的摘要)由 DSH 拷过去。有检查点时从那个检查点切开;只有「后续」时就从你点的那个方框连出去。
|
|
107
|
-
|
|
108
|
-
对话页里用 DSH 自带 fork,切回会话图也能看到对应分支。侧边栏归档一个子会话后,图上的分叉方框会跟着消失;取消归档会再出现。
|
|
109
|
-
|
|
110
|
-
画布:空白处拖动平移,滚轮缩放。
|
|
111
|
-
|
|
112
|
-
### 记忆芯片
|
|
113
|
-
|
|
114
|
-
会话图顶部可展开勾选。列出未归档的 preference / decision / entity(experience 不进芯片,避免太吵)。
|
|
128
|
+
列出未归档的 preference / decision / entity(experience 不进芯片)。勾选后从下一轮注入(L0,≤300 token)。条上可新增、归档、删除。
|
|
115
129
|
|
|
116
130
|
| 操作 | 生效时机 |
|
|
117
131
|
|---|---|
|
|
@@ -122,12 +136,16 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
122
136
|
| 选择 → 删除 | 从 `.tdb` 去掉,不可恢复 |
|
|
123
137
|
| 已经发出去的轮次 | 不回写 |
|
|
124
138
|
|
|
125
|
-
芯片按会话隔离。新开会话、fork
|
|
139
|
+
芯片按会话隔离。新开会话、fork 出的子会话默认都不勾。短地图仍在 session-start 自动带,不占芯片位。
|
|
140
|
+
|
|
141
|
+
其下 **会话层** 也默认关。打开后,同一标签里画出 compaction / 分叉方框(生成检查点、更新检查点、从方框 fork)。`episode` 不进 `ctx_find`。关掉会话层就停止写检查点,已有节点仍在 `.tdb`。
|
|
126
142
|
|
|
127
143
|
### 设置页(Trivium 记忆)
|
|
128
144
|
|
|
129
|
-
设置里的「Trivium
|
|
145
|
+
设置里的「Trivium 记忆」管库,也管这两层可选表面。
|
|
130
146
|
|
|
147
|
+
- **芯片标签(默认关)** — 标题栏「芯片」+ 钉选注入。保存后重启 `dsh web`。
|
|
148
|
+
- **会话层(默认关,挂在芯片下)** — 情节画布。要先打开芯片标签。
|
|
131
149
|
- **注入策略(三选一,默认关)** — 关:只有开场短地图。autoRecall:本步含用户文本时最多灌 3 条 L0。实体名折中:话里点到已有实体才灌 1 跳业务边邻居。芯片钉选与此并存,优先占预算。
|
|
132
150
|
- **抽取** — 默认在 `compaction/end` 和空闲后再抽;失败进 pending,下次 session-start 重放。每批正文 ≤3000 字、最多 24 条。
|
|
133
151
|
- **条目** — 可搜、按类型筛、展开业务边邻居、「只看挂在这上面的」、默认隐藏过期决策。可改名 / 改正文 / 别名 / until,同类型合并,归档或删除。
|
|
@@ -144,7 +162,7 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
144
162
|
| `ctx_remember` | 手动写入 |
|
|
145
163
|
| `ctx_link` | 两点之间建有向边 |
|
|
146
164
|
|
|
147
|
-
|
|
165
|
+
内核仍是这四个。芯片 / 会话层不加第五个工具。
|
|
148
166
|
|
|
149
167
|
---
|
|
150
168
|
|
|
@@ -170,11 +188,11 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
170
188
|
|
|
171
189
|
<img width="560" alt="条目列表" src="docs/screenshots/05-settings-list.png">
|
|
172
190
|
|
|
173
|
-
###
|
|
191
|
+
### 芯片标签 — 可选,在对话 / 轨迹旁
|
|
174
192
|
|
|
175
|
-
<img width="560" alt="
|
|
193
|
+
<img width="560" alt="芯片 / 会话层" src="docs/screenshots/06-session-map.png">
|
|
176
194
|
|
|
177
|
-
|
|
195
|
+
默认关。设置里打开 **芯片** 并重启后,标题栏出现「芯片」。若同时打开 **会话层**,没压过的会话只有一个「后续」方框;顶部是记忆芯片(默认不勾)。可点「生成检查点」把后续收成左边一格。compaction 或 `/compact` 之后左边也会出现历史方框,分叉从方框连到子会话。
|
|
178
196
|
|
|
179
197
|
---
|
|
180
198
|
|
|
@@ -184,7 +202,7 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
184
202
|
- 抽取会漏;脏数据可在芯片条归档 / 删除,或到设置里改、合并。
|
|
185
203
|
- 同一个 `.tdb` 不要两个 Node 进程同时打开(正常只开一个 `dsh web` 即可)。
|
|
186
204
|
- Markdown 导出是给人看的,不能再解析回去。外部导入(WorkBuddy / Claude Code / Codex)是一次性,不是双向同步,也不会吃会话 jsonl。
|
|
187
|
-
-
|
|
205
|
+
- 会话层打开时只投影 compaction 与 fork,不会按消息条数自己切方框。窗口没到 DSH 压缩线时,「更新检查点」也补不出历史方框;要分段请点「生成检查点」。
|
|
188
206
|
- 改插件设置后需要重启 `dsh web`。
|
|
189
207
|
- 宿主升到 rc.8 后,DSH 自己的旧会话库可能打不开(官方 SQLite 格式不兼容)。工作区里的 `.tdb` 图记忆还在;对不上号的旧会话图方框可以当情节残留,不影响 `ctx_find`。
|
|
190
208
|
|
|
@@ -192,6 +210,10 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
192
210
|
|
|
193
211
|
## 更新说明
|
|
194
212
|
|
|
213
|
+
**0.4.10** — `dsh plugin remove` 会删掉已知工作区的 `.tdb` 和 `~/.dsh/trivium.json`。升级安装不清记忆。配置里 disabled 仍保留数据。
|
|
214
|
+
|
|
215
|
+
**0.4.9** — 芯片标签和会话层画布都默认关。设置里打开芯片后标题栏才出现「芯片」;情节方框嵌在其下。内核(四工具、短地图、进程内 `.tdb`)不变。
|
|
216
|
+
|
|
195
217
|
**0.4.8** — 写入 + 注入卫生闸门(密钥、乱码、复读、JSON envelope、base64 残骸)。设置页 / 会话图跟随 DSH 语言。设置页可检查 npm 更新。一次性严导入同时发现 Claude Code `CLAUDE.md` 与 Codex `AGENTS.md`。若 `session-start` 和第一步赛跑,首轮短地图仍会补上。
|
|
196
218
|
|
|
197
219
|
**0.4.7** — 存储升到 `triviumdb@0.7.6`(引擎侧 `searchExact` / `searchBatch`)。find / 会话图行为不变。
|
|
@@ -210,12 +232,29 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
|
|
|
210
232
|
|
|
211
233
|
**0.4.0** — 会话图、记忆芯片、从检查点 fork。
|
|
212
234
|
|
|
235
|
+
## 排障
|
|
236
|
+
|
|
237
|
+
| 现象 | 处理 |
|
|
238
|
+
|---|---|
|
|
239
|
+
| 设置里没有「Trivium 记忆」 | 确认装的是 web profile,然后**重启** `dsh web`,再打开一个工作区。只刷新浏览器不够。 |
|
|
240
|
+
| 标题栏没有「芯片」 | 正常。默认关。在设置里打开「芯片标签」,保存后重启 `dsh web`。 |
|
|
241
|
+
| 改了注入 / 抽取 / embedding / 芯片开关没生效 | 同样要重启 `dsh web`。 |
|
|
242
|
+
| 会话层只有一个「后续」方框 | 正常(在会话层已打开时)。方框跟 DSH 压缩走,会话长不等于已经压过(默认约窗口 80% 才自动压)。要分段:对话里 `/compact`,或点「生成检查点」(只切图,不压窗口)。旧会话可点「更新检查点」补已经发生过的压缩 / 分叉。 |
|
|
243
|
+
| `ctx_find` 什么都没有 | 新会话默认只注入短地图,模型要自己调工具。也可以在芯片条「新增」或对模型说「记住:…」。闲聊和一次性改文件不会自动入库。 |
|
|
244
|
+
| 报 `.tdb` 被占用 / 打不开 | 只开一个 `dsh web`。不要同时跑两份链接了本插件的 Node 进程。 |
|
|
245
|
+
| embedding 填了但检索没变准 | 失败会退回关键词 + 图。看 `~/.dsh/trivium.json` 里 URL 是否 OpenAI 兼容(含 `/v1` 那种)。改完重启。 |
|
|
246
|
+
| 卸载后记忆还在 | 先停 `dsh web` 再 `dsh plugin remove`。剩下的 `.tdb` 是从未用 0.4.10 打开过的工作区,到该文件夹删 `.dsh/trivium.tdb`。只在配置里 disabled 不会清文件。 |
|
|
247
|
+
| 检查更新失败 | 只请求 npm registry 拿最新版本号,不影响记忆;过一会儿再点即可。 |
|
|
248
|
+
| rc.8 之后旧对话打不开 | 那是 DSH 自己的会话库格式变了,不是 `.tdb` 坏了。图记忆仍可 `ctx_find`;对不上号的旧方框可当情节残留。 |
|
|
249
|
+
|
|
250
|
+
本地联调:仓库里 `npm install` 后 `node scripts/link-dsh.mjs`,再重启 `dsh web`。`triviumdb` 是原生模块,装不上时先看 Node 是否满足 `package.json` 的 `engines`(`^22.19.0 || >=24`)。
|
|
251
|
+
|
|
213
252
|
---
|
|
214
253
|
|
|
215
254
|
## 发布信息
|
|
216
255
|
|
|
217
256
|
- GitHub: https://github.com/QWQcool/dsh-trivium
|
|
218
|
-
- npm: [`dsh-trivium@0.4.
|
|
257
|
+
- npm: [`dsh-trivium@0.4.10`](https://www.npmjs.com/package/dsh-trivium)
|
|
219
258
|
- 测试宿主:`@deepseek-ai/dsh@0.1.1-rc.2`(兼 `0.1.0-rc.8`)
|
|
220
259
|
- License: MIT(依赖 [TriviumDB](https://github.com/YoKONCy/TriviumDB) 为 Apache-2.0)
|
|
221
260
|
|
package/lib/client.js
CHANGED
|
@@ -28,6 +28,11 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
recallAnchorHint: "用户文本命中已有 entity 的 name/aliases 时,注入该实体 1 跳 about/decided/broke/fixed 邻居(≤300 token)。",
|
|
29
29
|
extract: "compaction/end 抽取",
|
|
30
30
|
extractHint: "规则 + 一次小 prompt;失败只记日志。turn 结束后约 12 秒空闲会再抽一次,下次 session-start 也会重放 pending。每轮写入正文合计 ≤3000 字、最多 24 条。",
|
|
31
|
+
chipTab: "芯片",
|
|
32
|
+
chipsSetting: "芯片标签(默认关)",
|
|
33
|
+
chipsSettingHint: "打开后,标题栏「对话 / 轨迹」旁出现「芯片」。勾选的条目从下一轮注入(L0,≤300 token)。节点仍在 .tdb,关开关不删记忆。改完保存后重启 dsh web,标签才会出现或消失。",
|
|
34
|
+
sessionLayer: "会话层(情节画布)",
|
|
35
|
+
sessionLayerHint: "可选。把 compaction / 分叉画成方框,可生成检查点。关着不记 episode,不影响 ctx_find。需先打开芯片标签。",
|
|
31
36
|
markdown: "Markdown 投影",
|
|
32
37
|
markdownHint: "只读导出,带边(- 鉴权走 header X —about→ AuthGateway)。给编辑器和 git 看。改完回写用 JSON 导入或本页编辑,不解析自由散文。",
|
|
33
38
|
exportMd: "导出 Markdown",
|
|
@@ -133,7 +138,8 @@ window.__ModuleLoader__.load({
|
|
|
133
138
|
cancelSelect: "取消选择",
|
|
134
139
|
chipSelectHint: "点芯片多选。归档:软删除,find / 短地图 / 芯片不再出现,节点还在库里。删除:从 .tdb 去掉,不可恢复。",
|
|
135
140
|
chipClipped: "已截到预算(≤300 token)",
|
|
136
|
-
chipPinHint: "
|
|
141
|
+
chipPinHint: "勾选后从当前这段的下一轮注入。",
|
|
142
|
+
chipPinHintMap: "勾选后从当前这段的下一轮注入。空白处拖动画布,滚轮缩放。",
|
|
137
143
|
chipDraftPh: "粘贴或输入要记住的内容。点一次写入,整段就是一条芯片。",
|
|
138
144
|
writePin: "写入并钉选",
|
|
139
145
|
suggested: "建议",
|
|
@@ -164,6 +170,11 @@ window.__ModuleLoader__.load({
|
|
|
164
170
|
recallAnchorHint: "When user text names an existing entity, inject 1-hop about/decided/broke/fixed neighbors (≤300 tokens).",
|
|
165
171
|
extract: "Extract on compaction/end",
|
|
166
172
|
extractHint: "Rules + one small prompt; failures are logged. Idle ~12s after turn/end also extracts; pending replays on next session-start. Per batch: body ≤3000 chars, at most 24 items.",
|
|
173
|
+
chipTab: "Chips",
|
|
174
|
+
chipsSetting: "Chips tab (off by default)",
|
|
175
|
+
chipsSettingHint: "When on, the title bar shows Chips next to Conversation / Trajectory. Checked items inject from the next turn (L0, ≤300 tokens). Nodes stay in .tdb; turning this off does not delete memory. Restart dsh web after save for the tab to appear or disappear.",
|
|
176
|
+
sessionLayer: "Session layer (plot canvas)",
|
|
177
|
+
sessionLayerHint: "Optional. Draws compaction / forks as boxes; Create checkpoint lives here. Off means no episode writes; ctx_find is unchanged. Requires the Chips tab first.",
|
|
167
178
|
markdown: "Markdown projection",
|
|
168
179
|
markdownHint: "Read-only export with edges (e.g. - auth in header X —about→ AuthGateway). For editors and git. Write back with JSON import or this page, not free prose.",
|
|
169
180
|
exportMd: "Export Markdown",
|
|
@@ -269,7 +280,8 @@ window.__ModuleLoader__.load({
|
|
|
269
280
|
cancelSelect: "Cancel select",
|
|
270
281
|
chipSelectHint: "Click chips to multi-select. Archive: soft delete, gone from find / short map / chips, node stays in .tdb. Delete: removed from .tdb, not recoverable.",
|
|
271
282
|
chipClipped: "Clipped to budget (≤300 tokens)",
|
|
272
|
-
chipPinHint: "Checked chips inject from the next turn of this segment.
|
|
283
|
+
chipPinHint: "Checked chips inject from the next turn of this segment.",
|
|
284
|
+
chipPinHintMap: "Checked chips inject from the next turn of this segment. Drag empty canvas to pan, scroll to zoom.",
|
|
273
285
|
chipDraftPh: "Paste or type a fact. One click writes one chip; a whole paste is still one node.",
|
|
274
286
|
writePin: "Write and pin",
|
|
275
287
|
suggested: "suggested",
|
|
@@ -408,6 +420,8 @@ window.__ModuleLoader__.load({
|
|
|
408
420
|
const [saved, setSaved] = react.useState(false);
|
|
409
421
|
const [recallMode, setRecallMode] = react.useState("off");
|
|
410
422
|
const [extractEnabled, setExtractEnabled] = react.useState(true);
|
|
423
|
+
const [chipsEnabled, setChipsEnabled] = react.useState(false);
|
|
424
|
+
const [sessionLayerEnabled, setSessionLayerEnabled] = react.useState(false);
|
|
411
425
|
const [embeddingEnabled, setEmbeddingEnabled] = react.useState(false);
|
|
412
426
|
const [embeddingUrl, setEmbeddingUrl] = react.useState("");
|
|
413
427
|
const [embeddingModel, setEmbeddingModel] = react.useState("text-embedding-3-small");
|
|
@@ -428,6 +442,8 @@ window.__ModuleLoader__.load({
|
|
|
428
442
|
setStatus(st);
|
|
429
443
|
setRecallMode(st.recallMode || (st.autoRecall ? "auto" : st.anchorRecall ? "anchor" : "off"));
|
|
430
444
|
setExtractEnabled(st.extractEnabled !== false);
|
|
445
|
+
setChipsEnabled(!!st.chipsEnabled);
|
|
446
|
+
setSessionLayerEnabled(!!st.sessionLayerEnabled);
|
|
431
447
|
setEmbeddingEnabled(!!st.embeddingEnabled);
|
|
432
448
|
setEmbeddingUrl(st.embeddingUrl || "");
|
|
433
449
|
setEmbeddingModel(st.embeddingModel || "text-embedding-3-small");
|
|
@@ -466,6 +482,8 @@ window.__ModuleLoader__.load({
|
|
|
466
482
|
const body = {
|
|
467
483
|
recallMode,
|
|
468
484
|
extractEnabled,
|
|
485
|
+
chipsEnabled,
|
|
486
|
+
sessionLayerEnabled,
|
|
469
487
|
embeddingEnabled,
|
|
470
488
|
embeddingUrl,
|
|
471
489
|
embeddingModel,
|
|
@@ -801,6 +819,36 @@ window.__ModuleLoader__.load({
|
|
|
801
819
|
jsx("div", { style: ui.hint, children: L.extractHint }),
|
|
802
820
|
],
|
|
803
821
|
}),
|
|
822
|
+
jsxs("div", {
|
|
823
|
+
style: ui.card,
|
|
824
|
+
children: [
|
|
825
|
+
jsxs("label", {
|
|
826
|
+
style: ui.switch,
|
|
827
|
+
children: [
|
|
828
|
+
jsx("input", {
|
|
829
|
+
type: "checkbox",
|
|
830
|
+
checked: chipsEnabled,
|
|
831
|
+
onChange: (e) => setChipsEnabled(e.target.checked),
|
|
832
|
+
}),
|
|
833
|
+
jsx("span", { children: L.chipsSetting }),
|
|
834
|
+
],
|
|
835
|
+
}),
|
|
836
|
+
jsx("div", { style: ui.hint, children: L.chipsSettingHint }),
|
|
837
|
+
jsxs("label", {
|
|
838
|
+
style: { ...ui.switch, fontWeight: 500, paddingLeft: 22, opacity: chipsEnabled ? 1 : 0.5 },
|
|
839
|
+
children: [
|
|
840
|
+
jsx("input", {
|
|
841
|
+
type: "checkbox",
|
|
842
|
+
checked: sessionLayerEnabled,
|
|
843
|
+
disabled: !chipsEnabled,
|
|
844
|
+
onChange: (e) => setSessionLayerEnabled(e.target.checked),
|
|
845
|
+
}),
|
|
846
|
+
jsx("span", { children: L.sessionLayer }),
|
|
847
|
+
],
|
|
848
|
+
}),
|
|
849
|
+
jsx("div", { style: { ...ui.hint, paddingLeft: 22 }, children: L.sessionLayerHint }),
|
|
850
|
+
],
|
|
851
|
+
}),
|
|
804
852
|
jsxs("div", {
|
|
805
853
|
style: ui.card,
|
|
806
854
|
children: [
|
|
@@ -2102,7 +2150,8 @@ window.__ModuleLoader__.load({
|
|
|
2102
2150
|
const [pan, setPan] = react.useState({ x: 0, y: 0 });
|
|
2103
2151
|
const [chipQuery, setChipQuery] = react.useState("");
|
|
2104
2152
|
const [inheritPins, setInheritPins] = react.useState(false);
|
|
2105
|
-
const [chipsOpen, setChipsOpen] = react.useState(
|
|
2153
|
+
const [chipsOpen, setChipsOpen] = react.useState(true);
|
|
2154
|
+
const [sessionLayer, setSessionLayer] = react.useState(false);
|
|
2106
2155
|
const [chipSelect, setChipSelect] = react.useState(false);
|
|
2107
2156
|
const [pickedIds, setPickedIds] = react.useState([]);
|
|
2108
2157
|
const [draftOpen, setDraftOpen] = react.useState(false);
|
|
@@ -2151,9 +2200,18 @@ window.__ModuleLoader__.load({
|
|
|
2151
2200
|
const load = react.useCallback(async () => {
|
|
2152
2201
|
if (!sessionId) return { nodes: [], edges: [] };
|
|
2153
2202
|
try {
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2203
|
+
const st = await api("/api/dsh-trivium/status");
|
|
2204
|
+
const layer = !!st.sessionLayerEnabled;
|
|
2205
|
+
setSessionLayer(layer);
|
|
2206
|
+
let map = { nodes: [], edges: [] };
|
|
2207
|
+
if (layer) {
|
|
2208
|
+
map = await api("/api/dsh-trivium/map" + qs);
|
|
2209
|
+
setNodes(map.nodes || []);
|
|
2210
|
+
setEdges(map.edges || []);
|
|
2211
|
+
} else {
|
|
2212
|
+
setNodes([]);
|
|
2213
|
+
setEdges([]);
|
|
2214
|
+
}
|
|
2157
2215
|
const chipResp = await api("/api/dsh-trivium/chips" + qs);
|
|
2158
2216
|
setChips(chipResp.chips || []);
|
|
2159
2217
|
const pinResp = await api("/api/dsh-trivium/pins" + qs);
|
|
@@ -2511,6 +2569,8 @@ window.__ModuleLoader__.load({
|
|
|
2511
2569
|
livePreview: lastUserPreview(chat),
|
|
2512
2570
|
};
|
|
2513
2571
|
|
|
2572
|
+
const showChips = !sessionLayer || chipsOpen;
|
|
2573
|
+
|
|
2514
2574
|
return jsxs("div", {
|
|
2515
2575
|
"data-map-build": "sessions-inject",
|
|
2516
2576
|
style: mapUi.root,
|
|
@@ -2528,33 +2588,39 @@ window.__ModuleLoader__.load({
|
|
|
2528
2588
|
jsxs("div", {
|
|
2529
2589
|
style: mapUi.chipsActions,
|
|
2530
2590
|
children: [
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2591
|
+
sessionLayer
|
|
2592
|
+
? jsx("button", {
|
|
2593
|
+
type: "button",
|
|
2594
|
+
style: mapUi.actionBtn,
|
|
2595
|
+
disabled: busy,
|
|
2596
|
+
title: t("cutTitle"),
|
|
2597
|
+
onClick: cutCheckpoint,
|
|
2598
|
+
children: busy ? t("cutting") : t("cutBtn"),
|
|
2599
|
+
})
|
|
2600
|
+
: null,
|
|
2601
|
+
sessionLayer
|
|
2602
|
+
? jsx("button", {
|
|
2603
|
+
type: "button",
|
|
2604
|
+
style: mapUi.actionBtn,
|
|
2605
|
+
disabled: busy,
|
|
2606
|
+
title: t("updateTitle"),
|
|
2607
|
+
onClick: syncCheckpoints,
|
|
2608
|
+
children: busy ? t("updating") : t("updateBtn"),
|
|
2609
|
+
})
|
|
2610
|
+
: null,
|
|
2611
|
+
sessionLayer
|
|
2612
|
+
? jsx("button", {
|
|
2613
|
+
type: "button",
|
|
2614
|
+
style: mapUi.actionBtn,
|
|
2615
|
+
onClick: () => setChipsOpen((v) => !v),
|
|
2616
|
+
children: chipsOpen ? t("collapse") : t("expand"),
|
|
2617
|
+
})
|
|
2618
|
+
: null,
|
|
2553
2619
|
],
|
|
2554
2620
|
}),
|
|
2555
2621
|
],
|
|
2556
2622
|
}),
|
|
2557
|
-
|
|
2623
|
+
showChips
|
|
2558
2624
|
? jsxs("div", {
|
|
2559
2625
|
style: { display: "flex", flexDirection: "column", gap: 8 },
|
|
2560
2626
|
children: [
|
|
@@ -2568,17 +2634,19 @@ window.__ModuleLoader__.load({
|
|
|
2568
2634
|
onChange: (e) => setChipQuery(e.target.value),
|
|
2569
2635
|
style: mapUi.search,
|
|
2570
2636
|
}),
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2637
|
+
sessionLayer
|
|
2638
|
+
? jsxs("label", {
|
|
2639
|
+
style: mapUi.inherit,
|
|
2640
|
+
children: [
|
|
2641
|
+
jsx("input", {
|
|
2642
|
+
type: "checkbox",
|
|
2643
|
+
checked: inheritPins,
|
|
2644
|
+
onChange: (e) => setInheritPins(e.target.checked),
|
|
2645
|
+
}),
|
|
2646
|
+
t("inheritPins"),
|
|
2647
|
+
],
|
|
2648
|
+
})
|
|
2649
|
+
: null,
|
|
2582
2650
|
jsxs("div", {
|
|
2583
2651
|
style: mapUi.chipsActions,
|
|
2584
2652
|
children: [
|
|
@@ -2630,7 +2698,9 @@ window.__ModuleLoader__.load({
|
|
|
2630
2698
|
? t("chipSelectHint")
|
|
2631
2699
|
: clipped
|
|
2632
2700
|
? t("chipClipped")
|
|
2633
|
-
:
|
|
2701
|
+
: sessionLayer
|
|
2702
|
+
? t("chipPinHintMap")
|
|
2703
|
+
: t("chipPinHint"),
|
|
2634
2704
|
}),
|
|
2635
2705
|
draftOpen
|
|
2636
2706
|
? jsxs("div", {
|
|
@@ -2707,7 +2777,8 @@ window.__ModuleLoader__.load({
|
|
|
2707
2777
|
})
|
|
2708
2778
|
: null,
|
|
2709
2779
|
error ? jsx("div", { style: mapUi.error, children: error }) : null,
|
|
2710
|
-
|
|
2780
|
+
sessionLayer
|
|
2781
|
+
? jsx("div", {
|
|
2711
2782
|
ref: canvasRef,
|
|
2712
2783
|
style: mapUi.canvas,
|
|
2713
2784
|
onPointerDown,
|
|
@@ -2810,7 +2881,8 @@ window.__ModuleLoader__.load({
|
|
|
2810
2881
|
);
|
|
2811
2882
|
}),
|
|
2812
2883
|
}),
|
|
2813
|
-
})
|
|
2884
|
+
})
|
|
2885
|
+
: null,
|
|
2814
2886
|
forkDraft
|
|
2815
2887
|
? jsx("div", {
|
|
2816
2888
|
style: mapUi.overlay,
|
|
@@ -2859,37 +2931,7 @@ window.__ModuleLoader__.load({
|
|
|
2859
2931
|
});
|
|
2860
2932
|
}
|
|
2861
2933
|
|
|
2862
|
-
function
|
|
2863
|
-
workspacesList = ctx.workspaces?.list || null;
|
|
2864
|
-
const sessions = ctx.sessions;
|
|
2865
|
-
try {
|
|
2866
|
-
const snap = ctx.locale && typeof ctx.locale.getLocale === "function" ? ctx.locale.getLocale() : null;
|
|
2867
|
-
if (snap && snap.active) setUiLocale(snap.active);
|
|
2868
|
-
} catch {
|
|
2869
|
-
// host may not expose locale
|
|
2870
|
-
}
|
|
2871
|
-
try {
|
|
2872
|
-
ctx.on("locale/change", (snap) => {
|
|
2873
|
-
if (snap && snap.active) setUiLocale(snap.active);
|
|
2874
|
-
});
|
|
2875
|
-
} catch {
|
|
2876
|
-
// ignore
|
|
2877
|
-
}
|
|
2878
|
-
ctx.slots.inject(
|
|
2879
|
-
"settings.section",
|
|
2880
|
-
() =>
|
|
2881
|
-
ctx.slots.register(
|
|
2882
|
-
{
|
|
2883
|
-
name: "settings.section",
|
|
2884
|
-
id: "dsh-trivium",
|
|
2885
|
-
order: 62,
|
|
2886
|
-
label: () => L.nav,
|
|
2887
|
-
inject: () => ({}),
|
|
2888
|
-
},
|
|
2889
|
-
TriviumCard,
|
|
2890
|
-
),
|
|
2891
|
-
"dsh-trivium: settings section",
|
|
2892
|
-
);
|
|
2934
|
+
function registerChipView(ctx, sessions) {
|
|
2893
2935
|
ctx.slots.inject(
|
|
2894
2936
|
"conversation.view",
|
|
2895
2937
|
() => {
|
|
@@ -2897,9 +2939,9 @@ window.__ModuleLoader__.load({
|
|
|
2897
2939
|
return ctx.slots.register(
|
|
2898
2940
|
{
|
|
2899
2941
|
name: "conversation.view",
|
|
2900
|
-
id: "
|
|
2942
|
+
id: "chips",
|
|
2901
2943
|
order: 20,
|
|
2902
|
-
label: () => t("
|
|
2944
|
+
label: () => t("chipTab"),
|
|
2903
2945
|
...(chatStore ? { store: chatStore } : {}),
|
|
2904
2946
|
inject: () => ({
|
|
2905
2947
|
fork: (opts) => sessions.fork(opts),
|
|
@@ -2930,8 +2972,49 @@ window.__ModuleLoader__.load({
|
|
|
2930
2972
|
SessionMapView,
|
|
2931
2973
|
);
|
|
2932
2974
|
},
|
|
2933
|
-
"dsh-trivium:
|
|
2975
|
+
"dsh-trivium: chips",
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function apply(ctx) {
|
|
2980
|
+
workspacesList = ctx.workspaces?.list || null;
|
|
2981
|
+
const sessions = ctx.sessions;
|
|
2982
|
+
try {
|
|
2983
|
+
const snap = ctx.locale && typeof ctx.locale.getLocale === "function" ? ctx.locale.getLocale() : null;
|
|
2984
|
+
if (snap && snap.active) setUiLocale(snap.active);
|
|
2985
|
+
} catch {
|
|
2986
|
+
// host may not expose locale
|
|
2987
|
+
}
|
|
2988
|
+
try {
|
|
2989
|
+
ctx.on("locale/change", (snap) => {
|
|
2990
|
+
if (snap && snap.active) setUiLocale(snap.active);
|
|
2991
|
+
});
|
|
2992
|
+
} catch {
|
|
2993
|
+
// ignore
|
|
2994
|
+
}
|
|
2995
|
+
ctx.slots.inject(
|
|
2996
|
+
"settings.section",
|
|
2997
|
+
() =>
|
|
2998
|
+
ctx.slots.register(
|
|
2999
|
+
{
|
|
3000
|
+
name: "settings.section",
|
|
3001
|
+
id: "dsh-trivium",
|
|
3002
|
+
order: 62,
|
|
3003
|
+
label: () => L.nav,
|
|
3004
|
+
inject: () => ({}),
|
|
3005
|
+
},
|
|
3006
|
+
TriviumCard,
|
|
3007
|
+
),
|
|
3008
|
+
"dsh-trivium: settings section",
|
|
2934
3009
|
);
|
|
3010
|
+
fetch("/api/dsh-trivium/status")
|
|
3011
|
+
.then((resp) => (resp && resp.ok ? resp.json() : null))
|
|
3012
|
+
.then((st) => {
|
|
3013
|
+
if (st && st.chipsEnabled === true) registerChipView(ctx, sessions);
|
|
3014
|
+
})
|
|
3015
|
+
.catch(() => {
|
|
3016
|
+
// default off: no title-bar tab
|
|
3017
|
+
});
|
|
2935
3018
|
}
|
|
2936
3019
|
|
|
2937
3020
|
exports.apply = apply;
|
package/lib/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
textFromContent,
|
|
21
21
|
} from "./extract.js";
|
|
22
22
|
import { readUiSettings, registerMemoryApi } from "./server.js";
|
|
23
|
-
import { applyRecallMode, recallModeOf } from "./settings.js";
|
|
23
|
+
import { applyRecallMode, chipsEnabledOf, recallModeOf, sessionLayerEnabledOf } from "./settings.js";
|
|
24
24
|
import { configureEmbedding } from "./embed.js";
|
|
25
25
|
import { recordCheckpoint, ensureTail, syncForkLineage } from "./episode.js";
|
|
26
26
|
import { buildPinInject } from "./pins.js";
|
|
@@ -33,6 +33,8 @@ const DEFAULTS = {
|
|
|
33
33
|
anchorRecall: false,
|
|
34
34
|
recallMode: "off",
|
|
35
35
|
extractEnabled: true,
|
|
36
|
+
chipsEnabled: false,
|
|
37
|
+
sessionLayerEnabled: false,
|
|
36
38
|
writeApproval: false,
|
|
37
39
|
mapTokenBudget: 400,
|
|
38
40
|
expandDepth: 1,
|
|
@@ -283,7 +285,7 @@ export function apply(ctx, config = {}) {
|
|
|
283
285
|
try {
|
|
284
286
|
const session = agent.session;
|
|
285
287
|
const sessionId = sessionKey(session);
|
|
286
|
-
if (sessionId) {
|
|
288
|
+
if (sessionId && sessionLayerEnabledOf(optionsOf())) {
|
|
287
289
|
const seedLen = session?.header?.seedLength;
|
|
288
290
|
ensureTail(db, sessionId, {
|
|
289
291
|
atSeq: Number.isFinite(Number(seedLen)) && seedLen > 0 ? seedLen - 1 : 0,
|
|
@@ -334,7 +336,7 @@ export function apply(ctx, config = {}) {
|
|
|
334
336
|
}).catch((err) => log.warn?.(`[dsh-trivium] compaction extract: ${err.message}`));
|
|
335
337
|
const cwd = buf.cwd || session.header?.cwd;
|
|
336
338
|
const sessionId = buf.sessionId || session.id;
|
|
337
|
-
if (cwd && sessionId) {
|
|
339
|
+
if (cwd && sessionId && sessionLayerEnabledOf(optionsOf())) {
|
|
338
340
|
const summary =
|
|
339
341
|
buf.compactionSummary?.id === event.data?.compactionId && buf.compactionSummary.text
|
|
340
342
|
? buf.compactionSummary.text
|
|
@@ -361,7 +363,13 @@ export function apply(ctx, config = {}) {
|
|
|
361
363
|
const bufEnd = bufOf(session);
|
|
362
364
|
const lastUser = [...(bufEnd.turns || [])].reverse().find((t) => t.role === "user");
|
|
363
365
|
const preview = String(lastUser?.text || "").trim();
|
|
364
|
-
if (
|
|
366
|
+
if (
|
|
367
|
+
bufEnd.cwd &&
|
|
368
|
+
bufEnd.sessionId &&
|
|
369
|
+
preview &&
|
|
370
|
+
!preview.startsWith("/") &&
|
|
371
|
+
sessionLayerEnabledOf(optionsOf())
|
|
372
|
+
) {
|
|
365
373
|
openWorkspaceDb(bufEnd.cwd)
|
|
366
374
|
.then((db) => {
|
|
367
375
|
ensureTail(db, bufEnd.sessionId, {
|
|
@@ -409,24 +417,26 @@ export function apply(ctx, config = {}) {
|
|
|
409
417
|
const extra = [];
|
|
410
418
|
const db = await openWorkspaceDb(cwdOf(agent));
|
|
411
419
|
const sessionId = sessionKey(agent.session);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
420
|
+
const options = optionsOf();
|
|
421
|
+
if (chipsEnabledOf(options)) {
|
|
422
|
+
try {
|
|
423
|
+
const pin = buildPinInject(db, sessionId, 300);
|
|
424
|
+
if (pin.text) {
|
|
425
|
+
extra.push(
|
|
426
|
+
createUserMessage({
|
|
427
|
+
content: [{ type: "text", text: pin.text }],
|
|
428
|
+
source: {
|
|
429
|
+
kind: "plugin",
|
|
430
|
+
plugin: "dsh-trivium",
|
|
431
|
+
form: "pins",
|
|
432
|
+
},
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
} catch (err) {
|
|
437
|
+
log.warn?.(`[dsh-trivium] pre-step pins skipped: ${err.message}`);
|
|
425
438
|
}
|
|
426
|
-
} catch (err) {
|
|
427
|
-
log.warn?.(`[dsh-trivium] pre-step pins skipped: ${err.message}`);
|
|
428
439
|
}
|
|
429
|
-
const options = optionsOf();
|
|
430
440
|
const mode = recallModeOf(options);
|
|
431
441
|
if (mode !== "off") {
|
|
432
442
|
const proposed = [...(decision.messages || []), ...(messages || [])];
|
package/lib/purge.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** Wipe plugin files on real uninstall. Never run this on dsh web restart. */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readdirSync, unlinkSync } from "node:fs";
|
|
4
|
+
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { listedWorkspaces, settingsFilePath } from "./settings.js";
|
|
6
|
+
import { closeAll, listOpenWorkspaces } from "./store.js";
|
|
7
|
+
|
|
8
|
+
export function isTriviumArtifactName(name) {
|
|
9
|
+
const n = String(name || "");
|
|
10
|
+
return n === "trivium-pending.json" || n === "trivium.tdb" || n.startsWith("trivium.tdb");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `dsh plugin remove` / `pnpm remove` should wipe.
|
|
15
|
+
* `pnpm add` / update / ci reinstall the same package and must keep memory.
|
|
16
|
+
*/
|
|
17
|
+
export function shouldWipeOnUninstall(env = process.env, argv = process.argv) {
|
|
18
|
+
if (argv.includes("--force-purge")) return true;
|
|
19
|
+
const event = String(env.npm_lifecycle_event || "").toLowerCase();
|
|
20
|
+
if (event && event !== "preuninstall" && event !== "uninstall") return false;
|
|
21
|
+
|
|
22
|
+
const cmd = String(env.npm_command || "").toLowerCase();
|
|
23
|
+
if (cmd === "remove" || cmd === "uninstall") return true;
|
|
24
|
+
if (cmd === "install" || cmd === "add" || cmd === "update" || cmd === "upgrade" || cmd === "ci" || cmd === "dedupe") {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cooked = String(env.npm_config_argv || "").toLowerCase();
|
|
29
|
+
if (/\b(remove|uninstall)\b/.test(cooked) && !/\b(install|add|update|upgrade)\b/.test(cooked)) return true;
|
|
30
|
+
if (/\b(install|add|update|upgrade|ci)\b/.test(cooked)) return false;
|
|
31
|
+
|
|
32
|
+
// Unknown command: keep memory. Upgrades must never delete the store.
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function collectPurgeCwds(extraCwds = []) {
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const out = [];
|
|
39
|
+
const add = (cwd) => {
|
|
40
|
+
const key = String(cwd || "").trim();
|
|
41
|
+
if (!key) return;
|
|
42
|
+
const resolved = resolve(key);
|
|
43
|
+
if (seen.has(resolved)) return;
|
|
44
|
+
seen.add(resolved);
|
|
45
|
+
out.push(resolved);
|
|
46
|
+
};
|
|
47
|
+
for (const cwd of listedWorkspaces()) add(cwd);
|
|
48
|
+
for (const row of listOpenWorkspaces()) add(row.cwd);
|
|
49
|
+
for (const cwd of extraCwds) add(cwd);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function unlinkQuiet(path, files, errors) {
|
|
54
|
+
try {
|
|
55
|
+
unlinkSync(path);
|
|
56
|
+
files.push(path);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err && err.code !== "ENOENT") errors.push({ path, message: String(err.message || err) });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function purgeWorkspaceArtifacts(cwd, files, errors) {
|
|
63
|
+
const dir = join(resolve(cwd), ".dsh");
|
|
64
|
+
if (!existsSync(dir)) return;
|
|
65
|
+
let names = [];
|
|
66
|
+
try {
|
|
67
|
+
names = readdirSync(dir);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
errors.push({ path: dir, message: String(err.message || err) });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const name of names) {
|
|
73
|
+
if (!isTriviumArtifactName(name)) continue;
|
|
74
|
+
unlinkQuiet(join(dir, name), files, errors);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Delete known workspace traces and ~/.dsh/trivium.json. Leaves other .dsh files alone. */
|
|
79
|
+
export function purgeAllTraces({ extraCwds = [], close = closeAll } = {}) {
|
|
80
|
+
const settingsFile = settingsFilePath();
|
|
81
|
+
const workspaces = collectPurgeCwds(extraCwds);
|
|
82
|
+
try {
|
|
83
|
+
close();
|
|
84
|
+
} catch {
|
|
85
|
+
// ignore
|
|
86
|
+
}
|
|
87
|
+
const files = [];
|
|
88
|
+
const errors = [];
|
|
89
|
+
for (const cwd of workspaces) purgeWorkspaceArtifacts(cwd, files, errors);
|
|
90
|
+
unlinkQuiet(settingsFile, files, errors);
|
|
91
|
+
return { ok: errors.length === 0, workspaces, files, errors, settingsFile };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function shouldRunCli() {
|
|
95
|
+
return basename(String(process.argv[1] || "")) === "purge.js";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function runUninstallPurge(env = process.env, argv = process.argv, log = console) {
|
|
99
|
+
if (!shouldWipeOnUninstall(env, argv)) {
|
|
100
|
+
log.info?.("[dsh-trivium] skip data wipe (install/update, not remove)");
|
|
101
|
+
return { skipped: true };
|
|
102
|
+
}
|
|
103
|
+
const result = purgeAllTraces();
|
|
104
|
+
const n = result.files.length;
|
|
105
|
+
log.info?.(`[dsh-trivium] removed ${n} plugin file${n === 1 ? "" : "s"} from ${result.workspaces.length} workspace(s)`);
|
|
106
|
+
if (result.errors.length) {
|
|
107
|
+
log.warn?.("[dsh-trivium] some files could not be deleted (stop dsh web and delete leftovers):");
|
|
108
|
+
for (const err of result.errors) log.warn?.(` ${err.path}: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (shouldRunCli()) {
|
|
114
|
+
const result = runUninstallPurge();
|
|
115
|
+
if (result?.errors?.length) process.exitCode = 1;
|
|
116
|
+
}
|
package/lib/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
updateNode,
|
|
16
16
|
} from "./store.js";
|
|
17
17
|
import { embeddingPublicStatus } from "./embed.js";
|
|
18
|
-
import { recallModeOf, writeUiSettings } from "./settings.js";
|
|
18
|
+
import { chipsEnabledOf, recallModeOf, sessionLayerEnabledOf, writeUiSettings } from "./settings.js";
|
|
19
19
|
import { discoverWorkbuddyFiles, discoverExternalFiles, exportMarkdown, importWorkbuddy, importExternal } from "./markdown.js";
|
|
20
20
|
import { checkNpmUpdate, installedVersion } from "./update.js";
|
|
21
21
|
import { writeGateMessage } from "./hygiene.js";
|
|
@@ -114,6 +114,8 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
114
114
|
autoRecall: mode === "auto",
|
|
115
115
|
anchorRecall: mode === "anchor",
|
|
116
116
|
extractEnabled: opts.extractEnabled !== false,
|
|
117
|
+
chipsEnabled: chipsEnabledOf(opts),
|
|
118
|
+
sessionLayerEnabled: opts.sessionLayerEnabled === true,
|
|
117
119
|
writeApproval: !!opts.writeApproval,
|
|
118
120
|
mapTokenBudget: opts.mapTokenBudget || 400,
|
|
119
121
|
workspaces: listOpenWorkspaces(),
|
|
@@ -309,6 +311,10 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
if (path === "/api/dsh-trivium/map/backfill" && method === "POST") {
|
|
314
|
+
if (!sessionLayerEnabledOf(liveOptions())) {
|
|
315
|
+
sendJson(res, 400, { ok: false, message: "session layer off" });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
312
318
|
const { cwd, db } = await dbForRequest(url);
|
|
313
319
|
if (!db) {
|
|
314
320
|
sendJson(res, 400, { ok: false, message: "no workspace" });
|
|
@@ -326,6 +332,10 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
326
332
|
}
|
|
327
333
|
|
|
328
334
|
if (path === "/api/dsh-trivium/map/cut" && method === "POST") {
|
|
335
|
+
if (!sessionLayerEnabledOf(liveOptions())) {
|
|
336
|
+
sendJson(res, 400, { ok: false, message: "session layer off" });
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
329
339
|
const { cwd, db } = await dbForRequest(url);
|
|
330
340
|
if (!db) {
|
|
331
341
|
sendJson(res, 400, { ok: false, message: "no workspace" });
|
package/lib/settings.js
CHANGED
|
@@ -1,19 +1,68 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { looksHttpUrl } from "./embed.js";
|
|
5
5
|
|
|
6
6
|
export const SETTINGS_FILE = join(homedir(), ".dsh", "trivium.json");
|
|
7
7
|
|
|
8
|
+
/** Tests may set DSH_TRIVIUM_SETTINGS to a temp file so we never touch the live home config. */
|
|
9
|
+
export function settingsFilePath() {
|
|
10
|
+
const override = String(process.env.DSH_TRIVIUM_SETTINGS || "").trim();
|
|
11
|
+
return override ? resolve(override) : SETTINGS_FILE;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function persistSettings(next) {
|
|
15
|
+
const file = settingsFilePath();
|
|
16
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
17
|
+
writeFileSync(file, JSON.stringify(next, null, 2), "utf8");
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export function readUiSettings() {
|
|
9
22
|
try {
|
|
10
|
-
const raw = JSON.parse(readFileSync(
|
|
23
|
+
const raw = JSON.parse(readFileSync(settingsFilePath(), "utf8"));
|
|
11
24
|
return raw && typeof raw === "object" ? raw : {};
|
|
12
25
|
} catch {
|
|
13
26
|
return {};
|
|
14
27
|
}
|
|
15
28
|
}
|
|
16
29
|
|
|
30
|
+
export function listedWorkspaces() {
|
|
31
|
+
const raw = readUiSettings().workspaces;
|
|
32
|
+
if (!Array.isArray(raw)) return [];
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const item of raw) {
|
|
36
|
+
const cwd = String(item || "").trim();
|
|
37
|
+
if (!cwd) continue;
|
|
38
|
+
const key = resolve(cwd);
|
|
39
|
+
if (seen.has(key)) continue;
|
|
40
|
+
seen.add(key);
|
|
41
|
+
out.push(key);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isInsideDir(parent, child) {
|
|
47
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
48
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(".." + sep));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Remember a workspace so uninstall can delete its .tdb. No-op if already listed. */
|
|
52
|
+
export function rememberWorkspace(cwd) {
|
|
53
|
+
const key = String(cwd || "").trim();
|
|
54
|
+
if (!key) return listedWorkspaces();
|
|
55
|
+
const resolved = resolve(key);
|
|
56
|
+
if (!process.env.DSH_TRIVIUM_SETTINGS && isInsideDir(tmpdir(), resolved)) {
|
|
57
|
+
return listedWorkspaces();
|
|
58
|
+
}
|
|
59
|
+
const prev = readUiSettings();
|
|
60
|
+
const workspaces = listedWorkspaces();
|
|
61
|
+
if (workspaces.includes(resolved)) return workspaces;
|
|
62
|
+
persistSettings({ ...prev, workspaces: [...workspaces, resolved] });
|
|
63
|
+
return [...workspaces, resolved];
|
|
64
|
+
}
|
|
65
|
+
|
|
17
66
|
export function recallModeOf(opts = {}) {
|
|
18
67
|
const mode = opts.recallMode;
|
|
19
68
|
if (mode === "auto" || mode === "anchor" || mode === "off") return mode;
|
|
@@ -23,6 +72,16 @@ export function recallModeOf(opts = {}) {
|
|
|
23
72
|
return "off";
|
|
24
73
|
}
|
|
25
74
|
|
|
75
|
+
/** Title-bar Chips tab + pin inject. Missing key = off. */
|
|
76
|
+
export function chipsEnabledOf(opts = {}) {
|
|
77
|
+
return opts.chipsEnabled === true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Checkpoint canvas. Nested under chips; missing key = off. */
|
|
81
|
+
export function sessionLayerEnabledOf(opts = {}) {
|
|
82
|
+
return chipsEnabledOf(opts) && opts.sessionLayerEnabled === true;
|
|
83
|
+
}
|
|
84
|
+
|
|
26
85
|
export function applyRecallMode(target, mode) {
|
|
27
86
|
const next = mode === "auto" || mode === "anchor" ? mode : "off";
|
|
28
87
|
target.recallMode = next;
|
|
@@ -32,7 +91,6 @@ export function applyRecallMode(target, mode) {
|
|
|
32
91
|
}
|
|
33
92
|
|
|
34
93
|
export function writeUiSettings(patch) {
|
|
35
|
-
mkdirSync(join(homedir(), ".dsh"), { recursive: true });
|
|
36
94
|
const prev = readUiSettings();
|
|
37
95
|
const next = { ...prev };
|
|
38
96
|
let mode = recallModeOf(prev);
|
|
@@ -50,6 +108,8 @@ export function writeUiSettings(patch) {
|
|
|
50
108
|
}
|
|
51
109
|
applyRecallMode(next, mode);
|
|
52
110
|
if (typeof patch.extractEnabled === "boolean") next.extractEnabled = patch.extractEnabled;
|
|
111
|
+
if (typeof patch.chipsEnabled === "boolean") next.chipsEnabled = patch.chipsEnabled;
|
|
112
|
+
if (typeof patch.sessionLayerEnabled === "boolean") next.sessionLayerEnabled = patch.sessionLayerEnabled;
|
|
53
113
|
if (typeof patch.embeddingEnabled === "boolean") next.embeddingEnabled = patch.embeddingEnabled;
|
|
54
114
|
if (typeof patch.embeddingUrl === "string") {
|
|
55
115
|
const url = patch.embeddingUrl.trim();
|
|
@@ -64,8 +124,9 @@ export function writeUiSettings(patch) {
|
|
|
64
124
|
} else if (prev.pinsBySession && typeof prev.pinsBySession === "object") {
|
|
65
125
|
next.pinsBySession = prev.pinsBySession;
|
|
66
126
|
}
|
|
67
|
-
|
|
68
|
-
|
|
127
|
+
if (Array.isArray(patch.workspaces)) next.workspaces = patch.workspaces;
|
|
128
|
+
else if (Array.isArray(prev.workspaces)) next.workspaces = prev.workspaces;
|
|
129
|
+
return persistSettings(next);
|
|
69
130
|
}
|
|
70
131
|
|
|
71
132
|
export function pinsOf(sessionId) {
|
|
@@ -84,11 +145,10 @@ export function writePins(sessionId, ids) {
|
|
|
84
145
|
if (!key) return nextIds;
|
|
85
146
|
if (!nextIds.length) delete pinsBySession[key];
|
|
86
147
|
else pinsBySession[key] = nextIds;
|
|
87
|
-
|
|
88
|
-
writeFileSync(SETTINGS_FILE, JSON.stringify({ ...prev, pinsBySession }, null, 2), "utf8");
|
|
148
|
+
persistSettings({ ...prev, pinsBySession });
|
|
89
149
|
return nextIds;
|
|
90
150
|
}
|
|
91
151
|
|
|
92
152
|
export function settingsFileExists() {
|
|
93
|
-
return existsSync(
|
|
153
|
+
return existsSync(settingsFilePath());
|
|
94
154
|
}
|
package/lib/store.js
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdirSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { rememberWorkspace } from "./settings.js";
|
|
5
6
|
import { DIM, EDGE_LABELS, BUSINESS_EDGES, NODE_TYPES, indexTextFor, nowIso, weightOf, zeroVector } from "./schema.js";
|
|
6
7
|
import { clipToTokenBudget, estimateTokens } from "./tokens.js";
|
|
7
8
|
import { isStalePayload, parseUntilFromText, queryMentionsUntil, resolveUntilAt } from "./until.js";
|
|
@@ -78,6 +79,11 @@ export async function openWorkspaceDb(cwd) {
|
|
|
78
79
|
ensureWorkspaceRoot(db, cwd);
|
|
79
80
|
rebuildTextIndex(db);
|
|
80
81
|
dbs.set(key, db);
|
|
82
|
+
try {
|
|
83
|
+
rememberWorkspace(key);
|
|
84
|
+
} catch {
|
|
85
|
+
// listing is best-effort; opening the store still succeeds
|
|
86
|
+
}
|
|
81
87
|
return db;
|
|
82
88
|
}
|
|
83
89
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-trivium",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "In-process graph memory
|
|
3
|
+
"version": "0.4.10",
|
|
4
|
+
"description": "In-process graph memory for DeepSeek Harness, backed by TriviumDB.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"smoke-p7": "node scripts/smoke-p7.mjs",
|
|
43
43
|
"smoke-p8": "node scripts/smoke-p8.mjs",
|
|
44
44
|
"smoke-p10": "node scripts/smoke-p10.mjs",
|
|
45
|
+
"smoke-p11": "node scripts/smoke-p11.mjs",
|
|
46
|
+
"preuninstall": "node ./lib/purge.js",
|
|
45
47
|
"verify-live-p2": "node scripts/verify-live-p2.mjs"
|
|
46
48
|
},
|
|
47
49
|
"engines": {
|