dsh-trivium 0.3.0 → 0.4.0
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 +89 -78
- package/lib/client.js +1348 -1
- package/lib/episode.js +278 -0
- package/lib/index.js +127 -39
- package/lib/pins.js +155 -0
- package/lib/schema.js +5 -0
- package/lib/server.js +73 -0
- package/lib/settings.js +26 -0
- package/lib/store.js +36 -8
- package/package.json +7 -3
package/lib/server.js
CHANGED
|
@@ -17,6 +17,8 @@ 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 { renameEpisode, sessionMapSnapshot } from "./episode.js";
|
|
21
|
+
import { applyPinsUpdate, listChips, readSessionPins } from "./pins.js";
|
|
20
22
|
|
|
21
23
|
export { readUiSettings, writeUiSettings } from "./settings.js";
|
|
22
24
|
|
|
@@ -258,5 +260,76 @@ async function handle(req, res, { liveOptions, status }) {
|
|
|
258
260
|
return;
|
|
259
261
|
}
|
|
260
262
|
|
|
263
|
+
if (path === "/api/dsh-trivium/map" && method === "GET") {
|
|
264
|
+
const { cwd, db } = await dbForRequest(url);
|
|
265
|
+
const sessionId = url.searchParams.get("sessionId") || "";
|
|
266
|
+
if (!db) {
|
|
267
|
+
sendJson(res, 200, { ok: true, cwd: cwd || "", sessionId, nodes: [], edges: [] });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const snap = sessionMapSnapshot(db, sessionId);
|
|
271
|
+
sendJson(res, 200, { ok: true, cwd, ...snap });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (path === "/api/dsh-trivium/chips" && method === "GET") {
|
|
276
|
+
const { cwd, db } = await dbForRequest(url);
|
|
277
|
+
const q = url.searchParams.get("q") || "";
|
|
278
|
+
const sessionId = url.searchParams.get("sessionId") || "";
|
|
279
|
+
if (!db) {
|
|
280
|
+
sendJson(res, 200, { ok: true, cwd: cwd || "", sessionId, q, chips: [] });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
sendJson(res, 200, { ok: true, cwd, sessionId, q, chips: listChips(db, { q, sessionId }) });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (path === "/api/dsh-trivium/pins" && method === "GET") {
|
|
288
|
+
const { cwd, db } = await dbForRequest(url);
|
|
289
|
+
const sessionId = url.searchParams.get("sessionId") || "";
|
|
290
|
+
if (!db) {
|
|
291
|
+
sendJson(res, 200, { ok: true, cwd: cwd || "", sessionId, ids: [], clipped: false, tokens: 0 });
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const pin = readSessionPins(db, sessionId);
|
|
295
|
+
sendJson(res, 200, { ok: true, cwd, sessionId, ids: pin.ids, clipped: pin.clipped, tokens: pin.tokens });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (path === "/api/dsh-trivium/pins" && method === "POST") {
|
|
300
|
+
const { cwd, db } = await dbForRequest(url);
|
|
301
|
+
if (!db) {
|
|
302
|
+
sendJson(res, 400, { ok: false, message: "no workspace" });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
306
|
+
const sessionId = body.sessionId || url.searchParams.get("sessionId") || "";
|
|
307
|
+
const pin = applyPinsUpdate(db, { ...body, sessionId });
|
|
308
|
+
sendJson(res, 200, { ok: true, cwd, sessionId, ids: pin.ids, clipped: pin.clipped, tokens: pin.tokens });
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const episodeMatch = path.match(/^\/api\/dsh-trivium\/episodes\/(\d+)$/);
|
|
313
|
+
if (episodeMatch && method === "POST") {
|
|
314
|
+
const { cwd, db } = await dbForRequest(url);
|
|
315
|
+
if (!db) {
|
|
316
|
+
sendJson(res, 400, { ok: false, message: "no workspace" });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
320
|
+
const result = renameEpisode(db, episodeMatch[1], body.name);
|
|
321
|
+
sendJson(res, result.ok ? 200 : result.status || 400, {
|
|
322
|
+
ok: result.ok,
|
|
323
|
+
cwd,
|
|
324
|
+
id: Number(episodeMatch[1]),
|
|
325
|
+
name: result.name || "",
|
|
326
|
+
summary: result.summary || "",
|
|
327
|
+
message: result.message,
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
261
332
|
sendJson(res, 404, { ok: false, message: "not found" });
|
|
262
333
|
}
|
|
334
|
+
|
|
335
|
+
export { handle as handleMemoryApi };
|
package/lib/settings.js
CHANGED
|
@@ -59,10 +59,36 @@ export function writeUiSettings(patch) {
|
|
|
59
59
|
if (typeof patch.embeddingApiKey === "string" && patch.embeddingApiKey.trim()) {
|
|
60
60
|
next.embeddingApiKey = patch.embeddingApiKey.trim();
|
|
61
61
|
}
|
|
62
|
+
if (patch.pinsBySession && typeof patch.pinsBySession === "object") {
|
|
63
|
+
next.pinsBySession = patch.pinsBySession;
|
|
64
|
+
} else if (prev.pinsBySession && typeof prev.pinsBySession === "object") {
|
|
65
|
+
next.pinsBySession = prev.pinsBySession;
|
|
66
|
+
}
|
|
62
67
|
writeFileSync(SETTINGS_FILE, JSON.stringify(next, null, 2), "utf8");
|
|
63
68
|
return next;
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
export function pinsOf(sessionId) {
|
|
72
|
+
const raw = readUiSettings().pinsBySession;
|
|
73
|
+
if (!raw || typeof raw !== "object") return [];
|
|
74
|
+
const ids = raw[String(sessionId || "")];
|
|
75
|
+
if (!Array.isArray(ids)) return [];
|
|
76
|
+
return [...new Set(ids.map(Number).filter((id) => Number.isFinite(id)))];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function writePins(sessionId, ids) {
|
|
80
|
+
const key = String(sessionId || "");
|
|
81
|
+
const prev = readUiSettings();
|
|
82
|
+
const pinsBySession = { ...(prev.pinsBySession && typeof prev.pinsBySession === "object" ? prev.pinsBySession : {}) };
|
|
83
|
+
const nextIds = [...new Set((ids || []).map(Number).filter((id) => Number.isFinite(id)))];
|
|
84
|
+
if (!key) return nextIds;
|
|
85
|
+
if (!nextIds.length) delete pinsBySession[key];
|
|
86
|
+
else pinsBySession[key] = nextIds;
|
|
87
|
+
mkdirSync(join(homedir(), ".dsh"), { recursive: true });
|
|
88
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify({ ...prev, pinsBySession }, null, 2), "utf8");
|
|
89
|
+
return nextIds;
|
|
90
|
+
}
|
|
91
|
+
|
|
66
92
|
export function settingsFileExists() {
|
|
67
93
|
return existsSync(SETTINGS_FILE);
|
|
68
94
|
}
|
package/lib/store.js
CHANGED
|
@@ -163,7 +163,7 @@ function rebuildTextIndex(db) {
|
|
|
163
163
|
|
|
164
164
|
export function findMergeTarget(db, candidate) {
|
|
165
165
|
const type = candidate?.type;
|
|
166
|
-
if (!type) return null;
|
|
166
|
+
if (!type || type === "episode") return null;
|
|
167
167
|
const name = String(candidate.name || "").trim().toLowerCase();
|
|
168
168
|
const text = String(candidate.text || "").trim().toLowerCase();
|
|
169
169
|
for (const n of findByType(db, type)) {
|
|
@@ -227,7 +227,7 @@ export function mergeInto(db, id, incoming) {
|
|
|
227
227
|
|
|
228
228
|
export function listNodes(
|
|
229
229
|
db,
|
|
230
|
-
{ type, q, includeArchived = false, includeStale = false, aboutId } = {},
|
|
230
|
+
{ type, q, includeArchived = false, includeStale = false, aboutId, includeEpisodes = false } = {},
|
|
231
231
|
) {
|
|
232
232
|
const rows = [];
|
|
233
233
|
const needle = String(q || "").trim().toLowerCase();
|
|
@@ -241,6 +241,7 @@ export function listNodes(
|
|
|
241
241
|
if (!node) continue;
|
|
242
242
|
const payload = node.payload || {};
|
|
243
243
|
if (!includeArchived && payload.status === "archived") continue;
|
|
244
|
+
if (payload.type === "episode" && !includeEpisodes && type !== "episode") continue;
|
|
244
245
|
if (type && payload.type !== type) continue;
|
|
245
246
|
if (around && !around.has(Number(id))) continue;
|
|
246
247
|
const stale = isStalePayload(payload);
|
|
@@ -418,7 +419,7 @@ export function exportGraph(db, cwd = "") {
|
|
|
418
419
|
};
|
|
419
420
|
}
|
|
420
421
|
|
|
421
|
-
function findByUri(db, uri) {
|
|
422
|
+
export function findByUri(db, uri) {
|
|
422
423
|
const needle = String(uri || "").trim();
|
|
423
424
|
if (!needle) return null;
|
|
424
425
|
for (const id of db.allNodeIds?.() || []) {
|
|
@@ -542,6 +543,7 @@ function persistVector(db, id, vec) {
|
|
|
542
543
|
}
|
|
543
544
|
|
|
544
545
|
function queueEmbed(db, id, payload) {
|
|
546
|
+
if (payload?.type === "episode") return;
|
|
545
547
|
if (!embeddingEnabled()) return;
|
|
546
548
|
const text = indexTextFor(payload);
|
|
547
549
|
if (!text) return;
|
|
@@ -564,7 +566,7 @@ export async function backfillEmbeddings(db, { log } = {}) {
|
|
|
564
566
|
for (const id of db.allNodeIds?.() || []) {
|
|
565
567
|
const node = db.get(id);
|
|
566
568
|
const payload = node?.payload;
|
|
567
|
-
if (!payload || payload.status === "archived") continue;
|
|
569
|
+
if (!payload || payload.status === "archived" || payload.type === "episode") continue;
|
|
568
570
|
const text = indexTextFor(payload);
|
|
569
571
|
if (!text) continue;
|
|
570
572
|
const vec = await embedText(text);
|
|
@@ -705,7 +707,14 @@ function rankBoost(db, hit, query, now) {
|
|
|
705
707
|
|
|
706
708
|
function hitRecord(db, id) {
|
|
707
709
|
const node = db.get(Number(id));
|
|
708
|
-
if (
|
|
710
|
+
if (
|
|
711
|
+
!node ||
|
|
712
|
+
node.payload?.status === "archived" ||
|
|
713
|
+
node.payload?.type === "workspace" ||
|
|
714
|
+
node.payload?.type === "episode"
|
|
715
|
+
) {
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
709
718
|
return {
|
|
710
719
|
id: Number(id),
|
|
711
720
|
score: 0,
|
|
@@ -749,7 +758,7 @@ function entityAnchorIds(db, query) {
|
|
|
749
758
|
const ids = [];
|
|
750
759
|
for (const id of db.allNodeIds?.() || []) {
|
|
751
760
|
const node = db.get(id);
|
|
752
|
-
if (node?.payload?.status === "archived") continue;
|
|
761
|
+
if (node?.payload?.status === "archived" || node?.payload?.type === "episode") continue;
|
|
753
762
|
if (entityAnchorScore(node?.payload, q) > 0) ids.push(Number(id));
|
|
754
763
|
}
|
|
755
764
|
return ids;
|
|
@@ -795,7 +804,7 @@ function scanBusinessNeighbors(db, id) {
|
|
|
795
804
|
return ids;
|
|
796
805
|
}
|
|
797
806
|
|
|
798
|
-
function businessNeighborIds(db, id) {
|
|
807
|
+
export function businessNeighborIds(db, id) {
|
|
799
808
|
const ids = scanBusinessNeighbors(db, id);
|
|
800
809
|
const viaTql = tqlBusinessNeighbors(db, id);
|
|
801
810
|
if (viaTql) {
|
|
@@ -804,6 +813,17 @@ function businessNeighborIds(db, id) {
|
|
|
804
813
|
return [...ids].filter((n) => Number.isFinite(n));
|
|
805
814
|
}
|
|
806
815
|
|
|
816
|
+
/** Chip suggestions: business-edge neighbors of entities named in `text`. Not written to pins. */
|
|
817
|
+
export function suggestedNeighborIds(db, text) {
|
|
818
|
+
const ids = new Set();
|
|
819
|
+
for (const eid of entityAnchorIds(db, text)) {
|
|
820
|
+
for (const nid of businessNeighborIds(db, eid)) {
|
|
821
|
+
if (Number(nid) !== Number(eid)) ids.add(Number(nid));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return [...ids];
|
|
825
|
+
}
|
|
826
|
+
|
|
807
827
|
function tqlBusinessNeighbors(db, id) {
|
|
808
828
|
if (typeof db.tql !== "function") return null;
|
|
809
829
|
const nid = Number(id);
|
|
@@ -828,7 +848,14 @@ function keywordFallback(db, text, topK) {
|
|
|
828
848
|
const hits = [];
|
|
829
849
|
for (const id of ids) {
|
|
830
850
|
const node = db.get(id);
|
|
831
|
-
if (
|
|
851
|
+
if (
|
|
852
|
+
!node ||
|
|
853
|
+
node.payload?.status === "archived" ||
|
|
854
|
+
node.payload?.type === "workspace" ||
|
|
855
|
+
node.payload?.type === "episode"
|
|
856
|
+
) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
832
859
|
const hay = indexTextFor(node.payload || {}).toLowerCase();
|
|
833
860
|
if (!hay.includes(q)) continue;
|
|
834
861
|
hits.push({
|
|
@@ -1000,6 +1027,7 @@ export function buildShortMapReport(db, budgetTokens = 400) {
|
|
|
1000
1027
|
const nodes = findByType(db, type);
|
|
1001
1028
|
counts[type] = nodes.length;
|
|
1002
1029
|
for (const n of nodes) {
|
|
1030
|
+
if (n.payload?.type === "episode") continue;
|
|
1003
1031
|
if (type === "decision" && isStalePayload(n.payload)) continue;
|
|
1004
1032
|
let label = n.payload?.name || n.payload?.text;
|
|
1005
1033
|
if (!label) continue;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-trivium",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "In-process graph memory
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "In-process graph memory and session map for DeepSeek Harness, backed by TriviumDB.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"client": {
|
|
24
24
|
"inject": [
|
|
25
25
|
"@deepseek-ai/dsh-client-runtime",
|
|
26
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
27
28
|
],
|
|
28
29
|
"platform": "web"
|
|
29
30
|
}
|
|
@@ -37,6 +38,9 @@
|
|
|
37
38
|
"smoke-p4": "node scripts/smoke-p4.mjs",
|
|
38
39
|
"smoke-p5": "node scripts/smoke-p5.mjs",
|
|
39
40
|
"smoke-p6": "node scripts/smoke-p6.mjs",
|
|
41
|
+
"smoke-p7": "node scripts/smoke-p7.mjs",
|
|
42
|
+
"smoke-p8": "node scripts/smoke-p8.mjs",
|
|
43
|
+
"smoke-p9": "node scripts/smoke-p9.mjs",
|
|
40
44
|
"verify-live-p2": "node scripts/verify-live-p2.mjs"
|
|
41
45
|
},
|
|
42
46
|
"engines": {
|