dsh-trivium 0.4.16 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ The kernel stays close to DSH: no extra process, no extra nav by default. Pin me
16
16
 
17
17
  ## Installation
18
18
 
19
- > Prerequisite: install [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) and start `dsh web` at least once. Tested host: `@deepseek-ai/dsh@0.1.1-rc.2` (`dsh-llm` / `dsh-tools` peers also accept `0.1.0-rc.8`).
19
+ > Prerequisite: install [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) and start `dsh web` at least once. Tested host: `@deepseek-ai/dsh@0.1.5-rc.1` (`dsh-llm` / `dsh-tools` peers also accept `0.1.1-rc.2` and `0.1.0-rc.8`).
20
20
 
21
21
  ```sh
22
22
  dsh plugin --profile web add dsh-trivium
@@ -46,8 +46,11 @@ Local source checkout:
46
46
  ```sh
47
47
  npm install
48
48
  node scripts/link-dsh.mjs
49
+ npm test # optional: runs every offline smoke script
49
50
  ```
50
51
 
52
+ `npm test` needs no DSH host and never touches your real `~/.dsh/trivium.json` (each script gets its own temp settings file). Pass a substring to narrow it: `npm test -- p8`.
53
+
51
54
  Then restart `dsh web`.
52
55
 
53
56
  ## Updating
@@ -242,6 +245,14 @@ Off by default. After you turn **Chips (memory whitelist)** on in Settings, the
242
245
 
243
246
  ## Changelog
244
247
 
248
+ **0.4.18** — `~/.dsh/trivium.json` is now written atomically (a truncated write used to reset every switch and chip pin silently) and is owner-only on POSIX. `npm test` runs every offline smoke script with isolated settings, and `smoke-p0` pins the hybrid text-index-after-reopen invariant. Four tools / short map / find paths unchanged.
249
+
250
+ **0.4.17** — Storage bump to `triviumdb@0.8.8` (engine behavior unchanged from 0.4.16). Host adaptation for `@deepseek-ai/dsh@0.1.5-rc.1`: `session.header.seedLength` was removed, so the session-layer fork cut now reads `session.inheritedEventCount`. Four tools / short map / find paths unchanged.
251
+
252
+ **0.4.16** — Storage bump to `triviumdb@0.8.4`: the engine constructor now takes an options object (`{ dim }`) instead of numeric positional args, and TQL rows return string ids that the plugin coerces back to numbers. Four tools / short map / find paths unchanged.
253
+
254
+ **0.4.15** — Storage bump to `triviumdb@0.8.2`. The settings API rejects non-loopback requests and unlisted `cwd` values; the extract buffer is cleared after a run and `savePending` no longer blocks the turn.
255
+
245
256
  **0.4.14** — Storage bump to `triviumdb@0.8.1` (harder WAL/locks, optional read-only share, ARM64 fixes). Four tools / short map / find paths unchanged.
246
257
 
247
258
  **0.4.13** — Title-bar tab is **Chips** (short); the Settings switch stays **Chips (memory whitelist)**. Toggling the session layer refreshes the Chips tab immediately. Settings / Chips screenshots updated.
@@ -294,8 +305,8 @@ Local source checkout: `npm install`, then `node scripts/link-dsh.mjs`, then res
294
305
  ## Release info
295
306
 
296
307
  - GitHub: https://github.com/QWQcool/dsh-trivium
297
- - npm: [`dsh-trivium@0.4.14`](https://www.npmjs.com/package/dsh-trivium)
298
- - Tested host: `@deepseek-ai/dsh@0.1.1-rc.2` (also `0.1.0-rc.8`)
308
+ - npm: [`dsh-trivium@0.4.18`](https://www.npmjs.com/package/dsh-trivium)
309
+ - Tested host: `@deepseek-ai/dsh@0.1.5-rc.1` (also `0.1.1-rc.2` / `0.1.0-rc.8`)
299
310
  - License: MIT (depends on [TriviumDB](https://github.com/YoKONCy/TriviumDB), Apache-2.0)
300
311
 
301
312
  ---
package/README.zh-CN.md CHANGED
@@ -14,7 +14,7 @@ DeepSeek Harness 的进程内图记忆插件:按节点和边记,默认少注
14
14
 
15
15
  ## 安装
16
16
 
17
- > 前提:已安装 [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`)。
17
+ > 前提:已安装 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 并至少启动过一次 `dsh web`。当前测试宿主:`@deepseek-ai/dsh@0.1.5-rc.1`(`dsh-llm` / `dsh-tools` peer 兼容 `0.1.1-rc.2` 与 `0.1.0-rc.8`)。
18
18
 
