dsh-trivium 0.4.4 → 0.4.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 +12 -7
- package/lib/client.js +66 -4
- package/lib/episode.js +35 -0
- package/lib/server.js +18 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ DeepSeek Harness 的跨会话图记忆插件:按节点和边记,默认少注
|
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
|
-
> 前提:已安装 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 并至少启动过一次 `dsh web`。当前测试宿主:`@deepseek-ai/dsh@0.1.0-rc.8
|
|
11
|
+
> 前提:已安装 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 并至少启动过一次 `dsh web`。当前测试宿主:`@deepseek-ai/dsh@0.1.1-rc.2`(`dsh-llm` / `dsh-tools` peer 兼容 `0.1.0-rc.8` 与 `0.1.1-rc.2`)。设置里的「Trivium 记忆」、新会话短地图、跨会话 `ctx_find`、会话图标签均可用。
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
dsh plugin --profile web add dsh-trivium
|
|
@@ -20,7 +20,7 @@ dsh plugin --profile web add dsh-trivium
|
|
|
20
20
|
<workspace>/.dsh/trivium.tdb
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
用 [Dsh_BatStart](https://github.com/QWQcool/Dsh_BatStart)
|
|
23
|
+
用 [Dsh_BatStart](https://github.com/QWQcool/Dsh_BatStart) 的,双击启动就会装上,不必再执行上面这条。
|
|
24
24
|
|
|
25
25
|
本地跟源码联调:
|
|
26
26
|
|
|
@@ -80,7 +80,8 @@ dsh plugin --profile web add dsh-trivium
|
|
|
80
80
|
|
|
81
81
|
- 没压过上下文:只有一个「后续」方框。
|
|
82
82
|
- 一次成功的 compaction 之后:左边历史检查点(摘要来自 DSH),右边仍是「后续」。
|
|
83
|
-
-
|
|
83
|
+
- 点「生成检查点」:把当前「后续」收成左边一格,右边继续当后续。只切情节图,**不**压缩模型窗口。同一轮次再点不会双写。
|
|
84
|
+
- 装插件之前已经压过 / 分过叉的旧会话:点「更新检查点」,从当前对话里的压缩标记和侧边栏分叉补方框(可重复点,不会双写)。没压过就仍只有「后续」——会话长、工具多不等于 DSH 已经压缩(默认约窗口 80% 才自动压)。要压上下文请在对话里输入 `/compact`。
|
|
84
85
|
- 方框自己不压缩。质量来自 DSH 已经做的摘要替换;fork 时不会再总结一遍。
|
|
85
86
|
- 「改名」只改方框标题,不动 DSH 摘要。点方框可跳回对话里对应的检查点,或打开轨迹。
|
|
86
87
|
|
|
@@ -113,7 +114,7 @@ dsh plugin --profile web add dsh-trivium
|
|
|
113
114
|
|
|
114
115
|
## 界面截图
|
|
115
116
|
|
|
116
|
-
以下入口在本机 DSH Web(`0.1.
|
|
117
|
+
以下入口在本机 DSH Web(`0.1.1-rc.2`)上仍在。截图摄于较早的 rc.6 界面。
|
|
117
118
|
|
|
118
119
|
### 会话 — `ctx_find("鉴权")`
|
|
119
120
|
|
|
@@ -147,7 +148,7 @@ dsh plugin --profile web add dsh-trivium
|
|
|
147
148
|
- 抽取会漏;脏数据可在芯片条归档 / 删除,或到设置里改、合并。
|
|
148
149
|
- 同一个 `.tdb` 不要两个 Node 进程同时打开(正常只开一个 `dsh web` 即可)。
|
|
149
150
|
- Markdown 导出是给人看的,不能再解析回去。WorkBuddy 导入是一次性,不是双向同步。
|
|
150
|
-
-
|
|
151
|
+
- 会话图默认只投影 compaction 与 fork,不会按消息条数自己切方框。窗口没到 DSH 压缩线时,「更新检查点」也补不出历史方框;要分段请点「生成检查点」。
|
|
151
152
|
- 改插件设置后需要重启 `dsh web`。
|
|
152
153
|
- 宿主升到 rc.8 后,DSH 自己的旧会话库可能打不开(官方 SQLite 格式不兼容)。工作区里的 `.tdb` 图记忆还在;对不上号的旧会话图方框可以当情节残留,不影响 `ctx_find`。
|
|
153
154
|
|
|
@@ -155,6 +156,10 @@ dsh plugin --profile web add dsh-trivium
|
|
|
155
156
|
|
|
156
157
|
## 更新说明
|
|
157
158
|
|
|
159
|
+
**0.4.6** — 会话图可「生成检查点」:把当前「后续」收成左边一格。不触发 DSH 压缩。
|
|
160
|
+
|
|
161
|
+
**0.4.5** — 宿主 peer 放宽到 `0.1.0-rc.8` 与 `0.1.1-rc.2`,不再嵌一套旧 `dsh-llm` / `dsh-tools`。
|
|
162
|
+
|
|
158
163
|
**0.4.4** — 依赖 `triviumdb@0.7.5`:入边、按 label 扩邻、按 label 删边走引擎 API,不再全表扫边。find / 会话图行为不变。
|
|
159
164
|
|
|
160
165
|
**0.4.3** — 会话图「更新检查点」:把当前对话里 DSH 已有的压缩标记和侧边栏 fork 补进图,给装插件之前的旧会话用。没压过的会话仍然只有「后续」。
|
|
@@ -170,6 +175,6 @@ dsh plugin --profile web add dsh-trivium
|
|
|
170
175
|
## 发布信息
|
|
171
176
|
|
|
172
177
|
- GitHub: https://github.com/QWQcool/dsh-trivium
|
|
173
|
-
- npm: [`dsh-trivium@0.4.
|
|
174
|
-
- 测试宿主:`@deepseek-ai/dsh@0.1.0-rc.8
|
|
178
|
+
- npm: [`dsh-trivium@0.4.6`](https://www.npmjs.com/package/dsh-trivium)
|
|
179
|
+
- 测试宿主:`@deepseek-ai/dsh@0.1.1-rc.2`(兼 `0.1.0-rc.8`)
|
|
175
180
|
- License: MIT(依赖 [TriviumDB](https://github.com/YoKONCy/TriviumDB) 为 Apache-2.0)
|
package/lib/client.js
CHANGED
|
@@ -1009,6 +1009,24 @@ window.__ModuleLoader__.load({
|
|
|
1009
1009
|
return rows.sort((a, b) => a.atSeq - b.atSeq);
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
|
+
function lastChatSeq(chat) {
|
|
1013
|
+
const nodes = chat?.nodes;
|
|
1014
|
+
if (!nodes || typeof nodes.values !== "function") return null;
|
|
1015
|
+
let best = -1;
|
|
1016
|
+
for (const n of nodes.values()) {
|
|
1017
|
+
if (n.kind === "compaction" || n.kind === "manual-compaction") continue;
|
|
1018
|
+
const seq = Number(n.anchorSeq ?? n.data?.seq);
|
|
1019
|
+
if (Number.isFinite(seq) && seq > best) best = seq;
|
|
1020
|
+
}
|
|
1021
|
+
if (best < 0) {
|
|
1022
|
+
for (const n of nodes.values()) {
|
|
1023
|
+
const seq = Number(n.anchorSeq ?? n.data?.seq);
|
|
1024
|
+
if (Number.isFinite(seq) && seq > best) best = seq;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return best >= 0 ? best : null;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1012
1030
|
function parentIdOf(row) {
|
|
1013
1031
|
return String(row?.parentSessionId || row?.parentId || "");
|
|
1014
1032
|
}
|
|
@@ -1821,6 +1839,11 @@ window.__ModuleLoader__.load({
|
|
|
1821
1839
|
}
|
|
1822
1840
|
}, [qs, sessionId]);
|
|
1823
1841
|
|
|
1842
|
+
const applyMap = (resp) => {
|
|
1843
|
+
if (Array.isArray(resp?.nodes)) setNodes(resp.nodes);
|
|
1844
|
+
if (Array.isArray(resp?.edges)) setEdges(resp.edges);
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1824
1847
|
const syncCheckpoints = async () => {
|
|
1825
1848
|
if (!sessionId || busy) return;
|
|
1826
1849
|
setBusy(true);
|
|
@@ -1833,12 +1856,11 @@ window.__ModuleLoader__.load({
|
|
|
1833
1856
|
headers: { "content-type": "application/json" },
|
|
1834
1857
|
body: JSON.stringify({ sessionId, compactations, forks }),
|
|
1835
1858
|
});
|
|
1836
|
-
|
|
1837
|
-
setEdges(resp.edges || []);
|
|
1859
|
+
applyMap(resp);
|
|
1838
1860
|
const checks = Number(resp.checkpoints) || 0;
|
|
1839
1861
|
const forkN = Number(resp.forks) || 0;
|
|
1840
1862
|
if (!checks && !forkN) {
|
|
1841
|
-
setBackfillNote("
|
|
1863
|
+
setBackfillNote("没有发现压缩标记或分叉。没压过的会话本来就只有「后续」。要分段请点「生成检查点」。");
|
|
1842
1864
|
} else {
|
|
1843
1865
|
setBackfillNote("已同步 " + checks + " 个检查点、" + forkN + " 条分叉。");
|
|
1844
1866
|
}
|
|
@@ -1849,6 +1871,38 @@ window.__ModuleLoader__.load({
|
|
|
1849
1871
|
}
|
|
1850
1872
|
};
|
|
1851
1873
|
|
|
1874
|
+
const cutCheckpoint = async () => {
|
|
1875
|
+
if (!sessionId || busy) return;
|
|
1876
|
+
const atSeq = lastChatSeq(chat);
|
|
1877
|
+
if (atSeq == null) {
|
|
1878
|
+
setError("当前对话还没有可切的轮次。先在对话里发一条,再生成检查点。");
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
setBusy(true);
|
|
1882
|
+
setError("");
|
|
1883
|
+
try {
|
|
1884
|
+
const resp = await api("/api/dsh-trivium/map/cut" + qs, {
|
|
1885
|
+
method: "POST",
|
|
1886
|
+
headers: { "content-type": "application/json" },
|
|
1887
|
+
body: JSON.stringify({
|
|
1888
|
+
sessionId,
|
|
1889
|
+
atSeq,
|
|
1890
|
+
summary: lastUserPreview(chat),
|
|
1891
|
+
}),
|
|
1892
|
+
});
|
|
1893
|
+
applyMap(resp);
|
|
1894
|
+
if (resp.created) {
|
|
1895
|
+
setBackfillNote("已把当前「后续」收成左边一格(seq " + atSeq + "),右边仍是后续。这不压缩 DSH 上下文。");
|
|
1896
|
+
} else {
|
|
1897
|
+
setBackfillNote("这个位置已经切过了(seq " + atSeq + ")。再聊几轮后再生成。");
|
|
1898
|
+
}
|
|
1899
|
+
} catch (err) {
|
|
1900
|
+
setError(String(err.message || err));
|
|
1901
|
+
} finally {
|
|
1902
|
+
setBusy(false);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
|
|
1852
1906
|
react.useEffect(() => {
|
|
1853
1907
|
load();
|
|
1854
1908
|
}, [load, lineageKey]);
|
|
@@ -2159,7 +2213,15 @@ window.__ModuleLoader__.load({
|
|
|
2159
2213
|
type: "button",
|
|
2160
2214
|
style: mapUi.actionBtn,
|
|
2161
2215
|
disabled: busy,
|
|
2162
|
-
title: "
|
|
2216
|
+
title: "把当前「后续」收成左边一格,右边继续当后续。只切情节图,不触发 DSH 压缩。要压上下文请在对话里输入 /compact。",
|
|
2217
|
+
onClick: cutCheckpoint,
|
|
2218
|
+
children: busy ? "生成中…" : "生成检查点",
|
|
2219
|
+
}),
|
|
2220
|
+
jsx("button", {
|
|
2221
|
+
type: "button",
|
|
2222
|
+
style: mapUi.actionBtn,
|
|
2223
|
+
disabled: busy,
|
|
2224
|
+
title: "读取当前对话里 DSH 已有的压缩标记和侧边栏分叉,补进图。没压过就补不出历史方框。",
|
|
2163
2225
|
onClick: syncCheckpoints,
|
|
2164
2226
|
children: busy ? "更新中…" : "更新检查点",
|
|
2165
2227
|
}),
|
package/lib/episode.js
CHANGED
|
@@ -148,6 +148,41 @@ export function ensureTail(db, sessionId, { atSeq, summary } = {}) {
|
|
|
148
148
|
return id;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Cut the live tail into a historical box at `atSeq`, then keep one 「后续」.
|
|
153
|
+
* Same atSeq reuses the checkpoint (no extra box). Does not compact DSH context.
|
|
154
|
+
*/
|
|
155
|
+
export function cutSessionMap(db, { sessionId, atSeq, summary } = {}) {
|
|
156
|
+
const sid = String(sessionId || "");
|
|
157
|
+
const seq = Number(atSeq);
|
|
158
|
+
if (!sid) return { ok: false, message: "need sessionId" };
|
|
159
|
+
if (!Number.isFinite(seq) || seq < 0) return { ok: false, message: "need atSeq" };
|
|
160
|
+
const existing = findByUri(db, episodeUri(sid, seq));
|
|
161
|
+
const existed = existing != null && isCheckpointPayload(db.get(existing)?.payload);
|
|
162
|
+
if (existed) {
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
created: false,
|
|
166
|
+
checkpointId: existing,
|
|
167
|
+
atSeq: seq,
|
|
168
|
+
...sessionMapSnapshot(db, sid),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const id = recordCheckpoint(db, {
|
|
172
|
+
sessionId: sid,
|
|
173
|
+
atSeq: seq,
|
|
174
|
+
compactionId: `manual-${seq}`,
|
|
175
|
+
summary: String(summary || "").trim() || "检查点",
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
ok: true,
|
|
179
|
+
created: true,
|
|
180
|
+
checkpointId: id,
|
|
181
|
+
atSeq: seq,
|
|
182
|
+
...sessionMapSnapshot(db, sid),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
151
186
|
export function recordCheckpoint(db, { sessionId, atSeq, compactionId, summary } = {}) {
|
|
152
187
|
const sid = String(sessionId || "");
|
|
153
188
|
const seq = Number(atSeq);
|
package/lib/server.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import { embeddingPublicStatus } from "./embed.js";
|
|
18
18
|
import { recallModeOf, writeUiSettings } from "./settings.js";
|
|
19
19
|
import { discoverWorkbuddyFiles, exportMarkdown, importWorkbuddy } from "./markdown.js";
|
|
20
|
-
import { backfillSessionMap, renameEpisode, sessionMapSnapshot } from "./episode.js";
|
|
20
|
+
import { backfillSessionMap, cutSessionMap, renameEpisode, sessionMapSnapshot } from "./episode.js";
|
|
21
21
|
import { applyPinsUpdate, addChipsFromText, batchChipAction, listChips, readSessionPins } from "./pins.js";
|
|
22
22
|
|
|
23
23
|
export { readUiSettings, writeUiSettings } from "./settings.js";
|
|
@@ -289,6 +289,23 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
if (path === "/api/dsh-trivium/map/cut" && method === "POST") {
|
|
293
|
+
const { cwd, db } = await dbForRequest(url);
|
|
294
|
+
if (!db) {
|
|
295
|
+
sendJson(res, 400, { ok: false, message: "no workspace" });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
299
|
+
const sessionId = body.sessionId || url.searchParams.get("sessionId") || "";
|
|
300
|
+
const result = cutSessionMap(db, {
|
|
301
|
+
sessionId,
|
|
302
|
+
atSeq: body.atSeq,
|
|
303
|
+
summary: body.summary,
|
|
304
|
+
});
|
|
305
|
+
sendJson(res, result.ok ? 200 : 400, { cwd, sessionId, ...result });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
292
309
|
if (path === "/api/dsh-trivium/chips" && method === "GET") {
|
|
293
310
|
const { cwd, db } = await dbForRequest(url);
|
|
294
311
|
const q = url.searchParams.get("q") || "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-trivium",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "In-process graph memory and session map for DeepSeek Harness, backed by TriviumDB.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"node": "^22.19.0 || >=24"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@deepseek-ai/dsh-llm": "0.1.0-rc.8",
|
|
51
|
-
"@deepseek-ai/dsh-tools": "0.1.0-rc.8"
|
|
50
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.8 <0.1.1 || >=0.1.1-rc.2 <0.2.0",
|
|
51
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.8 <0.1.1 || >=0.1.1-rc.2 <0.2.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"triviumdb": "^0.7.5"
|