dsh-trivium 0.4.9 → 0.4.13
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 +76 -44
- package/README.zh-CN.md +77 -47
- package/lib/client.js +374 -45
- package/lib/index.js +9 -0
- package/lib/purge.js +122 -0
- package/lib/server.js +29 -1
- package/lib/settings.js +106 -9
- package/lib/sidecar.js +422 -0
- package/lib/store.js +69 -8
- package/package.json +4 -1
package/lib/purge.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
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 (
|
|
11
|
+
n === "trivium-pending.json" ||
|
|
12
|
+
n === "trivium.jsonl" ||
|
|
13
|
+
n.startsWith("trivium.jsonl") ||
|
|
14
|
+
n === "trivium.tdb" ||
|
|
15
|
+
n.startsWith("trivium.tdb")
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `dsh plugin remove` / `pnpm remove` should wipe.
|
|
21
|
+
* `pnpm add` / update / ci reinstall the same package and must keep memory.
|
|
22
|
+
*/
|
|
23
|
+
export function shouldWipeOnUninstall(env = process.env, argv = process.argv) {
|
|
24
|
+
if (argv.includes("--force-purge")) return true;
|
|
25
|
+
const event = String(env.npm_lifecycle_event || "").toLowerCase();
|
|
26
|
+
if (event && event !== "preuninstall" && event !== "uninstall") return false;
|
|
27
|
+
|
|
28
|
+
const cmd = String(env.npm_command || "").toLowerCase();
|
|
29
|
+
if (cmd === "remove" || cmd === "uninstall") return true;
|
|
30
|
+
if (cmd === "install" || cmd === "add" || cmd === "update" || cmd === "upgrade" || cmd === "ci" || cmd === "dedupe") {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cooked = String(env.npm_config_argv || "").toLowerCase();
|
|
35
|
+
if (/\b(remove|uninstall)\b/.test(cooked) && !/\b(install|add|update|upgrade)\b/.test(cooked)) return true;
|
|
36
|
+
if (/\b(install|add|update|upgrade|ci)\b/.test(cooked)) return false;
|
|
37
|
+
|
|
38
|
+
// Unknown command: keep memory. Upgrades must never delete the store.
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function collectPurgeCwds(extraCwds = []) {
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const out = [];
|
|
45
|
+
const add = (cwd) => {
|
|
46
|
+
const key = String(cwd || "").trim();
|
|
47
|
+
if (!key) return;
|
|
48
|
+
const resolved = resolve(key);
|
|
49
|
+
if (seen.has(resolved)) return;
|
|
50
|
+
seen.add(resolved);
|
|
51
|
+
out.push(resolved);
|
|
52
|
+
};
|
|
53
|
+
for (const cwd of listedWorkspaces()) add(cwd);
|
|
54
|
+
for (const row of listOpenWorkspaces()) add(row.cwd);
|
|
55
|
+
for (const cwd of extraCwds) add(cwd);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function unlinkQuiet(path, files, errors) {
|
|
60
|
+
try {
|
|
61
|
+
unlinkSync(path);
|
|
62
|
+
files.push(path);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err && err.code !== "ENOENT") errors.push({ path, message: String(err.message || err) });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function purgeWorkspaceArtifacts(cwd, files, errors) {
|
|
69
|
+
const dir = join(resolve(cwd), ".dsh");
|
|
70
|
+
if (!existsSync(dir)) return;
|
|
71
|
+
let names = [];
|
|
72
|
+
try {
|
|
73
|
+
names = readdirSync(dir);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
errors.push({ path: dir, message: String(err.message || err) });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
if (!isTriviumArtifactName(name)) continue;
|
|
80
|
+
unlinkQuiet(join(dir, name), files, errors);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Delete known workspace traces and ~/.dsh/trivium.json. Leaves other .dsh files alone. */
|
|
85
|
+
export function purgeAllTraces({ extraCwds = [], close = closeAll } = {}) {
|
|
86
|
+
const settingsFile = settingsFilePath();
|
|
87
|
+
const workspaces = collectPurgeCwds(extraCwds);
|
|
88
|
+
try {
|
|
89
|
+
close();
|
|
90
|
+
} catch {
|
|
91
|
+
// ignore
|
|
92
|
+
}
|
|
93
|
+
const files = [];
|
|
94
|
+
const errors = [];
|
|
95
|
+
for (const cwd of workspaces) purgeWorkspaceArtifacts(cwd, files, errors);
|
|
96
|
+
unlinkQuiet(settingsFile, files, errors);
|
|
97
|
+
return { ok: errors.length === 0, workspaces, files, errors, settingsFile };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function shouldRunCli() {
|
|
101
|
+
return basename(String(process.argv[1] || "")) === "purge.js";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function runUninstallPurge(env = process.env, argv = process.argv, log = console) {
|
|
105
|
+
if (!shouldWipeOnUninstall(env, argv)) {
|
|
106
|
+
log.info?.("[dsh-trivium] skip data wipe (install/update, not remove)");
|
|
107
|
+
return { skipped: true };
|
|
108
|
+
}
|
|
109
|
+
const result = purgeAllTraces();
|
|
110
|
+
const n = result.files.length;
|
|
111
|
+
log.info?.(`[dsh-trivium] removed ${n} plugin file${n === 1 ? "" : "s"} from ${result.workspaces.length} workspace(s)`);
|
|
112
|
+
if (result.errors.length) {
|
|
113
|
+
log.warn?.("[dsh-trivium] some files could not be deleted (stop dsh web and delete leftovers):");
|
|
114
|
+
for (const err of result.errors) log.warn?.(` ${err.path}: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (shouldRunCli()) {
|
|
120
|
+
const result = runUninstallPurge();
|
|
121
|
+
if (result?.errors?.length) process.exitCode = 1;
|
|
122
|
+
}
|
package/lib/server.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import {
|
|
3
|
+
generateSidecarFromDb,
|
|
3
4
|
archiveNode,
|
|
4
5
|
backfillEmbeddings,
|
|
5
6
|
dbPathFor,
|
|
7
|
+
deleteGitSidecars,
|
|
6
8
|
deleteNode,
|
|
7
9
|
exportGraph,
|
|
8
10
|
getActiveCwd,
|
|
@@ -15,7 +17,7 @@ import {
|
|
|
15
17
|
updateNode,
|
|
16
18
|
} from "./store.js";
|
|
17
19
|
import { embeddingPublicStatus } from "./embed.js";
|
|
18
|
-
import { chipsEnabledOf, recallModeOf, sessionLayerEnabledOf, writeUiSettings } from "./settings.js";
|
|
20
|
+
import { chipsEnabledOf, configPanelOpenOf, gitSidecarEnabledOf, pluginLocaleOf, recallModeOf, sessionLayerEnabledOf, writeUiSettings } from "./settings.js";
|
|
19
21
|
import { discoverWorkbuddyFiles, discoverExternalFiles, exportMarkdown, importWorkbuddy, importExternal } from "./markdown.js";
|
|
20
22
|
import { checkNpmUpdate, installedVersion } from "./update.js";
|
|
21
23
|
import { writeGateMessage } from "./hygiene.js";
|
|
@@ -116,6 +118,9 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
116
118
|
extractEnabled: opts.extractEnabled !== false,
|
|
117
119
|
chipsEnabled: chipsEnabledOf(opts),
|
|
118
120
|
sessionLayerEnabled: opts.sessionLayerEnabled === true,
|
|
121
|
+
gitSidecarEnabled: gitSidecarEnabledOf(opts),
|
|
122
|
+
pluginLocale: pluginLocaleOf(opts),
|
|
123
|
+
configPanelOpen: configPanelOpenOf(opts),
|
|
119
124
|
writeApproval: !!opts.writeApproval,
|
|
120
125
|
mapTokenBudget: opts.mapTokenBudget || 400,
|
|
121
126
|
workspaces: listOpenWorkspaces(),
|
|
@@ -138,6 +143,29 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
138
143
|
return;
|
|
139
144
|
}
|
|
140
145
|
|
|
146
|
+
if (path === "/api/dsh-trivium/sidecar/generate" && method === "POST") {
|
|
147
|
+
if (!gitSidecarEnabledOf(liveOptions())) {
|
|
148
|
+
sendJson(res, 400, { ok: false, message: "git sidecar off" });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const { cwd, db } = await dbForRequest(url);
|
|
152
|
+
if (!db) {
|
|
153
|
+
sendJson(res, 400, { ok: false, message: "no workspace" });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
sendJson(res, 200, generateSidecarFromDb(cwd, db));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (path === "/api/dsh-trivium/sidecar/delete" && method === "POST") {
|
|
161
|
+
if (gitSidecarEnabledOf(liveOptions())) {
|
|
162
|
+
sendJson(res, 400, { ok: false, message: "turn sidecar off first" });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
sendJson(res, 200, deleteGitSidecars());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
141
169
|
if (path === "/api/dsh-trivium/export" && method === "GET") {
|
|
142
170
|
const { cwd, db } = await dbForRequest(url);
|
|
143
171
|
if (!db) {
|
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;
|
|
@@ -33,6 +82,44 @@ export function sessionLayerEnabledOf(opts = {}) {
|
|
|
33
82
|
return chipsEnabledOf(opts) && opts.sessionLayerEnabled === true;
|
|
34
83
|
}
|
|
35
84
|
|
|
85
|
+
/** Git JSONL sidecar write. Missing key = off. Import still runs when this is off. */
|
|
86
|
+
export function gitSidecarEnabledOf(opts = {}) {
|
|
87
|
+
return opts.gitSidecarEnabled === true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Plugin UI language. Missing key = follow the DSH host. */
|
|
91
|
+
export function pluginLocaleOf(opts = {}) {
|
|
92
|
+
const v = String(opts.pluginLocale || "").toLowerCase();
|
|
93
|
+
if (v === "zh" || v === "en" || v === "host") return v;
|
|
94
|
+
return "host";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Settings fold. Missing key = collapsed. */
|
|
98
|
+
export function configPanelOpenOf(opts = {}) {
|
|
99
|
+
return opts.configPanelOpen === true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function sidecarHashOf(cwd) {
|
|
103
|
+
const raw = readUiSettings().sidecarHashByCwd;
|
|
104
|
+
if (!raw || typeof raw !== "object") return "";
|
|
105
|
+
const key = resolve(String(cwd || ""));
|
|
106
|
+
return String(raw[key] || "");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function writeSidecarHash(cwd, hash) {
|
|
110
|
+
const key = resolve(String(cwd || ""));
|
|
111
|
+
if (!process.env.DSH_TRIVIUM_SETTINGS && isInsideDir(tmpdir(), key)) {
|
|
112
|
+
return readUiSettings();
|
|
113
|
+
}
|
|
114
|
+
const prev = readUiSettings();
|
|
115
|
+
const sidecarHashByCwd = {
|
|
116
|
+
...(prev.sidecarHashByCwd && typeof prev.sidecarHashByCwd === "object" ? prev.sidecarHashByCwd : {}),
|
|
117
|
+
};
|
|
118
|
+
if (!hash) delete sidecarHashByCwd[key];
|
|
119
|
+
else sidecarHashByCwd[key] = String(hash);
|
|
120
|
+
return persistSettings({ ...prev, sidecarHashByCwd });
|
|
121
|
+
}
|
|
122
|
+
|
|
36
123
|
export function applyRecallMode(target, mode) {
|
|
37
124
|
const next = mode === "auto" || mode === "anchor" ? mode : "off";
|
|
38
125
|
target.recallMode = next;
|
|
@@ -42,7 +129,6 @@ export function applyRecallMode(target, mode) {
|
|
|
42
129
|
}
|
|
43
130
|
|
|
44
131
|
export function writeUiSettings(patch) {
|
|
45
|
-
mkdirSync(join(homedir(), ".dsh"), { recursive: true });
|
|
46
132
|
const prev = readUiSettings();
|
|
47
133
|
const next = { ...prev };
|
|
48
134
|
let mode = recallModeOf(prev);
|
|
@@ -62,6 +148,12 @@ export function writeUiSettings(patch) {
|
|
|
62
148
|
if (typeof patch.extractEnabled === "boolean") next.extractEnabled = patch.extractEnabled;
|
|
63
149
|
if (typeof patch.chipsEnabled === "boolean") next.chipsEnabled = patch.chipsEnabled;
|
|
64
150
|
if (typeof patch.sessionLayerEnabled === "boolean") next.sessionLayerEnabled = patch.sessionLayerEnabled;
|
|
151
|
+
if (typeof patch.gitSidecarEnabled === "boolean") next.gitSidecarEnabled = patch.gitSidecarEnabled;
|
|
152
|
+
if (typeof patch.configPanelOpen === "boolean") next.configPanelOpen = patch.configPanelOpen;
|
|
153
|
+
if (typeof patch.pluginLocale === "string") {
|
|
154
|
+
const loc = String(patch.pluginLocale || "").toLowerCase();
|
|
155
|
+
next.pluginLocale = loc === "zh" || loc === "en" ? loc : "host";
|
|
156
|
+
}
|
|
65
157
|
if (typeof patch.embeddingEnabled === "boolean") next.embeddingEnabled = patch.embeddingEnabled;
|
|
66
158
|
if (typeof patch.embeddingUrl === "string") {
|
|
67
159
|
const url = patch.embeddingUrl.trim();
|
|
@@ -76,8 +168,14 @@ export function writeUiSettings(patch) {
|
|
|
76
168
|
} else if (prev.pinsBySession && typeof prev.pinsBySession === "object") {
|
|
77
169
|
next.pinsBySession = prev.pinsBySession;
|
|
78
170
|
}
|
|
79
|
-
|
|
80
|
-
|
|
171
|
+
if (Array.isArray(patch.workspaces)) next.workspaces = patch.workspaces;
|
|
172
|
+
else if (Array.isArray(prev.workspaces)) next.workspaces = prev.workspaces;
|
|
173
|
+
if (patch.sidecarHashByCwd && typeof patch.sidecarHashByCwd === "object") {
|
|
174
|
+
next.sidecarHashByCwd = patch.sidecarHashByCwd;
|
|
175
|
+
} else if (prev.sidecarHashByCwd && typeof prev.sidecarHashByCwd === "object") {
|
|
176
|
+
next.sidecarHashByCwd = prev.sidecarHashByCwd;
|
|
177
|
+
}
|
|
178
|
+
return persistSettings(next);
|
|
81
179
|
}
|
|
82
180
|
|
|
83
181
|
export function pinsOf(sessionId) {
|
|
@@ -96,11 +194,10 @@ export function writePins(sessionId, ids) {
|
|
|
96
194
|
if (!key) return nextIds;
|
|
97
195
|
if (!nextIds.length) delete pinsBySession[key];
|
|
98
196
|
else pinsBySession[key] = nextIds;
|
|
99
|
-
|
|
100
|
-
writeFileSync(SETTINGS_FILE, JSON.stringify({ ...prev, pinsBySession }, null, 2), "utf8");
|
|
197
|
+
persistSettings({ ...prev, pinsBySession });
|
|
101
198
|
return nextIds;
|
|
102
199
|
}
|
|
103
200
|
|
|
104
201
|
export function settingsFileExists() {
|
|
105
|
-
return existsSync(
|
|
202
|
+
return existsSync(settingsFilePath());
|
|
106
203
|
}
|