19
19
  ```sh
20
20
  dsh plugin --profile web add dsh-trivium
@@ -44,8 +44,11 @@ clone / `git pull` 后若 jsonl 变了会自动导回。设置里 **Git 旁路**
44
44
  ```sh
45
45
  npm install
46
46
  node scripts/link-dsh.mjs
47
+ npm test # 可选:跑全部离线 smoke
47
48
  ```
48
49
 
50
+ `npm test` 不需要 DSH 宿主,也不会碰你真实的 `~/.dsh/trivium.json`(每个脚本用独立的临时设置文件)。想只跑一部分就传子串:`npm test -- p8`。
51
+
49
52
  然后重启 `dsh web`。
50
53
 
51
54
  ## 更新
@@ -238,6 +241,14 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
238
241
 
239
242
  ## 更新说明
240
243
 
244
+ **0.4.18** — `~/.dsh/trivium.json` 改为原子写(此前写到一半被杀会让所有开关和芯片钉选静默回默认),POSIX 下权限收紧为仅属主可读。新增 `npm test` 聚合入口(每个脚本用独立临时设置文件),`smoke-p0` 新增「重开后 hybrid 文本索引仍可用」的断言。四工具 / 短地图 / find 路径行为不变。
245
+
246
+ **0.4.17** — 存储升到 `triviumdb@0.8.8`(引擎行为相对 0.4.16 不变)。适配宿主 `@deepseek-ai/dsh@0.1.5-rc.1`:`session.header.seedLength` 被移除,会话层 fork 切点改读 `session.inheritedEventCount`。四工具 / 短地图 / find 路径不变。
247
+
248
+ **0.4.16** — 存储升到 `triviumdb@0.8.4`:引擎构造函数改为 options 对象(`{ dim }`),不再收数字位置参数;TQL 行 id 变字符串,插件内部归一为数字。四工具 / 短地图 / find 路径不变。
249
+
250
+ **0.4.15** — 存储升到 `triviumdb@0.8.2`。设置 API 拒绝非回环请求与未登记 `cwd`;抽取后清空缓冲,`savePending` 不再阻塞当轮。
251
+
241
252
  **0.4.14** — 存储升到 `triviumdb@0.8.1`(WAL/锁更硬、可选只读共享、ARM64 修复)。四工具 / 短地图 / find 路径不变。
242
253
 
243
254
  **0.4.13** — 标题栏只显示 **芯片**;设置里的开关仍叫 **芯片(记忆白名单)**。会话层开关立刻刷新芯片页。设置 / 芯片截图更新。
@@ -290,8 +301,8 @@ Trivium 是记忆内核,不是日记、日历或聊天伴侣。它按**节点
290
301
  ## 发布信息
291
302
 
292
303
  - GitHub: https://github.com/QWQcool/dsh-trivium
293
- - npm: [`dsh-trivium@0.4.14`](https://www.npmjs.com/package/dsh-trivium)
294
- - 测试宿主:`@deepseek-ai/dsh@0.1.1-rc.2`(兼 `0.1.0-rc.8`)
304
+ - npm: [`dsh-trivium@0.4.18`](https://www.npmjs.com/package/dsh-trivium)
305
+ - 测试宿主:`@deepseek-ai/dsh@0.1.5-rc.1`(兼 `0.1.1-rc.2` / `0.1.0-rc.8`)
295
306
  - License: MIT(依赖 [TriviumDB](https://github.com/YoKONCy/TriviumDB) 为 Apache-2.0)
296
307
 
297
308
  ---
package/lib/atomic.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Atomic text write: write a sibling temp file, then rename over the target.
3
+ *
4
+ * `writeFileSync(file, ...)` truncates in place, so a crash (or a kill) between
5
+ * truncate and write leaves a half-written file. For the settings file that
6
+ * means `readUiSettings()` silently returns `{}` and every switch / chip pin
7
+ * resets. A rename is atomic on the same volume, so readers see either the old
8
+ * file or the new one, never a truncated one.
9
+ */
10
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
11
+ import { dirname } from "node:path";
12
+
13
+ /**
14
+ * @param file absolute target path
15
+ * @param text UTF-8 payload
16
+ * @param mode optional POSIX mode (ignored on Windows). Applied to the temp
17
+ * file, which the rename then promotes to the target.
18
+ * @returns the target path
19
+ */
20
+ export function atomicWriteFile(file, text, { mode } = {}) {
21
+ mkdirSync(dirname(file), { recursive: true });
22
+ const tmp = `${file}.tmp`;
23
+ // Drop a stale temp first so `mode` is actually applied on creation.
24
+ try {
25
+ unlinkSync(tmp);
26
+ } catch {
27
+ // nothing stale to remove
28
+ }
29
+ writeFileSync(tmp, text, mode === undefined ? "utf8" : { encoding: "utf8", mode });
30
+ try {
31
+ renameSync(tmp, file);
32
+ } catch {
33
+ // Windows cannot always rename over an existing file; clear and retry.
34
+ try {
35
+ unlinkSync(file);
36
+ } catch {
37
+ // destination may not exist yet
38
+ }
39
+ renameSync(tmp, file);
40
+ }
41
+ return file;
42
+ }
package/lib/client.js CHANGED
@@ -3283,7 +3283,10 @@ window.__ModuleLoader__.load({
3283
3283
  open: (id) => sessions.open(id),
3284
3284
  seedLengthOf: (id) => {
3285
3285
  try {
3286
- const seed = sessions.binding?.(id)?.session?.header?.seedLength;
3286
+ const face = sessions.binding?.(id)?.session;
3287
+ // DSH 0.1.5 dropped header.seedLength; the live session exposes
3288
+ // the fork-inherited prefix length as inheritedEventCount.
3289
+ const seed = face?.inheritedEventCount ?? face?.header?.seedLength;
3287
3290
  const n = Number(seed);
3288
3291
  return Number.isFinite(n) && n > 0 ? n : null;
3289
3292
  } catch {
package/lib/index.js CHANGED
@@ -68,6 +68,19 @@ function sessionKey(session) {
68
68
  return String(session?.id || session?.header?.id || "");
69
69
  }
70
70
 
71
+ /**
72
+ * Fork-inherited prefix length. DSH 0.1.5 moved this off the header
73
+ * (`header.seedLength` is now rejected as an invalid field) onto the live
74
+ * session as `inheritedEventCount`; keep the header read as a fallback for
75
+ * older hosts.
76
+ */
77
+ function sessionSeedLength(session) {
78
+ const live = session?.inheritedEventCount;
79
+ if (Number.isFinite(Number(live))) return Number(live);
80
+ const legacy = session?.header?.seedLength;
81
+ return Number.isFinite(Number(legacy)) ? Number(legacy) : 0;
82
+ }
83
+
71
84
  function bufOf(session) {
72
85
  const id = sessionKey(session);
73
86
  if (!buffers.has(id)) {
@@ -296,15 +309,15 @@ export function apply(ctx, config = {}) {
296
309
  const session = agent.session;
297
310
  const sessionId = sessionKey(session);
298
311
  if (sessionId && sessionLayerEnabledOf(optionsOf())) {
299
- const seedLen = session?.header?.seedLength;
312
+ const seedLen = sessionSeedLength(session);
300
313
  ensureTail(db, sessionId, {
301
- atSeq: Number.isFinite(Number(seedLen)) && seedLen > 0 ? seedLen - 1 : 0,
314
+ atSeq: seedLen > 0 ? seedLen - 1 : 0,
302
315
  });
303
316
  if (session?.header?.parentSession) {
304
317
  syncForkLineage(db, {
305
318
  childSessionId: sessionId,
306
319
  parentSessionId: session.header.parentSession,
307
- atSeq: Number.isFinite(Number(seedLen)) && seedLen > 0 ? seedLen - 1 : undefined,
320
+ atSeq: seedLen > 0 ? seedLen - 1 : undefined,
308
321
  });
309
322
  }
310
323
  }
@@ -441,10 +454,12 @@ export function apply(ctx, config = {}) {
441
454
  extra.push(
442
455
  createUserMessage({
443
456
  content: [{ type: "text", text: pin.text }],
457
+ // No `form`: "pins" was never in DSH's ContextForm union, so the
458
+ // client already rendered it as undeclared (opaque) context.
459
+ // Omitting the field is the documented default for that.
444
460
  source: {
445
461
  kind: "plugin",
446
462
  plugin: "dsh-trivium",
447
- form: "pins",
448
463
  },
449
464
  }),
450
465
  );
package/lib/settings.js CHANGED
@@ -1,10 +1,14 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir, tmpdir } from "node:os";
3
- import { dirname, join, relative, resolve, sep } from "node:path";
3
+ import { join, relative, resolve, sep } from "node:path";
4
+ import { atomicWriteFile } from "./atomic.js";
4
5
  import { looksHttpUrl } from "./embed.js";
5
6
 
6
7
  export const SETTINGS_FILE = join(homedir(), ".dsh", "trivium.json");
7
8
 
9
+ /** This file can hold an embedding API key; keep it owner-only. No-op on Windows. */
10
+ const SETTINGS_FILE_MODE = 0o600;
11
+
8
12
  /** Tests may set DSH_TRIVIUM_SETTINGS to a temp file so we never touch the live home config. */
9
13
  export function settingsFilePath() {
10
14
  const override = String(process.env.DSH_TRIVIUM_SETTINGS || "").trim();
@@ -12,9 +16,9 @@ export function settingsFilePath() {
12
16
  }
13
17
 
14
18
  function persistSettings(next) {
15
- const file = settingsFilePath();
16
- mkdirSync(dirname(file), { recursive: true });
17
- writeFileSync(file, JSON.stringify(next, null, 2), "utf8");
19
+ // Atomic: a truncated settings file would make readUiSettings() return {}
20
+ // and silently reset every switch / chip pin.
21
+ atomicWriteFile(settingsFilePath(), JSON.stringify(next, null, 2), { mode: SETTINGS_FILE_MODE });
18
22
  return next;
19
23
  }
20
24
 
package/lib/sidecar.js CHANGED
@@ -1,8 +1,9 @@
1
1
  /** Git-friendly JSONL sidecar next to trivium.tdb. No embeddings, episodes, or chip pins. */
2
2
 
3
3
  import { createHash } from "node:crypto";
4
- import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
5
- import { dirname, join, resolve } from "node:path";
4
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import { atomicWriteFile } from "./atomic.js";
6
7
  import { BUSINESS_EDGES } from "./schema.js";
7
8
  import {
8
9
  gitSidecarEnabledOf,
@@ -196,22 +197,6 @@ function readSidecarFile(cwd) {
196
197
  }
197
198
  }
198
199
 
199
- function atomicWrite(file, text) {
200
- mkdirSync(dirname(file), { recursive: true });
201
- const tmp = `${file}.tmp`;
202
- writeFileSync(tmp, text, "utf8");
203
- try {
204
- renameSync(tmp, file);
205
- } catch {
206
- try {
207
- unlinkSync(file);
208
- } catch {
209
- // dest may not exist
210
- }
211
- renameSync(tmp, file);
212
- }
213
- }
214
-
215
200
  function rememberHash(cwd, hash) {
216
201
  const key = resolve(cwd);
217
202
  memoryHash.set(key, hash);
@@ -293,7 +278,7 @@ export function writeSidecarNow(cwd, db, { force = false } = {}) {
293
278
  if (!force && hash === knownHash(cwd) && existsSync(file)) {
294
279
  return { path: file, hash, wrote: false };
295
280
  }
296
- atomicWrite(file, text);
281
+ atomicWriteFile(file, text);
297
282
  rememberHash(cwd, hash);
298
283
  return { path: file, hash, wrote: true };
299
284
  }
package/lib/store.js CHANGED
@@ -114,6 +114,8 @@ export async function openWorkspaceDb(cwd) {
114
114
  if (dbs.has(key)) return dbs.get(key);
115
115
  mkdirSync(join(key, ".dsh"), { recursive: true });
116
116
  const Engine = loadEngine();
117
+ // triviumdb 0.8.3+ removed numeric positional constructor args
118
+ // (TDB_API_MIGRATION_REQUIRED); dim now rides an options object.
117
119
  const db = new Engine(dbPathFor(key), { dim: DIM });
118
120
  try {
119
121
  db.createIndex("type");
@@ -122,6 +124,11 @@ export async function openWorkspaceDb(cwd) {
122
124
  // indexes may already exist
123
125
  }
124
126
  ensureWorkspaceRoot(db, cwd);
127
+ // Do NOT remove. triviumdb opens with `loadTextIndex: false` by default, so
128
+ // the persisted BM25 sidecar is not loaded and `searchHybrid`'s text channel
129
+ // returns nothing after a reopen until the index is rebuilt here. `searchNodes`
130
+ // cannot paper over the loss: its keyword fallback still scores, so a
131
+ // regression is silent at the tool level. scripts/smoke-p0.mjs pins this.
125
132
  rebuildTextIndex(db);
126
133
  dbs.set(key, db);
127
134
  try {
@@ -191,6 +198,8 @@ function normalizeTql(rows) {
191
198
  return rows
192
199
  .map((row) => {
193
200
  const node = row?._ || row?.["*"] || row?.a || Object.values(row || {})[0];
201
+ // triviumdb 0.8.x serializes TQL row ids as strings; coerce so the rest
202
+ // of the plugin keeps comparing numbers.
194
203
  if (!node) return null;
195
204
  const id = Number(node.id);
196
205
  if (!Number.isFinite(id)) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-trivium",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "description": "In-process graph memory for DeepSeek Harness, backed by TriviumDB.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,7 @@
31
31
  }
32
32
  },
33
33
  "scripts": {
34
+ "test": "node scripts/run-all.mjs",
34
35
  "link-dsh": "node scripts/link-dsh.mjs",
35
36
  "smoke-p0": "node scripts/smoke-p0.mjs",
36
37
  "smoke-p1": "node scripts/smoke-p1.mjs",
@@ -41,6 +42,7 @@
41
42
  "smoke-p6": "node scripts/smoke-p6.mjs",
42
43
  "smoke-p7": "node scripts/smoke-p7.mjs",
43
44
  "smoke-p8": "node scripts/smoke-p8.mjs",
45
+ "smoke-p9": "node scripts/smoke-p9.mjs",
44
46
  "smoke-p10": "node scripts/smoke-p10.mjs",
45
47
  "smoke-p11": "node scripts/smoke-p11.mjs",
46
48
  "smoke-p12": "node scripts/smoke-p12.mjs",
@@ -55,7 +57,7 @@
55
57
  "@deepseek-ai/dsh-tools": ">=0.1.0-rc.8 <0.1.1 || >=0.1.1-rc.2 <0.2.0"
56
58
  },
57
59
  "dependencies": {
58
- "triviumdb": "^0.8.4"
60
+ "triviumdb": "^0.8.8"
59
61
  },
60
62
  "keywords": [
61
63
  "dsh-plugin",