stratagate-dsh 0.2.19 → 0.2.25
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/CHANGELOG.md +38 -0
- package/README.md +24 -11
- package/dist/client.js +545 -99
- package/dist/index.js +1261 -184
- package/dist/index.js.map +1 -1
- package/docs/README.zh-CN.md +24 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
-
import { createUserMessage as
|
|
4
|
+
import { createUserMessage as createUserMessage3 } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -122,12 +122,12 @@ function isFillerSentence(value) {
|
|
|
122
122
|
}
|
|
123
123
|
function resultSummary(value) {
|
|
124
124
|
if (typeof value === "string") {
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
125
|
+
const text3 = value.replace(/\s+/g, " ").trim();
|
|
126
|
+
if (!text3) return "";
|
|
127
127
|
try {
|
|
128
|
-
return resultSummary(JSON.parse(
|
|
128
|
+
return resultSummary(JSON.parse(text3));
|
|
129
129
|
} catch {
|
|
130
|
-
return
|
|
130
|
+
return text3.slice(0, 160);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
@@ -199,9 +199,9 @@ function condenseMessage(content) {
|
|
|
199
199
|
const source = content.trim();
|
|
200
200
|
const toolSummary = summarizeToolJson(source);
|
|
201
201
|
if (toolSummary) return [{ text: toolSummary, pasteCandidate: false }];
|
|
202
|
-
return splitTextAndCode(source).flatMap(({ text:
|
|
203
|
-
if (isCode) return
|
|
204
|
-
return
|
|
202
|
+
return splitTextAndCode(source).flatMap(({ text: text3, isCode }) => {
|
|
203
|
+
if (isCode) return text3.trim() ? [{ text: text3.trim(), pasteCandidate: true }] : [];
|
|
204
|
+
return text3.split(/\n\s*\n+/u).map(removeStandaloneFillers).filter(Boolean).map((paragraph) => ({
|
|
205
205
|
text: paragraph,
|
|
206
206
|
pasteCandidate: looksLikeCode(paragraph) || normalizedParagraph(paragraph).length >= REPEATED_PASTE_MIN_CHARS
|
|
207
207
|
}));
|
|
@@ -209,8 +209,8 @@ function condenseMessage(content) {
|
|
|
209
209
|
}
|
|
210
210
|
function formatReadableTranscript(messages) {
|
|
211
211
|
return messages.filter((message) => message.role !== "system").flatMap((message) => {
|
|
212
|
-
const
|
|
213
|
-
const inline = message.role === "tool" ? summarizeToolJson(
|
|
212
|
+
const text3 = message.content.trim();
|
|
213
|
+
const inline = message.role === "tool" ? summarizeToolJson(text3) ?? text3 : text3;
|
|
214
214
|
const lines = inline ? [`${roleLabel(message.role)}: ${inline}`] : [];
|
|
215
215
|
lines.push(...(message.toolCalls ?? []).map(summarizeToolTrace));
|
|
216
216
|
return lines;
|
|
@@ -252,36 +252,6 @@ function deterministicBlockLayers(messages) {
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
// ../../src/retrieval.ts
|
|
256
|
-
var RETRIEVAL_STRATEGIES = [
|
|
257
|
-
"answer",
|
|
258
|
-
"search_events",
|
|
259
|
-
"expand_event",
|
|
260
|
-
"search_elements",
|
|
261
|
-
"expand_element",
|
|
262
|
-
"search_raw_memory",
|
|
263
|
-
"expand_block"
|
|
264
|
-
];
|
|
265
|
-
function shortText(value) {
|
|
266
|
-
return typeof value === "string" ? value.trim().replace(/\s+/g, " ").slice(0, 160) : "";
|
|
267
|
-
}
|
|
268
|
-
function normalizeStrategy(value) {
|
|
269
|
-
return typeof value === "string" && RETRIEVAL_STRATEGIES.includes(value) ? value : "search_events";
|
|
270
|
-
}
|
|
271
|
-
function normalizeRetrievalAssessment(input, latestEvidenceRefs) {
|
|
272
|
-
const requestedVerdict = input.verdict === "sufficient" || input.verdict === "wrong" ? input.verdict : "partial";
|
|
273
|
-
const evidenceRefs = Array.isArray(input.evidence_refs) ? [...new Set(input.evidence_refs.filter((id) => typeof id === "string" && latestEvidenceRefs.has(id)))].slice(0, 8) : [];
|
|
274
|
-
const requestedStrategy = normalizeStrategy(input.next_strategy);
|
|
275
|
-
const sufficient = requestedVerdict === "sufficient" && evidenceRefs.length > 0 && requestedStrategy === "answer";
|
|
276
|
-
return {
|
|
277
|
-
verdict: sufficient ? "sufficient" : requestedVerdict === "wrong" ? "wrong" : "partial",
|
|
278
|
-
evidenceRefs,
|
|
279
|
-
fit: shortText(input.fit),
|
|
280
|
-
missing: sufficient ? "" : shortText(input.missing) || "Direct evidence required to answer the question is still missing.",
|
|
281
|
-
nextStrategy: sufficient ? "answer" : requestedStrategy === "answer" ? "search_events" : requestedStrategy
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
255
|
// ../../src/search.ts
|
|
286
256
|
var wordSegmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
287
257
|
function normalizeSearchText(value) {
|
|
@@ -359,8 +329,215 @@ function rrfRank(rankings) {
|
|
|
359
329
|
return [...fused.values()].sort((left, right) => right.score - left.score || left.bestRank - right.bestRank || left.item.id.localeCompare(right.item.id)).map(({ item, score }) => ({ item, score }));
|
|
360
330
|
}
|
|
361
331
|
|
|
332
|
+
// ../../src/graph.ts
|
|
333
|
+
var NODE_TYPES = /* @__PURE__ */ new Set(["person", "project", "organization", "tool", "place"]);
|
|
334
|
+
var STATUSES = /* @__PURE__ */ new Set(["active", "superseded", "disputed", "archived"]);
|
|
335
|
+
function text(value, limit = 240) {
|
|
336
|
+
return typeof value === "string" ? value.trim().replace(/\s+/g, " ").slice(0, limit) : "";
|
|
337
|
+
}
|
|
338
|
+
function strings(value, limit = 32) {
|
|
339
|
+
return Array.isArray(value) ? [...new Set(value.map((item) => text(item)).filter(Boolean))].slice(0, limit) : [];
|
|
340
|
+
}
|
|
341
|
+
function confidence(value) {
|
|
342
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0.8;
|
|
343
|
+
}
|
|
344
|
+
function chronology(events, ids, fallback) {
|
|
345
|
+
return ids.flatMap((id) => events.find((event) => event.id === id) ?? []).map((event) => event.temporal.happenedStart ?? event.temporal.happenedEnd ?? event.temporal.mentionedAt ?? event.createdAt).sort().at(-1) ?? fallback;
|
|
346
|
+
}
|
|
347
|
+
function sameEntity(node, proposal) {
|
|
348
|
+
if (node.type !== proposal.type) return false;
|
|
349
|
+
const key = (value) => normalizeSearchText(value).replace(/[\s_-]+/g, "");
|
|
350
|
+
const names = new Set([proposal.name, ...proposal.aliases ?? []].map(key));
|
|
351
|
+
return [node.name, ...node.aliases].some((name2) => names.has(key(name2)));
|
|
352
|
+
}
|
|
353
|
+
function applyGraphProjection(options) {
|
|
354
|
+
const refs = /* @__PURE__ */ new Map();
|
|
355
|
+
const touchedNodes = /* @__PURE__ */ new Set();
|
|
356
|
+
const touchedEdges = /* @__PURE__ */ new Set();
|
|
357
|
+
const validSources = (value) => {
|
|
358
|
+
const requested = strings(value, 64);
|
|
359
|
+
return requested.length > 0 && requested.every((id) => options.allowedEventIds.has(id)) ? requested : [];
|
|
360
|
+
};
|
|
361
|
+
for (const proposal of Array.isArray(options.result.nodes) ? options.result.nodes : []) {
|
|
362
|
+
const ref = text(proposal.ref, 120);
|
|
363
|
+
const name2 = text(proposal.name, 160);
|
|
364
|
+
const sources2 = validSources(proposal.sourceEventIds);
|
|
365
|
+
if (!ref || !name2 || !NODE_TYPES.has(proposal.type) || sources2.length === 0) continue;
|
|
366
|
+
const aliases = strings(proposal.aliases, 20).filter((alias) => normalizeSearchText(alias) !== normalizeSearchText(name2));
|
|
367
|
+
let node = options.nodes.find((candidate) => sameEntity(candidate, { ...proposal, name: name2, aliases }));
|
|
368
|
+
if (!node) {
|
|
369
|
+
node = {
|
|
370
|
+
id: options.idFactory("node"),
|
|
371
|
+
name: name2,
|
|
372
|
+
type: proposal.type,
|
|
373
|
+
aliases: [],
|
|
374
|
+
currentState: "",
|
|
375
|
+
facts: [],
|
|
376
|
+
status: "active",
|
|
377
|
+
confidence: confidence(proposal.confidence),
|
|
378
|
+
sourceEventIds: [],
|
|
379
|
+
createdAt: options.now,
|
|
380
|
+
updatedAt: options.now
|
|
381
|
+
};
|
|
382
|
+
options.nodes.push(node);
|
|
383
|
+
}
|
|
384
|
+
node.aliases = [.../* @__PURE__ */ new Set([...node.aliases, ...aliases])];
|
|
385
|
+
node.status = STATUSES.has(proposal.status ?? "active") ? proposal.status ?? "active" : "active";
|
|
386
|
+
node.confidence = confidence(proposal.confidence);
|
|
387
|
+
node.sourceEventIds = [.../* @__PURE__ */ new Set([...node.sourceEventIds, ...sources2])];
|
|
388
|
+
const validFrom = text(proposal.validFrom, 80) || chronology(options.events, sources2, options.now);
|
|
389
|
+
const validTo = text(proposal.validTo, 80) || void 0;
|
|
390
|
+
const facts = [
|
|
391
|
+
...text(proposal.state, 1200) ? [{ key: "state", value: text(proposal.state, 1200) }] : [],
|
|
392
|
+
...Array.isArray(proposal.facts) ? proposal.facts : []
|
|
393
|
+
];
|
|
394
|
+
for (const rawFact of facts) {
|
|
395
|
+
const key = text(rawFact.key, 160);
|
|
396
|
+
const value = Array.isArray(rawFact.value) ? strings(rawFact.value, 40) : text(rawFact.value, 1200);
|
|
397
|
+
if (!key || (Array.isArray(value) ? value.length === 0 : !value)) continue;
|
|
398
|
+
const factSources = "sourceEventIds" in rawFact ? validSources(rawFact.sourceEventIds) : sources2;
|
|
399
|
+
if (factSources.length === 0) continue;
|
|
400
|
+
for (const old of node.facts.filter((fact2) => fact2.status === "active" && fact2.key === key)) {
|
|
401
|
+
old.status = "superseded";
|
|
402
|
+
if (!old.validTo) old.validTo = validFrom;
|
|
403
|
+
old.updatedAt = options.now;
|
|
404
|
+
}
|
|
405
|
+
const fact = {
|
|
406
|
+
id: options.idFactory("gfact"),
|
|
407
|
+
key,
|
|
408
|
+
value,
|
|
409
|
+
status: node.status,
|
|
410
|
+
validFrom,
|
|
411
|
+
...validTo ? { validTo } : {},
|
|
412
|
+
confidence: node.confidence,
|
|
413
|
+
sourceEventIds: factSources,
|
|
414
|
+
createdAt: options.now,
|
|
415
|
+
updatedAt: options.now
|
|
416
|
+
};
|
|
417
|
+
node.facts.push(fact);
|
|
418
|
+
}
|
|
419
|
+
node.currentState = node.facts.filter((fact) => fact.status === "active").map((fact) => `${fact.key}: ${Array.isArray(fact.value) ? fact.value.join("\u3001") : fact.value}`).join("\n");
|
|
420
|
+
node.updatedAt = options.now;
|
|
421
|
+
refs.set(ref, node);
|
|
422
|
+
touchedNodes.add(node.id);
|
|
423
|
+
}
|
|
424
|
+
for (const proposal of Array.isArray(options.result.edges) ? options.result.edges : []) {
|
|
425
|
+
const from = refs.get(text(proposal.fromRef, 120));
|
|
426
|
+
const to = refs.get(text(proposal.toRef, 120));
|
|
427
|
+
const relation = text(proposal.relation, 100);
|
|
428
|
+
const sources2 = validSources(proposal.sourceEventIds);
|
|
429
|
+
if (!from || !to || from.id === to.id || !relation || sources2.length === 0) continue;
|
|
430
|
+
const status = STATUSES.has(proposal.status ?? "active") ? proposal.status ?? "active" : "active";
|
|
431
|
+
const validFrom = text(proposal.validFrom, 80) || chronology(options.events, sources2, options.now);
|
|
432
|
+
const validTo = text(proposal.validTo, 80) || void 0;
|
|
433
|
+
for (const old of options.edges.filter((edge2) => edge2.status === "active" && edge2.fromNodeId === from.id && edge2.relation === relation && edge2.toNodeId !== to.id)) {
|
|
434
|
+
old.status = "superseded";
|
|
435
|
+
if (!old.validTo) old.validTo = validFrom;
|
|
436
|
+
old.updatedAt = options.now;
|
|
437
|
+
}
|
|
438
|
+
let edge = options.edges.find((candidate) => candidate.fromNodeId === from.id && candidate.toNodeId === to.id && candidate.relation === relation && candidate.status === status);
|
|
439
|
+
if (!edge) {
|
|
440
|
+
edge = {
|
|
441
|
+
id: options.idFactory("edge"),
|
|
442
|
+
fromNodeId: from.id,
|
|
443
|
+
toNodeId: to.id,
|
|
444
|
+
relation,
|
|
445
|
+
status,
|
|
446
|
+
validFrom,
|
|
447
|
+
...validTo ? { validTo } : {},
|
|
448
|
+
confidence: confidence(proposal.confidence),
|
|
449
|
+
sourceEventIds: sources2,
|
|
450
|
+
createdAt: options.now,
|
|
451
|
+
updatedAt: options.now
|
|
452
|
+
};
|
|
453
|
+
options.edges.push(edge);
|
|
454
|
+
} else {
|
|
455
|
+
edge.sourceEventIds = [.../* @__PURE__ */ new Set([...edge.sourceEventIds, ...sources2])];
|
|
456
|
+
edge.confidence = confidence(proposal.confidence);
|
|
457
|
+
edge.updatedAt = options.now;
|
|
458
|
+
}
|
|
459
|
+
touchedEdges.add(edge.id);
|
|
460
|
+
}
|
|
461
|
+
for (const event of options.events.filter((candidate) => options.allowedEventIds.has(candidate.id))) {
|
|
462
|
+
const participantNodeIds = options.nodes.filter((node) => node.sourceEventIds.includes(event.id)).map(({ id }) => id);
|
|
463
|
+
event.temporal.participantNodeIds = [.../* @__PURE__ */ new Set([...event.temporal.participantNodeIds ?? [], ...participantNodeIds])];
|
|
464
|
+
}
|
|
465
|
+
return { nodeIds: [...touchedNodes], edgeIds: [...touchedEdges] };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ../../src/events.ts
|
|
469
|
+
function normalizeStandardEventType(value) {
|
|
470
|
+
const normalized = normalizeSearchText(value ?? "").replace(/[\s_-]+/g, "");
|
|
471
|
+
const aliases = {
|
|
472
|
+
"\u53D1\u5E03": "release",
|
|
473
|
+
"\u7248\u672C\u53D1\u5E03": "release",
|
|
474
|
+
release: "release",
|
|
475
|
+
released: "release",
|
|
476
|
+
"\u51B3\u5B9A": "decision",
|
|
477
|
+
"\u51B3\u7B56": "decision",
|
|
478
|
+
decision: "decision",
|
|
479
|
+
"\u5B8C\u6210": "task_completed",
|
|
480
|
+
"\u4EFB\u52A1\u5B8C\u6210": "task_completed",
|
|
481
|
+
taskcompleted: "task_completed",
|
|
482
|
+
completed: "task_completed",
|
|
483
|
+
"\u8BA1\u5212": "plan",
|
|
484
|
+
plan: "plan",
|
|
485
|
+
planned: "plan",
|
|
486
|
+
"\u53D8\u66F4": "change",
|
|
487
|
+
"\u4FEE\u6539": "change",
|
|
488
|
+
change: "change",
|
|
489
|
+
"\u53D6\u6D88": "cancellation",
|
|
490
|
+
cancellation: "cancellation",
|
|
491
|
+
cancelled: "cancellation",
|
|
492
|
+
canceled: "cancellation",
|
|
493
|
+
"\u6545\u969C": "incident",
|
|
494
|
+
incident: "incident",
|
|
495
|
+
"\u4F1A\u8BAE": "meeting",
|
|
496
|
+
meeting: "meeting",
|
|
497
|
+
"\u534F\u4F5C": "collaboration",
|
|
498
|
+
collaboration: "collaboration",
|
|
499
|
+
"\u8FC1\u79FB": "migration",
|
|
500
|
+
migration: "migration",
|
|
501
|
+
other: "other"
|
|
502
|
+
};
|
|
503
|
+
return aliases[normalized] ?? "other";
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ../../src/retrieval.ts
|
|
507
|
+
var RETRIEVAL_STRATEGIES = [
|
|
508
|
+
"answer",
|
|
509
|
+
"search_events",
|
|
510
|
+
"expand_event",
|
|
511
|
+
"search_graph",
|
|
512
|
+
"expand_graph_node",
|
|
513
|
+
"search_elements",
|
|
514
|
+
"expand_element",
|
|
515
|
+
"search_raw_memory",
|
|
516
|
+
"expand_block"
|
|
517
|
+
];
|
|
518
|
+
function shortText(value) {
|
|
519
|
+
return typeof value === "string" ? value.trim().replace(/\s+/g, " ").slice(0, 160) : "";
|
|
520
|
+
}
|
|
521
|
+
function normalizeStrategy(value) {
|
|
522
|
+
return typeof value === "string" && RETRIEVAL_STRATEGIES.includes(value) ? value : "search_events";
|
|
523
|
+
}
|
|
524
|
+
function normalizeRetrievalAssessment(input, latestEvidenceRefs) {
|
|
525
|
+
const requestedVerdict = input.verdict === "sufficient" || input.verdict === "wrong" ? input.verdict : "partial";
|
|
526
|
+
const evidenceRefs = Array.isArray(input.evidence_refs) ? [...new Set(input.evidence_refs.filter((id) => typeof id === "string" && latestEvidenceRefs.has(id)))].slice(0, 8) : [];
|
|
527
|
+
const requestedStrategy = normalizeStrategy(input.next_strategy);
|
|
528
|
+
const sufficient = requestedVerdict === "sufficient" && evidenceRefs.length > 0 && requestedStrategy === "answer";
|
|
529
|
+
return {
|
|
530
|
+
verdict: sufficient ? "sufficient" : requestedVerdict === "wrong" ? "wrong" : "partial",
|
|
531
|
+
evidenceRefs,
|
|
532
|
+
fit: shortText(input.fit),
|
|
533
|
+
missing: sufficient ? "" : shortText(input.missing) || "Direct evidence required to answer the question is still missing.",
|
|
534
|
+
nextStrategy: sufficient ? "answer" : requestedStrategy === "answer" ? "search_events" : requestedStrategy
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
362
538
|
// ../../src/storage.ts
|
|
363
|
-
var STRATAGATE_STORAGE_SCHEMA_VERSION =
|
|
539
|
+
var STRATAGATE_STORAGE_SCHEMA_VERSION = 8;
|
|
540
|
+
var KNOWLEDGE_GRAPH_PROJECTOR_VERSION = 1;
|
|
364
541
|
var StorageConflictError = class extends Error {
|
|
365
542
|
constructor(namespace, expectedRevision, actualRevision) {
|
|
366
543
|
super(`Storage revision conflict for ${namespace}: expected ${expectedRevision}, found ${actualRevision ?? "missing"}`);
|
|
@@ -376,11 +553,14 @@ var StorageConflictError = class extends Error {
|
|
|
376
553
|
function cloneSnapshot(snapshot) {
|
|
377
554
|
return structuredClone(snapshot);
|
|
378
555
|
}
|
|
556
|
+
function emptyGraph() {
|
|
557
|
+
return { graphNodes: [], graphEdges: [], graphProjectionJobs: [] };
|
|
558
|
+
}
|
|
379
559
|
function migrateLegacyBlocks(blocks) {
|
|
380
560
|
return blocks.map((block) => {
|
|
381
561
|
const { pointerAnchorTurn, ...current } = block;
|
|
382
562
|
const position = blocks.filter((candidate) => candidate.threadId === block.threadId && candidate.endTurn <= pointerAnchorTurn).length;
|
|
383
|
-
return { ...current, pointerAnchorBlockPosition: Math.max(1, position) };
|
|
563
|
+
return { ...current, pointerAnchorBlockPosition: Math.max(1, position), lastLiftedBy: null };
|
|
384
564
|
});
|
|
385
565
|
}
|
|
386
566
|
function normalizeSnapshot(value) {
|
|
@@ -397,7 +577,8 @@ function normalizeSnapshot(value) {
|
|
|
397
577
|
elements: [],
|
|
398
578
|
elementProjectionJobs: [],
|
|
399
579
|
usageReceipts: Array.isArray(legacy.usageReceipts) ? legacy.usageReceipts.map((receipt) => ({ ...receipt, elementIds: [] })) : [],
|
|
400
|
-
ingestionReceipts: []
|
|
580
|
+
ingestionReceipts: [],
|
|
581
|
+
...emptyGraph()
|
|
401
582
|
};
|
|
402
583
|
} else if (schemaVersion === 2) {
|
|
403
584
|
const legacy = value;
|
|
@@ -406,7 +587,8 @@ function normalizeSnapshot(value) {
|
|
|
406
587
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
407
588
|
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
408
589
|
blocks: migrateLegacyBlocks(legacy.blocks),
|
|
409
|
-
ingestionReceipts: []
|
|
590
|
+
ingestionReceipts: [],
|
|
591
|
+
...emptyGraph()
|
|
410
592
|
};
|
|
411
593
|
} else if (schemaVersion === 3) {
|
|
412
594
|
const legacy = value;
|
|
@@ -414,7 +596,8 @@ function normalizeSnapshot(value) {
|
|
|
414
596
|
...structuredClone(legacy),
|
|
415
597
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
416
598
|
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
417
|
-
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
599
|
+
blocks: migrateLegacyBlocks(legacy.blocks),
|
|
600
|
+
...emptyGraph()
|
|
418
601
|
};
|
|
419
602
|
} else if (schemaVersion === 4) {
|
|
420
603
|
const legacy = value;
|
|
@@ -422,7 +605,8 @@ function normalizeSnapshot(value) {
|
|
|
422
605
|
...structuredClone(legacy),
|
|
423
606
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
424
607
|
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
425
|
-
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
608
|
+
blocks: migrateLegacyBlocks(legacy.blocks),
|
|
609
|
+
...emptyGraph()
|
|
426
610
|
};
|
|
427
611
|
} else if (schemaVersion === 5) {
|
|
428
612
|
const legacy = value;
|
|
@@ -430,8 +614,20 @@ function normalizeSnapshot(value) {
|
|
|
430
614
|
...structuredClone(legacy),
|
|
431
615
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
432
616
|
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
433
|
-
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
617
|
+
blocks: migrateLegacyBlocks(legacy.blocks),
|
|
618
|
+
...emptyGraph()
|
|
619
|
+
};
|
|
620
|
+
} else if (schemaVersion === 6) {
|
|
621
|
+
const legacy = value;
|
|
622
|
+
snapshot = {
|
|
623
|
+
...structuredClone(legacy),
|
|
624
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
625
|
+
blocks: legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null })),
|
|
626
|
+
...emptyGraph()
|
|
434
627
|
};
|
|
628
|
+
} else if (schemaVersion === 7) {
|
|
629
|
+
const legacy = value;
|
|
630
|
+
snapshot = { ...structuredClone(legacy), schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION, ...emptyGraph() };
|
|
435
631
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
436
632
|
snapshot = structuredClone(value);
|
|
437
633
|
} else {
|
|
@@ -446,14 +642,20 @@ function normalizeSnapshot(value) {
|
|
|
446
642
|
if (!Number.isFinite(snapshot.blockDecayLambda) || snapshot.blockDecayLambda < 0) {
|
|
447
643
|
throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
|
|
448
644
|
}
|
|
449
|
-
for (const key of ["openTail", "blocks", "events", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts"]) {
|
|
645
|
+
for (const key of ["openTail", "blocks", "events", "graphNodes", "graphEdges", "graphProjectionJobs", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts"]) {
|
|
450
646
|
if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
|
|
451
647
|
}
|
|
452
648
|
if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
|
|
649
|
+
for (const event of snapshot.events) {
|
|
650
|
+
event.temporal = { ...event.temporal, eventType: normalizeStandardEventType(event.temporal.eventType) };
|
|
651
|
+
}
|
|
453
652
|
for (const block of snapshot.blocks) {
|
|
454
653
|
if (!Number.isSafeInteger(block.pointerAnchorBlockPosition) || block.pointerAnchorBlockPosition < 1) {
|
|
455
654
|
throw new TypeError("Invalid StrataGate snapshot: pointerAnchorBlockPosition must be a positive integer");
|
|
456
655
|
}
|
|
656
|
+
if (block.lastLiftedBy !== null && block.lastLiftedBy !== "user" && block.lastLiftedBy !== "agent") {
|
|
657
|
+
throw new TypeError("Invalid StrataGate snapshot: lastLiftedBy must be user, agent, or null");
|
|
658
|
+
}
|
|
457
659
|
}
|
|
458
660
|
if (snapshot.successfulModelResponses.length > 5) {
|
|
459
661
|
snapshot.successfulModelResponses = snapshot.successfulModelResponses.slice(-5);
|
|
@@ -652,6 +854,7 @@ CREATE TABLE IF NOT EXISTS blocks (
|
|
|
652
854
|
pointer_anchor_level INTEGER NOT NULL,
|
|
653
855
|
pointer_anchor_block_position INTEGER NOT NULL,
|
|
654
856
|
last_lifted_at TEXT,
|
|
857
|
+
last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent')),
|
|
655
858
|
PRIMARY KEY (namespace, id),
|
|
656
859
|
UNIQUE (namespace, sequence),
|
|
657
860
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
@@ -806,6 +1009,15 @@ CREATE TABLE IF NOT EXISTS element_projection_jobs (
|
|
|
806
1009
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
807
1010
|
) STRICT;
|
|
808
1011
|
|
|
1012
|
+
CREATE TABLE IF NOT EXISTS graph_state (
|
|
1013
|
+
namespace TEXT PRIMARY KEY,
|
|
1014
|
+
nodes_json TEXT NOT NULL DEFAULT '[]',
|
|
1015
|
+
edges_json TEXT NOT NULL DEFAULT '[]',
|
|
1016
|
+
jobs_json TEXT NOT NULL DEFAULT '[]',
|
|
1017
|
+
updated_at TEXT NOT NULL,
|
|
1018
|
+
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
1019
|
+
) STRICT;
|
|
1020
|
+
|
|
809
1021
|
CREATE TABLE IF NOT EXISTS usage_receipts (
|
|
810
1022
|
namespace TEXT NOT NULL,
|
|
811
1023
|
receipt_id TEXT NOT NULL,
|
|
@@ -918,7 +1130,8 @@ var SqliteStorage = class {
|
|
|
918
1130
|
pointerCurrentLevel: row.pointer_current_level,
|
|
919
1131
|
pointerAnchorLevel: row.pointer_anchor_level,
|
|
920
1132
|
pointerAnchorBlockPosition: row.pointer_anchor_block_position,
|
|
921
|
-
lastLiftedAt: row.last_lifted_at
|
|
1133
|
+
lastLiftedAt: row.last_lifted_at,
|
|
1134
|
+
lastLiftedBy: row.last_lifted_by
|
|
922
1135
|
}));
|
|
923
1136
|
const sourceRows = this.database.prepare(`
|
|
924
1137
|
SELECT event_id, message_id, position FROM event_sources
|
|
@@ -942,7 +1155,10 @@ var SqliteStorage = class {
|
|
|
942
1155
|
quotes: parseJson(row.quotes_json, "events.quotes_json"),
|
|
943
1156
|
sourceMessageIds: sourcesByEvent.get(row.id) ?? [],
|
|
944
1157
|
sourceBlockId: row.source_block_id,
|
|
945
|
-
temporal:
|
|
1158
|
+
temporal: (() => {
|
|
1159
|
+
const temporal = parseJson(row.temporal_json, "events.temporal_json");
|
|
1160
|
+
return { ...temporal, eventType: normalizeStandardEventType(temporal.eventType) };
|
|
1161
|
+
})(),
|
|
946
1162
|
scope: row.scope,
|
|
947
1163
|
criticality: row.criticality,
|
|
948
1164
|
confidence: row.confidence,
|
|
@@ -1080,6 +1296,12 @@ var SqliteStorage = class {
|
|
|
1080
1296
|
id: row.receipt_id,
|
|
1081
1297
|
createdAt: row.created_at
|
|
1082
1298
|
}));
|
|
1299
|
+
const graphState = this.database.prepare(`
|
|
1300
|
+
SELECT nodes_json, edges_json, jobs_json FROM graph_state WHERE namespace = ?
|
|
1301
|
+
`).get(key);
|
|
1302
|
+
const graphNodes = graphState ? parseJson(graphState.nodes_json, "graph_state.nodes_json") : [];
|
|
1303
|
+
const graphEdges = graphState ? parseJson(graphState.edges_json, "graph_state.edges_json") : [];
|
|
1304
|
+
const graphProjectionJobs = graphState ? parseJson(graphState.jobs_json, "graph_state.jobs_json") : [];
|
|
1083
1305
|
const snapshot = {
|
|
1084
1306
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1085
1307
|
currentTurn: space.current_turn,
|
|
@@ -1088,6 +1310,9 @@ var SqliteStorage = class {
|
|
|
1088
1310
|
openTail,
|
|
1089
1311
|
blocks,
|
|
1090
1312
|
events,
|
|
1313
|
+
graphNodes,
|
|
1314
|
+
graphEdges,
|
|
1315
|
+
graphProjectionJobs,
|
|
1091
1316
|
elements,
|
|
1092
1317
|
extractionJobs,
|
|
1093
1318
|
elementProjectionJobs,
|
|
@@ -1154,8 +1379,8 @@ var SqliteStorage = class {
|
|
|
1154
1379
|
INSERT INTO blocks (
|
|
1155
1380
|
namespace, id, thread_id, sequence, start_turn, end_turn, created_at, should_extract,
|
|
1156
1381
|
l0_title, l0_tags_json, l1_summary, l2_keypoints_json, l3_condensed, l4_readable,
|
|
1157
|
-
pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at
|
|
1158
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1382
|
+
pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at, last_lifted_by
|
|
1383
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1159
1384
|
ON CONFLICT (namespace, id) DO UPDATE SET
|
|
1160
1385
|
thread_id = excluded.thread_id,
|
|
1161
1386
|
sequence = excluded.sequence,
|
|
@@ -1172,7 +1397,8 @@ var SqliteStorage = class {
|
|
|
1172
1397
|
pointer_current_level = excluded.pointer_current_level,
|
|
1173
1398
|
pointer_anchor_level = excluded.pointer_anchor_level,
|
|
1174
1399
|
pointer_anchor_block_position = excluded.pointer_anchor_block_position,
|
|
1175
|
-
last_lifted_at = excluded.last_lifted_at
|
|
1400
|
+
last_lifted_at = excluded.last_lifted_at,
|
|
1401
|
+
last_lifted_by = excluded.last_lifted_by
|
|
1176
1402
|
`);
|
|
1177
1403
|
for (const block of snapshot.blocks) {
|
|
1178
1404
|
insertBlock.run(
|
|
@@ -1193,7 +1419,8 @@ var SqliteStorage = class {
|
|
|
1193
1419
|
block.pointerCurrentLevel,
|
|
1194
1420
|
block.pointerAnchorLevel,
|
|
1195
1421
|
block.pointerAnchorBlockPosition,
|
|
1196
|
-
block.lastLiftedAt
|
|
1422
|
+
block.lastLiftedAt,
|
|
1423
|
+
block.lastLiftedBy
|
|
1197
1424
|
);
|
|
1198
1425
|
}
|
|
1199
1426
|
const insertMessage = this.database.prepare(`
|
|
@@ -1418,6 +1645,21 @@ var SqliteStorage = class {
|
|
|
1418
1645
|
job.updatedAt
|
|
1419
1646
|
);
|
|
1420
1647
|
}
|
|
1648
|
+
this.database.prepare(`
|
|
1649
|
+
INSERT INTO graph_state (namespace, nodes_json, edges_json, jobs_json, updated_at)
|
|
1650
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1651
|
+
ON CONFLICT (namespace) DO UPDATE SET
|
|
1652
|
+
nodes_json = excluded.nodes_json,
|
|
1653
|
+
edges_json = excluded.edges_json,
|
|
1654
|
+
jobs_json = excluded.jobs_json,
|
|
1655
|
+
updated_at = excluded.updated_at
|
|
1656
|
+
`).run(
|
|
1657
|
+
namespace,
|
|
1658
|
+
JSON.stringify(snapshot.graphNodes),
|
|
1659
|
+
JSON.stringify(snapshot.graphEdges),
|
|
1660
|
+
JSON.stringify(snapshot.graphProjectionJobs),
|
|
1661
|
+
updatedAt
|
|
1662
|
+
);
|
|
1421
1663
|
const insertReceipt = this.database.prepare(`
|
|
1422
1664
|
INSERT INTO usage_receipts (namespace, receipt_id, event_ids_json, element_ids_json, audit_json, created_at)
|
|
1423
1665
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -1462,7 +1704,7 @@ var SqliteStorage = class {
|
|
|
1462
1704
|
this.database.exec(THREAD_INDEXES);
|
|
1463
1705
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1464
1706
|
});
|
|
1465
|
-
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5) {
|
|
1707
|
+
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5 || version === 6 || version === 7) {
|
|
1466
1708
|
this.immediateTransaction(() => {
|
|
1467
1709
|
this.database.exec(SCHEMA);
|
|
1468
1710
|
if (version === 1) {
|
|
@@ -1495,6 +1737,9 @@ var SqliteStorage = class {
|
|
|
1495
1737
|
))
|
|
1496
1738
|
`);
|
|
1497
1739
|
}
|
|
1740
|
+
if (!blockColumns.some(({ name: name2 }) => name2 === "last_lifted_by")) {
|
|
1741
|
+
this.database.exec("ALTER TABLE blocks ADD COLUMN last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent'))");
|
|
1742
|
+
}
|
|
1498
1743
|
const messageColumns = this.database.prepare("PRAGMA table_info('messages')").all();
|
|
1499
1744
|
if (!messageColumns.some(({ name: name2 }) => name2 === "thread_id")) {
|
|
1500
1745
|
this.database.exec("ALTER TABLE messages ADD COLUMN thread_id TEXT");
|
|
@@ -1549,6 +1794,9 @@ function defaultIdFactory(prefix) {
|
|
|
1549
1794
|
function defaultElementIdFactory(prefix) {
|
|
1550
1795
|
return `${prefix}_${crypto.randomUUID()}`;
|
|
1551
1796
|
}
|
|
1797
|
+
function defaultGraphIdFactory(prefix) {
|
|
1798
|
+
return `${prefix}_${crypto.randomUUID()}`;
|
|
1799
|
+
}
|
|
1552
1800
|
function defaultSummary(messages) {
|
|
1553
1801
|
const natural = messages.filter((message) => message.role === "user" || message.role === "assistant");
|
|
1554
1802
|
const firstUser = natural.find((message) => message.role === "user");
|
|
@@ -1586,15 +1834,21 @@ var StrataGate = class _StrataGate {
|
|
|
1586
1834
|
summarizer;
|
|
1587
1835
|
extractor;
|
|
1588
1836
|
elementProjector;
|
|
1837
|
+
disableElementProjection;
|
|
1838
|
+
graphProjector;
|
|
1589
1839
|
now;
|
|
1590
1840
|
idFactory;
|
|
1591
1841
|
elementIdFactory;
|
|
1842
|
+
graphIdFactory;
|
|
1592
1843
|
openTail = [];
|
|
1593
1844
|
blocks = [];
|
|
1594
1845
|
events = [];
|
|
1595
1846
|
elements = [];
|
|
1847
|
+
graphNodes = [];
|
|
1848
|
+
graphEdges = [];
|
|
1596
1849
|
extractionJobs = /* @__PURE__ */ new Map();
|
|
1597
1850
|
elementProjectionJobs = /* @__PURE__ */ new Map();
|
|
1851
|
+
graphProjectionJobs = /* @__PURE__ */ new Map();
|
|
1598
1852
|
usageReceipts = /* @__PURE__ */ new Map();
|
|
1599
1853
|
successfulModelResponses = [];
|
|
1600
1854
|
ingestionReceipts = /* @__PURE__ */ new Map();
|
|
@@ -1616,9 +1870,12 @@ var StrataGate = class _StrataGate {
|
|
|
1616
1870
|
this.summarizer = options.summarizer;
|
|
1617
1871
|
this.extractor = options.extractor;
|
|
1618
1872
|
this.elementProjector = options.elementProjector;
|
|
1873
|
+
this.disableElementProjection = options.disableElementProjection ?? false;
|
|
1874
|
+
this.graphProjector = options.graphProjector;
|
|
1619
1875
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1620
1876
|
this.idFactory = options.idFactory ?? defaultIdFactory;
|
|
1621
1877
|
this.elementIdFactory = options.elementIdFactory ?? defaultElementIdFactory;
|
|
1878
|
+
this.graphIdFactory = options.graphIdFactory ?? defaultGraphIdFactory;
|
|
1622
1879
|
}
|
|
1623
1880
|
static inMemory(options = {}) {
|
|
1624
1881
|
return new _StrataGate(options, STRATAGATE_CONSTRUCTOR_TOKEN);
|
|
@@ -1639,9 +1896,12 @@ var StrataGate = class _StrataGate {
|
|
|
1639
1896
|
...options.summarizer ? { summarizer: options.summarizer } : {},
|
|
1640
1897
|
...options.extractor ? { extractor: options.extractor } : {},
|
|
1641
1898
|
...options.elementProjector ? { elementProjector: options.elementProjector } : {},
|
|
1899
|
+
...options.disableElementProjection !== void 0 ? { disableElementProjection: options.disableElementProjection } : {},
|
|
1900
|
+
...options.graphProjector ? { graphProjector: options.graphProjector } : {},
|
|
1642
1901
|
...options.now ? { now: options.now } : {},
|
|
1643
1902
|
...options.idFactory ? { idFactory: options.idFactory } : {},
|
|
1644
|
-
...options.elementIdFactory ? { elementIdFactory: options.elementIdFactory } : {}
|
|
1903
|
+
...options.elementIdFactory ? { elementIdFactory: options.elementIdFactory } : {},
|
|
1904
|
+
...options.graphIdFactory ? { graphIdFactory: options.graphIdFactory } : {}
|
|
1645
1905
|
});
|
|
1646
1906
|
} catch (error) {
|
|
1647
1907
|
await storage.close();
|
|
@@ -1683,9 +1943,12 @@ var StrataGate = class _StrataGate {
|
|
|
1683
1943
|
if (options.summarizer) memoryOptions.summarizer = options.summarizer;
|
|
1684
1944
|
if (options.extractor) memoryOptions.extractor = options.extractor;
|
|
1685
1945
|
if (options.elementProjector) memoryOptions.elementProjector = options.elementProjector;
|
|
1946
|
+
if (options.disableElementProjection !== void 0) memoryOptions.disableElementProjection = options.disableElementProjection;
|
|
1947
|
+
if (options.graphProjector) memoryOptions.graphProjector = options.graphProjector;
|
|
1686
1948
|
if (options.now) memoryOptions.now = options.now;
|
|
1687
1949
|
if (options.idFactory) memoryOptions.idFactory = options.idFactory;
|
|
1688
1950
|
if (options.elementIdFactory) memoryOptions.elementIdFactory = options.elementIdFactory;
|
|
1951
|
+
if (options.graphIdFactory) memoryOptions.graphIdFactory = options.graphIdFactory;
|
|
1689
1952
|
const memory = new _StrataGate(memoryOptions, STRATAGATE_CONSTRUCTOR_TOKEN);
|
|
1690
1953
|
memory.storage = options.storage;
|
|
1691
1954
|
memory.namespace = namespace;
|
|
@@ -1720,6 +1983,21 @@ var StrataGate = class _StrataGate {
|
|
|
1720
1983
|
}
|
|
1721
1984
|
});
|
|
1722
1985
|
}
|
|
1986
|
+
const interruptedGraphProjections = [...memory.graphProjectionJobs.values()].filter((job) => job.status === "running");
|
|
1987
|
+
if (interruptedGraphProjections.length > 0) {
|
|
1988
|
+
await memory.commitMutation(() => {
|
|
1989
|
+
const now = toUtc8Iso(memory.now());
|
|
1990
|
+
for (const job of interruptedGraphProjections) {
|
|
1991
|
+
memory.graphProjectionJobs.set(job.id, {
|
|
1992
|
+
...job,
|
|
1993
|
+
status: "failed",
|
|
1994
|
+
lastError: "Graph projection was interrupted before completion.",
|
|
1995
|
+
updatedAt: now
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
if (memory.graphProjector) await memory.commitMutation(() => memory.queueMissingGraphProjections());
|
|
1723
2001
|
} else {
|
|
1724
2002
|
await memory.persist();
|
|
1725
2003
|
}
|
|
@@ -1752,6 +2030,12 @@ var StrataGate = class _StrataGate {
|
|
|
1752
2030
|
listElements() {
|
|
1753
2031
|
return this.elements;
|
|
1754
2032
|
}
|
|
2033
|
+
listGraphNodes() {
|
|
2034
|
+
return this.graphNodes;
|
|
2035
|
+
}
|
|
2036
|
+
listGraphEdges() {
|
|
2037
|
+
return this.graphEdges;
|
|
2038
|
+
}
|
|
1755
2039
|
listOpenTail(threadId) {
|
|
1756
2040
|
if (threadId === void 0) return this.openTail;
|
|
1757
2041
|
return this.openTail.filter((message) => message.threadId === threadId);
|
|
@@ -1762,6 +2046,9 @@ var StrataGate = class _StrataGate {
|
|
|
1762
2046
|
listElementProjectionJobs() {
|
|
1763
2047
|
return [...this.elementProjectionJobs.values()];
|
|
1764
2048
|
}
|
|
2049
|
+
listGraphProjectionJobs() {
|
|
2050
|
+
return [...this.graphProjectionJobs.values()];
|
|
2051
|
+
}
|
|
1765
2052
|
listUsageReceipts() {
|
|
1766
2053
|
return [...this.usageReceipts.values()];
|
|
1767
2054
|
}
|
|
@@ -1789,6 +2076,9 @@ var StrataGate = class _StrataGate {
|
|
|
1789
2076
|
openTail: this.openTail,
|
|
1790
2077
|
blocks: this.blocks,
|
|
1791
2078
|
events: this.events,
|
|
2079
|
+
graphNodes: this.graphNodes,
|
|
2080
|
+
graphEdges: this.graphEdges,
|
|
2081
|
+
graphProjectionJobs: [...this.graphProjectionJobs.values()],
|
|
1792
2082
|
elements: this.elements,
|
|
1793
2083
|
extractionJobs: [...this.extractionJobs.values()],
|
|
1794
2084
|
elementProjectionJobs: [...this.elementProjectionJobs.values()],
|
|
@@ -1839,11 +2129,13 @@ var StrataGate = class _StrataGate {
|
|
|
1839
2129
|
}
|
|
1840
2130
|
if (this.threadOpenTail(threadId).filter((message) => message.role === "user").length < this.blockTurnSize) {
|
|
1841
2131
|
const projectedElements2 = await this.projectEligibleElements() ?? [];
|
|
2132
|
+
await this.projectEligibleGraph();
|
|
1842
2133
|
return { sealedBlock: null, extractedEvents: [], projectedElements: projectedElements2 };
|
|
1843
2134
|
}
|
|
1844
2135
|
const sealedBlock = await this.sealOpenTail(threadId);
|
|
1845
2136
|
const extractedEvents = await this.extractEligibleBlock() ?? [];
|
|
1846
2137
|
const projectedElements = await this.projectEligibleElements() ?? [];
|
|
2138
|
+
await this.projectEligibleGraph();
|
|
1847
2139
|
return { sealedBlock, extractedEvents, projectedElements };
|
|
1848
2140
|
}
|
|
1849
2141
|
async resumePendingWork(options = {}) {
|
|
@@ -1856,12 +2148,14 @@ var StrataGate = class _StrataGate {
|
|
|
1856
2148
|
sealedBlocks.push(await this.sealOpenTail(sealable.threadId));
|
|
1857
2149
|
extractedEvents.push(...await this.extractEligibleBlock() ?? []);
|
|
1858
2150
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2151
|
+
await this.projectEligibleGraph();
|
|
1859
2152
|
}
|
|
1860
2153
|
while (true) {
|
|
1861
2154
|
const extracted = await this.extractEligibleBlock();
|
|
1862
2155
|
if (extracted === null) break;
|
|
1863
2156
|
extractedEvents.push(...extracted);
|
|
1864
2157
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2158
|
+
await this.projectEligibleGraph();
|
|
1865
2159
|
}
|
|
1866
2160
|
if (options.retrySkipped === true) {
|
|
1867
2161
|
const skippedBlockIds = this.blocks.filter((block) => this.nextBlockInThread(block) !== null && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
|
|
@@ -1870,6 +2164,7 @@ var StrataGate = class _StrataGate {
|
|
|
1870
2164
|
if (extracted === null) continue;
|
|
1871
2165
|
extractedEvents.push(...extracted);
|
|
1872
2166
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2167
|
+
await this.projectEligibleGraph();
|
|
1873
2168
|
}
|
|
1874
2169
|
}
|
|
1875
2170
|
while (true) {
|
|
@@ -1877,12 +2172,14 @@ var StrataGate = class _StrataGate {
|
|
|
1877
2172
|
if (projected === null) break;
|
|
1878
2173
|
projectedElements.push(...projected);
|
|
1879
2174
|
}
|
|
2175
|
+
await this.projectEligibleGraph();
|
|
1880
2176
|
return { sealedBlocks, extractedEvents, projectedElements };
|
|
1881
2177
|
}
|
|
1882
2178
|
async addEvent(input) {
|
|
1883
2179
|
return this.commitMutation(() => {
|
|
1884
2180
|
const event = this.addEventInMemory(input);
|
|
1885
2181
|
this.queueElementProjection([event.id]);
|
|
2182
|
+
this.queueGraphProjection([event.id], 1e3);
|
|
1886
2183
|
return event;
|
|
1887
2184
|
});
|
|
1888
2185
|
}
|
|
@@ -1912,10 +2209,10 @@ var StrataGate = class _StrataGate {
|
|
|
1912
2209
|
[event.temporal.originalText ?? "", 4],
|
|
1913
2210
|
[`${event.temporal.happenedStart ?? ""} ${event.temporal.happenedEnd ?? ""}`, 4]
|
|
1914
2211
|
])).map(({ item }) => item);
|
|
1915
|
-
const
|
|
2212
|
+
const chronology2 = (event) => event.temporal.happenedStart ?? event.temporal.happenedEnd ?? event.temporal.mentionedAt ?? event.createdAt;
|
|
1916
2213
|
const structured = (items) => [...items].sort((left, right) => {
|
|
1917
|
-
if (options.temporalIntent === "first") return
|
|
1918
|
-
if (options.temporalIntent === "latest") return
|
|
2214
|
+
if (options.temporalIntent === "first") return chronology2(left).localeCompare(chronology2(right));
|
|
2215
|
+
if (options.temporalIntent === "latest") return chronology2(right).localeCompare(chronology2(left));
|
|
1919
2216
|
return memoryWeightAt(right, this.currentTurn) - memoryWeightAt(left, this.currentTurn) || right.updatedAt.localeCompare(left.updatedAt);
|
|
1920
2217
|
});
|
|
1921
2218
|
const participantIds = new Set(participantMatches.map(({ id }) => id));
|
|
@@ -2001,6 +2298,66 @@ var StrataGate = class _StrataGate {
|
|
|
2001
2298
|
job.updatedAt = toUtc8Iso(this.now());
|
|
2002
2299
|
});
|
|
2003
2300
|
}
|
|
2301
|
+
async claimNextGraphProjection() {
|
|
2302
|
+
return this.commitMutation(() => {
|
|
2303
|
+
const job = [...this.graphProjectionJobs.values()].filter((candidate) => candidate.status === "pending" || candidate.status === "failed").sort((left, right) => right.priority - left.priority || left.createdAt.localeCompare(right.createdAt))[0];
|
|
2304
|
+
if (!job) return null;
|
|
2305
|
+
const events = job.sourceEventIds.flatMap((id) => this.events.find((event) => event.id === id) ?? []);
|
|
2306
|
+
if (events.length === 0) throw new Error(`Graph projection ${job.id} has no available source events`);
|
|
2307
|
+
job.status = "running";
|
|
2308
|
+
job.attempts += 1;
|
|
2309
|
+
job.lastError = null;
|
|
2310
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
2311
|
+
const eventText = normalizeSearchText(events.map((event) => [
|
|
2312
|
+
event.title,
|
|
2313
|
+
event.summary,
|
|
2314
|
+
event.tags.join(" "),
|
|
2315
|
+
(event.temporal.participants ?? []).join(" ")
|
|
2316
|
+
].join(" ")).join(" "));
|
|
2317
|
+
const relevantNodes = this.graphNodes.filter((node) => [node.name, ...node.aliases].some((name2) => eventText.includes(normalizeSearchText(name2)))).concat([...this.graphNodes].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, 24));
|
|
2318
|
+
const existingNodes = [...new Map(relevantNodes.map((node) => [node.id, node])).values()].slice(0, 80);
|
|
2319
|
+
const nodeIds = new Set(existingNodes.map(({ id }) => id));
|
|
2320
|
+
return {
|
|
2321
|
+
jobId: job.id,
|
|
2322
|
+
projectorVersion: job.projectorVersion,
|
|
2323
|
+
events: structuredClone(events),
|
|
2324
|
+
existingNodes: structuredClone(existingNodes),
|
|
2325
|
+
existingEdges: structuredClone(this.graphEdges.filter(({ fromNodeId, toNodeId }) => nodeIds.has(fromNodeId) && nodeIds.has(toNodeId)).slice(-120))
|
|
2326
|
+
};
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
async completeGraphProjection(jobId, result) {
|
|
2330
|
+
return this.commitMutation(() => {
|
|
2331
|
+
const job = this.requireGraphProjectionJob(jobId);
|
|
2332
|
+
if (job.status === "completed") return { nodeIds: job.nodeIds, edgeIds: job.edgeIds };
|
|
2333
|
+
if (job.status !== "running") throw new Error(`Graph projection ${job.id} is ${job.status}, not running`);
|
|
2334
|
+
const touched = applyGraphProjection({
|
|
2335
|
+
nodes: this.graphNodes,
|
|
2336
|
+
edges: this.graphEdges,
|
|
2337
|
+
events: this.events,
|
|
2338
|
+
result,
|
|
2339
|
+
allowedEventIds: new Set(job.sourceEventIds),
|
|
2340
|
+
now: toUtc8Iso(this.now()),
|
|
2341
|
+
idFactory: this.graphIdFactory
|
|
2342
|
+
});
|
|
2343
|
+
job.status = "completed";
|
|
2344
|
+
job.nodeIds = touched.nodeIds;
|
|
2345
|
+
job.edgeIds = touched.edgeIds;
|
|
2346
|
+
job.reason = typeof result.reason === "string" ? result.reason.trim().replace(/\s+/g, " ").slice(0, 500) || null : null;
|
|
2347
|
+
job.lastError = null;
|
|
2348
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
2349
|
+
return touched;
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
async failGraphProjection(jobId, error) {
|
|
2353
|
+
await this.commitMutation(() => {
|
|
2354
|
+
const job = this.requireGraphProjectionJob(jobId);
|
|
2355
|
+
if (job.status === "completed") return;
|
|
2356
|
+
job.status = "failed";
|
|
2357
|
+
job.lastError = errorMessage(error);
|
|
2358
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2004
2361
|
async searchElements(query, options = {}) {
|
|
2005
2362
|
const normalizedName = normalizeSearchText(options.name ?? "");
|
|
2006
2363
|
const candidates = this.elements.flatMap((element) => element.facts.map((fact) => ({
|
|
@@ -2108,7 +2465,7 @@ var StrataGate = class _StrataGate {
|
|
|
2108
2465
|
};
|
|
2109
2466
|
});
|
|
2110
2467
|
}
|
|
2111
|
-
async expandBlock(id, target = "next") {
|
|
2468
|
+
async expandBlock(id, target = "next", source = "agent") {
|
|
2112
2469
|
return this.commitMutation(() => {
|
|
2113
2470
|
const block = this.blocks.find((candidate) => candidate.id === id);
|
|
2114
2471
|
if (!block) throw new Error(`Unknown block: ${id}`);
|
|
@@ -2125,6 +2482,7 @@ var StrataGate = class _StrataGate {
|
|
|
2125
2482
|
block.pointerAnchorLevel = level;
|
|
2126
2483
|
block.pointerAnchorBlockPosition = latestBlockPosition;
|
|
2127
2484
|
block.lastLiftedAt = toUtc8Iso(this.now());
|
|
2485
|
+
block.lastLiftedBy = source;
|
|
2128
2486
|
return {
|
|
2129
2487
|
id: block.id,
|
|
2130
2488
|
...block.threadId ? { threadId: block.threadId } : {},
|
|
@@ -2221,7 +2579,10 @@ var StrataGate = class _StrataGate {
|
|
|
2221
2579
|
quotes: [...new Set(input.quotes ?? [])].slice(0, 12),
|
|
2222
2580
|
sourceMessageIds,
|
|
2223
2581
|
sourceBlockId: sourceBlock.id,
|
|
2224
|
-
temporal:
|
|
2582
|
+
temporal: {
|
|
2583
|
+
...input.temporal ? { ...input.temporal } : { mentionedAt: now },
|
|
2584
|
+
eventType: normalizeStandardEventType(input.temporal?.eventType)
|
|
2585
|
+
},
|
|
2225
2586
|
scope: input.scope ?? "user",
|
|
2226
2587
|
criticality,
|
|
2227
2588
|
confidence: Math.max(0, Math.min(1, input.confidence ?? 1)),
|
|
@@ -2260,7 +2621,59 @@ var StrataGate = class _StrataGate {
|
|
|
2260
2621
|
if (!job) throw new Error(`Unknown element projection: ${id}`);
|
|
2261
2622
|
return job;
|
|
2262
2623
|
}
|
|
2624
|
+
async searchGraphNodes(query, limit = 8) {
|
|
2625
|
+
const candidates = this.graphNodes.filter((node) => node.status === "active" || node.status === "disputed");
|
|
2626
|
+
const ranked = bm25Rank(candidates, query, (node) => weightedSearchTokens([
|
|
2627
|
+
[node.name, 6],
|
|
2628
|
+
[node.aliases.join(" "), 5],
|
|
2629
|
+
[node.type, 2],
|
|
2630
|
+
[node.currentState, 4],
|
|
2631
|
+
[node.facts.map((fact) => `${fact.key} ${Array.isArray(fact.value) ? fact.value.join(" ") : fact.value}`).join(" "), 4],
|
|
2632
|
+
[this.graphEdges.filter((edge) => edge.fromNodeId === node.id || edge.toNodeId === node.id).map(({ relation }) => relation).join(" "), 3]
|
|
2633
|
+
])).slice(0, Math.max(1, Math.min(20, limit)));
|
|
2634
|
+
if (searchTokens(query).length > 0 && ranked.length === 0) return [];
|
|
2635
|
+
return ranked.map(({ item: node, score }) => ({ node, score }));
|
|
2636
|
+
}
|
|
2637
|
+
requireGraphProjectionJob(id) {
|
|
2638
|
+
const job = this.graphProjectionJobs.get(id);
|
|
2639
|
+
if (!job) throw new Error(`Unknown graph projection: ${id}`);
|
|
2640
|
+
return job;
|
|
2641
|
+
}
|
|
2642
|
+
queueGraphProjection(sourceEventIds, priority) {
|
|
2643
|
+
if (!this.graphProjector) return null;
|
|
2644
|
+
const completed = new Set([...this.graphProjectionJobs.values()].filter((job2) => job2.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job2.status === "completed").flatMap((job2) => job2.sourceEventIds));
|
|
2645
|
+
const queued = new Set([...this.graphProjectionJobs.values()].filter((job2) => job2.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job2.status !== "completed").flatMap((job2) => job2.sourceEventIds));
|
|
2646
|
+
const ids = [...new Set(sourceEventIds.filter((id) => this.events.some((event) => event.id === id) && !completed.has(id) && !queued.has(id)))];
|
|
2647
|
+
if (ids.length === 0) return null;
|
|
2648
|
+
const now = toUtc8Iso(this.now());
|
|
2649
|
+
const job = {
|
|
2650
|
+
id: this.graphIdFactory("gproj"),
|
|
2651
|
+
sourceEventIds: ids,
|
|
2652
|
+
projectorVersion: KNOWLEDGE_GRAPH_PROJECTOR_VERSION,
|
|
2653
|
+
status: "pending",
|
|
2654
|
+
attempts: 0,
|
|
2655
|
+
priority,
|
|
2656
|
+
nodeIds: [],
|
|
2657
|
+
edgeIds: [],
|
|
2658
|
+
reason: null,
|
|
2659
|
+
lastError: null,
|
|
2660
|
+
createdAt: now,
|
|
2661
|
+
updatedAt: now
|
|
2662
|
+
};
|
|
2663
|
+
this.graphProjectionJobs.set(job.id, job);
|
|
2664
|
+
return job;
|
|
2665
|
+
}
|
|
2666
|
+
queueMissingGraphProjections() {
|
|
2667
|
+
const candidates = [...this.events].filter((event) => event.status !== "forgotten" && event.status !== "archived").sort((left, right) => {
|
|
2668
|
+
const score = (event) => (event.status === "active" ? 1e4 : 0) + event.weight.mentionCount * 100 + (event.scope === "project" ? 500 : 0) + (Date.parse(event.temporal.happenedStart ?? event.temporal.mentionedAt ?? event.updatedAt) || 0) / 1e12;
|
|
2669
|
+
return score(right) - score(left);
|
|
2670
|
+
});
|
|
2671
|
+
for (let index = 0; index < candidates.length; index += 8) {
|
|
2672
|
+
this.queueGraphProjection(candidates.slice(index, index + 8).map(({ id }) => id), candidates.length - index);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2263
2675
|
queueElementProjection(sourceEventIds) {
|
|
2676
|
+
if (this.disableElementProjection) return null;
|
|
2264
2677
|
const ids = [...new Set(sourceEventIds.filter((id) => this.events.some((event) => event.id === id)))];
|
|
2265
2678
|
if (ids.length === 0) return null;
|
|
2266
2679
|
const now = toUtc8Iso(this.now());
|
|
@@ -2354,7 +2767,8 @@ var StrataGate = class _StrataGate {
|
|
|
2354
2767
|
pointerCurrentLevel: 5,
|
|
2355
2768
|
pointerAnchorLevel: 5,
|
|
2356
2769
|
pointerAnchorBlockPosition: blockPosition,
|
|
2357
|
-
lastLiftedAt: null
|
|
2770
|
+
lastLiftedAt: null,
|
|
2771
|
+
lastLiftedBy: null
|
|
2358
2772
|
};
|
|
2359
2773
|
const sealedIds = new Set(raw.map((message) => message.id));
|
|
2360
2774
|
const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
|
|
@@ -2428,7 +2842,11 @@ var StrataGate = class _StrataGate {
|
|
|
2428
2842
|
}
|
|
2429
2843
|
return this.commitMutation(() => {
|
|
2430
2844
|
const extracted = result.shouldExtract ? result.events.map((event) => this.addEventInMemory({ ...event, sourceBlockId: target.id })) : [];
|
|
2431
|
-
if (extracted.length > 0)
|
|
2845
|
+
if (extracted.length > 0) {
|
|
2846
|
+
const ids = extracted.map(({ id }) => id);
|
|
2847
|
+
this.queueElementProjection(ids);
|
|
2848
|
+
this.queueGraphProjection(ids, 1e3);
|
|
2849
|
+
}
|
|
2432
2850
|
const job = this.extractionJobs.get(target.id);
|
|
2433
2851
|
if (!job) throw new Error(`Missing extraction job for block: ${target.id}`);
|
|
2434
2852
|
this.extractionJobs.set(target.id, {
|
|
@@ -2452,6 +2870,17 @@ var StrataGate = class _StrataGate {
|
|
|
2452
2870
|
throw error;
|
|
2453
2871
|
}
|
|
2454
2872
|
}
|
|
2873
|
+
async projectEligibleGraph() {
|
|
2874
|
+
if (!this.graphProjector) return null;
|
|
2875
|
+
const batch = await this.claimNextGraphProjection();
|
|
2876
|
+
if (!batch) return null;
|
|
2877
|
+
try {
|
|
2878
|
+
return await this.completeGraphProjection(batch.jobId, await this.graphProjector(batch));
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
await this.failGraphProjection(batch.jobId, error);
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2455
2884
|
async commitMutation(mutation) {
|
|
2456
2885
|
const previous = this.mutationQueue;
|
|
2457
2886
|
let release;
|
|
@@ -2495,11 +2924,15 @@ var StrataGate = class _StrataGate {
|
|
|
2495
2924
|
this.openTail.splice(0, this.openTail.length, ...copy.openTail);
|
|
2496
2925
|
this.blocks.splice(0, this.blocks.length, ...copy.blocks);
|
|
2497
2926
|
this.events.splice(0, this.events.length, ...copy.events);
|
|
2927
|
+
this.graphNodes.splice(0, this.graphNodes.length, ...copy.graphNodes);
|
|
2928
|
+
this.graphEdges.splice(0, this.graphEdges.length, ...copy.graphEdges);
|
|
2498
2929
|
this.elements.splice(0, this.elements.length, ...copy.elements);
|
|
2499
2930
|
this.extractionJobs.clear();
|
|
2500
2931
|
for (const job of copy.extractionJobs) this.extractionJobs.set(job.blockId, job);
|
|
2501
2932
|
this.elementProjectionJobs.clear();
|
|
2502
2933
|
for (const job of copy.elementProjectionJobs) this.elementProjectionJobs.set(job.id, job);
|
|
2934
|
+
this.graphProjectionJobs.clear();
|
|
2935
|
+
for (const job of copy.graphProjectionJobs) this.graphProjectionJobs.set(job.id, job);
|
|
2503
2936
|
this.usageReceipts.clear();
|
|
2504
2937
|
for (const receipt of copy.usageReceipts) this.usageReceipts.set(receipt.id, receipt);
|
|
2505
2938
|
this.ingestionReceipts.clear();
|
|
@@ -2563,6 +2996,42 @@ var StrataGate = class _StrataGate {
|
|
|
2563
2996
|
if (!elementIds.has(elementId)) throw new Error(`Element projection ${job.id} references unknown element ${elementId}`);
|
|
2564
2997
|
}
|
|
2565
2998
|
}
|
|
2999
|
+
const graphNodeIds = /* @__PURE__ */ new Set();
|
|
3000
|
+
for (const node of this.graphNodes) {
|
|
3001
|
+
if (graphNodeIds.has(node.id)) throw new Error(`Duplicate graph node ID in snapshot: ${node.id}`);
|
|
3002
|
+
graphNodeIds.add(node.id);
|
|
3003
|
+
for (const eventId of node.sourceEventIds) {
|
|
3004
|
+
if (!eventIds.has(eventId)) throw new Error(`Graph node ${node.id} references unknown event ${eventId}`);
|
|
3005
|
+
}
|
|
3006
|
+
for (const fact of node.facts) for (const eventId of fact.sourceEventIds) {
|
|
3007
|
+
if (!eventIds.has(eventId)) throw new Error(`Graph fact ${fact.id} references unknown event ${eventId}`);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
const graphEdgeIds = /* @__PURE__ */ new Set();
|
|
3011
|
+
for (const edge of this.graphEdges) {
|
|
3012
|
+
if (graphEdgeIds.has(edge.id)) throw new Error(`Duplicate graph edge ID in snapshot: ${edge.id}`);
|
|
3013
|
+
graphEdgeIds.add(edge.id);
|
|
3014
|
+
if (!graphNodeIds.has(edge.fromNodeId) || !graphNodeIds.has(edge.toNodeId)) {
|
|
3015
|
+
throw new Error(`Graph edge ${edge.id} references an unknown node`);
|
|
3016
|
+
}
|
|
3017
|
+
for (const eventId of edge.sourceEventIds) {
|
|
3018
|
+
if (!eventIds.has(eventId)) throw new Error(`Graph edge ${edge.id} references unknown event ${eventId}`);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
for (const event of this.events) for (const nodeId of event.temporal.participantNodeIds ?? []) {
|
|
3022
|
+
if (!graphNodeIds.has(nodeId)) throw new Error(`Event ${event.id} references unknown graph node ${nodeId}`);
|
|
3023
|
+
}
|
|
3024
|
+
for (const job of this.graphProjectionJobs.values()) {
|
|
3025
|
+
for (const eventId of job.sourceEventIds) if (!eventIds.has(eventId)) {
|
|
3026
|
+
throw new Error(`Graph projection ${job.id} references unknown event ${eventId}`);
|
|
3027
|
+
}
|
|
3028
|
+
for (const nodeId of job.nodeIds) if (!graphNodeIds.has(nodeId)) {
|
|
3029
|
+
throw new Error(`Graph projection ${job.id} references unknown node ${nodeId}`);
|
|
3030
|
+
}
|
|
3031
|
+
for (const edgeId of job.edgeIds) if (!graphEdgeIds.has(edgeId)) {
|
|
3032
|
+
throw new Error(`Graph projection ${job.id} references unknown edge ${edgeId}`);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
2566
3035
|
}
|
|
2567
3036
|
};
|
|
2568
3037
|
|
|
@@ -2699,10 +3168,10 @@ var CRITICALITIES = /* @__PURE__ */ new Set(["routine", "preference", "identity"
|
|
|
2699
3168
|
function object(value) {
|
|
2700
3169
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2701
3170
|
}
|
|
2702
|
-
function
|
|
3171
|
+
function strings2(value) {
|
|
2703
3172
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2704
3173
|
}
|
|
2705
|
-
function
|
|
3174
|
+
function text2(value, fallback = "") {
|
|
2706
3175
|
return typeof value === "string" ? value.trim() : fallback;
|
|
2707
3176
|
}
|
|
2708
3177
|
function l2Neighbor(block) {
|
|
@@ -2732,7 +3201,8 @@ var RETRY_MAX_TOKENS = 1e4;
|
|
|
2732
3201
|
var STRUCTURED_FIELDS = {
|
|
2733
3202
|
summarizer: ["l0Title", "l0Tags", "l1Summary", "l2Keypoints", "shouldExtract"],
|
|
2734
3203
|
extractor: ["shouldExtract", "reason", "events"],
|
|
2735
|
-
projector: ["reason", "changes"]
|
|
3204
|
+
projector: ["reason", "changes"],
|
|
3205
|
+
graphProjector: ["reason", "nodes", "edges"]
|
|
2736
3206
|
};
|
|
2737
3207
|
var STRING_ARRAY = { type: "array", items: { type: "string" } };
|
|
2738
3208
|
var OPEN_OBJECT = { type: "object", additionalProperties: true };
|
|
@@ -2798,6 +3268,47 @@ var PROJECTOR_PARAMETERS = {
|
|
|
2798
3268
|
reason: { type: "string", required: true },
|
|
2799
3269
|
changes: { type: "array", items: ELEMENT_CHANGE, required: true }
|
|
2800
3270
|
};
|
|
3271
|
+
var GRAPH_FACT = {
|
|
3272
|
+
type: "object",
|
|
3273
|
+
additionalProperties: false,
|
|
3274
|
+
properties: { key: { type: "string", required: true }, value: { ...VALUE, required: true }, sourceEventIds: { ...STRING_ARRAY, required: true } }
|
|
3275
|
+
};
|
|
3276
|
+
var GRAPH_NODE = {
|
|
3277
|
+
type: "object",
|
|
3278
|
+
additionalProperties: false,
|
|
3279
|
+
properties: {
|
|
3280
|
+
ref: { type: "string", required: true },
|
|
3281
|
+
name: { type: "string", required: true },
|
|
3282
|
+
type: { type: "string", enum: ["person", "project", "organization", "tool", "place"], required: true },
|
|
3283
|
+
aliases: STRING_ARRAY,
|
|
3284
|
+
state: { type: "string" },
|
|
3285
|
+
facts: { type: "array", items: GRAPH_FACT },
|
|
3286
|
+
status: { type: "string", enum: ["active", "superseded", "disputed", "archived"] },
|
|
3287
|
+
validFrom: { type: "string" },
|
|
3288
|
+
validTo: { type: "string" },
|
|
3289
|
+
confidence: { type: "number" },
|
|
3290
|
+
sourceEventIds: { ...STRING_ARRAY, required: true }
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
var GRAPH_EDGE = {
|
|
3294
|
+
type: "object",
|
|
3295
|
+
additionalProperties: false,
|
|
3296
|
+
properties: {
|
|
3297
|
+
fromRef: { type: "string", required: true },
|
|
3298
|
+
toRef: { type: "string", required: true },
|
|
3299
|
+
relation: { type: "string", required: true },
|
|
3300
|
+
status: { type: "string", enum: ["active", "superseded", "disputed", "archived"] },
|
|
3301
|
+
validFrom: { type: "string" },
|
|
3302
|
+
validTo: { type: "string" },
|
|
3303
|
+
confidence: { type: "number" },
|
|
3304
|
+
sourceEventIds: { ...STRING_ARRAY, required: true }
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
var GRAPH_PROJECTOR_PARAMETERS = {
|
|
3308
|
+
reason: { type: "string", required: true },
|
|
3309
|
+
nodes: { type: "array", items: GRAPH_NODE, required: true },
|
|
3310
|
+
edges: { type: "array", items: GRAPH_EDGE, required: true }
|
|
3311
|
+
};
|
|
2801
3312
|
var STRUCTURED_TOOLS = {
|
|
2802
3313
|
summarizer: {
|
|
2803
3314
|
name: "stratagate_summarize_block",
|
|
@@ -2813,6 +3324,11 @@ var STRUCTURED_TOOLS = {
|
|
|
2813
3324
|
name: "stratagate_project_element_cards",
|
|
2814
3325
|
description: "Submit element-card changes supported by the supplied event cards.",
|
|
2815
3326
|
parameters: PROJECTOR_PARAMETERS
|
|
3327
|
+
},
|
|
3328
|
+
graphProjector: {
|
|
3329
|
+
name: "stratagate_project_knowledge_graph",
|
|
3330
|
+
description: "Project stable graph nodes and directed edges from supplied event evidence.",
|
|
3331
|
+
parameters: GRAPH_PROJECTOR_PARAMETERS
|
|
2816
3332
|
}
|
|
2817
3333
|
};
|
|
2818
3334
|
function toolSchema(kind) {
|
|
@@ -2850,10 +3366,10 @@ var DshModelBridge = class {
|
|
|
2850
3366
|
{ messages }
|
|
2851
3367
|
));
|
|
2852
3368
|
return {
|
|
2853
|
-
l0Title:
|
|
2854
|
-
l0Tags:
|
|
2855
|
-
l1Summary:
|
|
2856
|
-
l2Keypoints:
|
|
3369
|
+
l0Title: text2(raw.l0Title, "Conversation block").slice(0, 120),
|
|
3370
|
+
l0Tags: strings2(raw.l0Tags).slice(0, 12),
|
|
3371
|
+
l1Summary: text2(raw.l1Summary).slice(0, 2e3),
|
|
3372
|
+
l2Keypoints: strings2(raw.l2Keypoints).slice(0, 20),
|
|
2857
3373
|
shouldExtract: raw.shouldExtract === true
|
|
2858
3374
|
};
|
|
2859
3375
|
};
|
|
@@ -2861,21 +3377,21 @@ var DshModelBridge = class {
|
|
|
2861
3377
|
const validMessageIds = new Set(context.target.l5Raw.map((message) => message.id));
|
|
2862
3378
|
const raw = object(await this.callStructured(
|
|
2863
3379
|
"extractor",
|
|
2864
|
-
`Extract only durable, evidence-backed events from target.l5Raw, then call ${STRUCTURED_TOOLS.extractor.name} exactly once. The target block is the only legal source of new facts, quotations, and sourceMessageIds. neighbors.previous and neighbors.next are context-only L2 summaries; never extract from them. Every sourceMessageIds entry must exactly match allowedSourceMessageIds. If a fact appears only in a neighbor, do not extract it in this call. Events must be understandable later without the original chat. Use project scope for repository decisions, user scope for stable preferences/identity, and session scope for temporary task state. Use ISO-8601 timestamps with the explicit +08:00 offset in temporal fields. Do not turn an assistant statement that merely recalls older memory into a new event; require new human input or a new observable task/tool outcome from target.l5Raw. Do not return the result as text.`,
|
|
3380
|
+
`Extract only durable, evidence-backed events from target.l5Raw, then call ${STRUCTURED_TOOLS.extractor.name} exactly once. The target block is the only legal source of new facts, quotations, and sourceMessageIds. neighbors.previous and neighbors.next are context-only L2 summaries; never extract from them. Every sourceMessageIds entry must exactly match allowedSourceMessageIds. If a fact appears only in a neighbor, do not extract it in this call. Events must be understandable later without the original chat. Use project scope for repository decisions, user scope for stable preferences/identity, and session scope for temporary task state. temporal.eventType must use exactly one stable value: decision, release, task_completed, plan, change, cancellation, incident, meeting, collaboration, migration, or other. temporal.participants contains canonical entity names. Use ISO-8601 timestamps with the explicit +08:00 offset in temporal fields. Keep happened time separate from mentionedAt; when happened time is unknown omit it and set precision/basis to unknown. Do not turn an assistant statement that merely recalls older memory into a new event; require new human input or a new observable task/tool outcome from target.l5Raw. Do not return the result as text.`,
|
|
2865
3381
|
extractorPayload(context)
|
|
2866
3382
|
));
|
|
2867
3383
|
const events = (Array.isArray(raw.events) ? raw.events : []).map((candidate) => {
|
|
2868
3384
|
const item = object(candidate);
|
|
2869
|
-
const sourceMessageIds =
|
|
3385
|
+
const sourceMessageIds = strings2(item.sourceMessageIds).filter((id) => validMessageIds.has(id));
|
|
2870
3386
|
const scope = SCOPES.has(item.scope) ? item.scope : "project";
|
|
2871
3387
|
const criticality = CRITICALITIES.has(item.criticality) ? item.criticality : "routine";
|
|
2872
|
-
if (!
|
|
3388
|
+
if (!text2(item.title) || !text2(item.summary) || sourceMessageIds.length === 0) return null;
|
|
2873
3389
|
return {
|
|
2874
|
-
title:
|
|
2875
|
-
summary:
|
|
2876
|
-
narrative:
|
|
2877
|
-
tags:
|
|
2878
|
-
quotes:
|
|
3390
|
+
title: text2(item.title).slice(0, 200),
|
|
3391
|
+
summary: text2(item.summary).slice(0, 1e3),
|
|
3392
|
+
narrative: text2(item.narrative),
|
|
3393
|
+
tags: strings2(item.tags).slice(0, 16),
|
|
3394
|
+
quotes: strings2(item.quotes).slice(0, 12),
|
|
2879
3395
|
sourceMessageIds,
|
|
2880
3396
|
sourceBlockId: context.target.id,
|
|
2881
3397
|
temporal: object(item.temporal),
|
|
@@ -2886,7 +3402,7 @@ var DshModelBridge = class {
|
|
|
2886
3402
|
}).filter((event) => event !== null);
|
|
2887
3403
|
return {
|
|
2888
3404
|
shouldExtract: raw.shouldExtract === true,
|
|
2889
|
-
reason:
|
|
3405
|
+
reason: text2(raw.reason, events.length ? "Durable evidence extracted." : "No durable evidence."),
|
|
2890
3406
|
events
|
|
2891
3407
|
};
|
|
2892
3408
|
};
|
|
@@ -2901,27 +3417,82 @@ var DshModelBridge = class {
|
|
|
2901
3417
|
const item = object(candidate);
|
|
2902
3418
|
const element = object(item.element);
|
|
2903
3419
|
const type = element.type;
|
|
2904
|
-
const sourceEventIds =
|
|
3420
|
+
const sourceEventIds = strings2(item.sourceEventIds).filter((id) => eventIds.has(id));
|
|
2905
3421
|
const operation = item.operation;
|
|
2906
3422
|
const mode = item.mode;
|
|
2907
3423
|
const value = item.value;
|
|
2908
|
-
if (!
|
|
3424
|
+
if (!text2(element.name) || !ELEMENT_TYPES2.has(type) || sourceEventIds.length === 0) return [];
|
|
2909
3425
|
if (!["set_state", "add_set_item", "set_relation"].includes(String(operation))) return [];
|
|
2910
3426
|
if (!["state", "set", "relation"].includes(String(mode))) return [];
|
|
2911
3427
|
if (!(typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string"))) return [];
|
|
2912
3428
|
return [{
|
|
2913
|
-
element: { name:
|
|
3429
|
+
element: { name: text2(element.name), type, aliases: strings2(element.aliases) },
|
|
2914
3430
|
operation,
|
|
2915
|
-
key:
|
|
3431
|
+
key: text2(item.key, "state"),
|
|
2916
3432
|
mode,
|
|
2917
3433
|
value,
|
|
2918
|
-
...
|
|
2919
|
-
...
|
|
3434
|
+
...text2(item.validFrom) ? { validFrom: text2(item.validFrom) } : {},
|
|
3435
|
+
...text2(item.validTo) ? { validTo: text2(item.validTo) } : {},
|
|
2920
3436
|
sourceEventIds,
|
|
2921
3437
|
...typeof item.confidence === "number" ? { confidence: item.confidence } : {}
|
|
2922
3438
|
}];
|
|
2923
3439
|
});
|
|
2924
|
-
return { reason:
|
|
3440
|
+
return { reason: text2(raw.reason, "Projected event evidence."), changes };
|
|
3441
|
+
};
|
|
3442
|
+
graphProjector = async (context) => {
|
|
3443
|
+
const eventIds = new Set(context.events.map((event) => event.id));
|
|
3444
|
+
const raw = object(await this.callStructured(
|
|
3445
|
+
"graphProjector",
|
|
3446
|
+
`Project the supplied Events into the current Knowledge Graph, then call ${STRUCTURED_TOOLS.graphProjector.name} exactly once. Events are the sole source of truth; never use legacy Element data. Return stable entity nodes for people, projects, organizations, tools, and places. Use aliases to merge spelling/case/separator variants. Put attributes in node facts and every relationship in a directed edge using fromRef/toRef\u2014never encode a relationship as a fact string. Prefer concise canonical Chinese relation labels such as \u4F7F\u7528\u3001\u5C5E\u4E8E\u3001\u521B\u5EFA\u3001\u53C2\u4E0E\u3001\u8D21\u732E\u3001\u4F9D\u8D56\u3001\u4F4D\u4E8E\u3001\u76F8\u5173. Every node, fact, and edge must cite only supplied Event ids. Do not return text.`,
|
|
3447
|
+
context
|
|
3448
|
+
));
|
|
3449
|
+
const nodes = (Array.isArray(raw.nodes) ? raw.nodes : []).flatMap((candidate) => {
|
|
3450
|
+
const item = object(candidate);
|
|
3451
|
+
const sourceEventIds = strings2(item.sourceEventIds).filter((id) => eventIds.has(id));
|
|
3452
|
+
const type = item.type;
|
|
3453
|
+
if (!text2(item.ref) || !text2(item.name) || !ELEMENT_TYPES2.has(type) || sourceEventIds.length === 0) return [];
|
|
3454
|
+
const facts = (Array.isArray(item.facts) ? item.facts : []).flatMap((candidateFact) => {
|
|
3455
|
+
const fact = object(candidateFact);
|
|
3456
|
+
const value = fact.value;
|
|
3457
|
+
if (!text2(fact.key) || !(typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string"))) return [];
|
|
3458
|
+
const sourceEventIds2 = strings2(fact.sourceEventIds).filter((id) => eventIds.has(id));
|
|
3459
|
+
if (sourceEventIds2.length === 0) return [];
|
|
3460
|
+
return [{ key: text2(fact.key), value, sourceEventIds: sourceEventIds2 }];
|
|
3461
|
+
});
|
|
3462
|
+
return [{
|
|
3463
|
+
ref: text2(item.ref),
|
|
3464
|
+
name: text2(item.name),
|
|
3465
|
+
type,
|
|
3466
|
+
aliases: strings2(item.aliases),
|
|
3467
|
+
...text2(item.state) ? { state: text2(item.state) } : {},
|
|
3468
|
+
facts,
|
|
3469
|
+
...typeof item.status === "string" ? { status: item.status } : {},
|
|
3470
|
+
...text2(item.validFrom) ? { validFrom: text2(item.validFrom) } : {},
|
|
3471
|
+
...text2(item.validTo) ? { validTo: text2(item.validTo) } : {},
|
|
3472
|
+
...typeof item.confidence === "number" ? { confidence: item.confidence } : {},
|
|
3473
|
+
sourceEventIds
|
|
3474
|
+
}];
|
|
3475
|
+
});
|
|
3476
|
+
const refs = new Set(nodes.map(({ ref }) => ref));
|
|
3477
|
+
const edges = (Array.isArray(raw.edges) ? raw.edges : []).flatMap((candidate) => {
|
|
3478
|
+
const item = object(candidate);
|
|
3479
|
+
const sourceEventIds = strings2(item.sourceEventIds).filter((id) => eventIds.has(id));
|
|
3480
|
+
const fromRef = text2(item.fromRef);
|
|
3481
|
+
const toRef = text2(item.toRef);
|
|
3482
|
+
const relation = text2(item.relation);
|
|
3483
|
+
if (!refs.has(fromRef) || !refs.has(toRef) || !relation || sourceEventIds.length === 0) return [];
|
|
3484
|
+
return [{
|
|
3485
|
+
fromRef,
|
|
3486
|
+
toRef,
|
|
3487
|
+
relation,
|
|
3488
|
+
...typeof item.status === "string" ? { status: item.status } : {},
|
|
3489
|
+
...text2(item.validFrom) ? { validFrom: text2(item.validFrom) } : {},
|
|
3490
|
+
...text2(item.validTo) ? { validTo: text2(item.validTo) } : {},
|
|
3491
|
+
...typeof item.confidence === "number" ? { confidence: item.confidence } : {},
|
|
3492
|
+
sourceEventIds
|
|
3493
|
+
}];
|
|
3494
|
+
});
|
|
3495
|
+
return { reason: text2(raw.reason, "Projected Event evidence into the Knowledge Graph."), nodes, edges };
|
|
2925
3496
|
};
|
|
2926
3497
|
async callStructured(kind, system, payload) {
|
|
2927
3498
|
const session = this.sessions.getStore();
|
|
@@ -3044,6 +3615,7 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
3044
3615
|
import { createHash } from "node:crypto";
|
|
3045
3616
|
import { existsSync } from "node:fs";
|
|
3046
3617
|
import { resolve } from "node:path";
|
|
3618
|
+
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
3047
3619
|
|
|
3048
3620
|
// src/fold.ts
|
|
3049
3621
|
function renderBlocks(blocks) {
|
|
@@ -3097,13 +3669,13 @@ var TurnFolder = class {
|
|
|
3097
3669
|
if (event.data.source.kind !== "user") return null;
|
|
3098
3670
|
const turn = this.activeTurn.get(sessionId);
|
|
3099
3671
|
if (turn === void 0) return null;
|
|
3100
|
-
const
|
|
3101
|
-
if (
|
|
3672
|
+
const text3 = renderBlocks(event.data.content);
|
|
3673
|
+
if (text3) this.pending(sessionId, turn).user.push(text3);
|
|
3102
3674
|
return null;
|
|
3103
3675
|
}
|
|
3104
3676
|
case "assistant/message": {
|
|
3105
|
-
const
|
|
3106
|
-
if (
|
|
3677
|
+
const text3 = renderBlocks(event.data.message.content);
|
|
3678
|
+
if (text3) this.pending(sessionId, event.data.turn).assistant.push(text3);
|
|
3107
3679
|
return null;
|
|
3108
3680
|
}
|
|
3109
3681
|
case "tool/call": {
|
|
@@ -3139,7 +3711,8 @@ var TurnFolder = class {
|
|
|
3139
3711
|
threadId: sessionId,
|
|
3140
3712
|
assistantToolCalls: [...pending.tools.values()],
|
|
3141
3713
|
createdAt: toUtc8Iso(event.time),
|
|
3142
|
-
receiptId: `dsh:${sessionId}:turn:${event.data.turn}
|
|
3714
|
+
receiptId: `dsh:${sessionId}:turn:${event.data.turn}`,
|
|
3715
|
+
dshTurn: event.data.turn
|
|
3143
3716
|
};
|
|
3144
3717
|
}
|
|
3145
3718
|
default:
|
|
@@ -3219,6 +3792,7 @@ var DshMetadataStore = class {
|
|
|
3219
3792
|
var AUTO_EVENT_LIMIT = 4;
|
|
3220
3793
|
var AUTO_ELEMENT_LIMIT = 4;
|
|
3221
3794
|
var AUTO_MEMORY_TOKEN_BUDGET = 900;
|
|
3795
|
+
var COMPACTION_SOURCE_PLUGIN = "stratagate-memory";
|
|
3222
3796
|
function projectKey(cwd) {
|
|
3223
3797
|
const canonical = resolve(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
|
|
3224
3798
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
@@ -3229,21 +3803,25 @@ function workspaceDisplayName(cwd) {
|
|
|
3229
3803
|
}
|
|
3230
3804
|
var StrataGateRuntime = class {
|
|
3231
3805
|
constructor(config, models, onIngestError = () => {
|
|
3806
|
+
}, flushNativeSession = async () => {
|
|
3232
3807
|
}) {
|
|
3233
3808
|
this.config = config;
|
|
3234
3809
|
this.models = models;
|
|
3235
3810
|
this.onIngestError = onIngestError;
|
|
3811
|
+
this.flushNativeSession = flushNativeSession;
|
|
3236
3812
|
this.blockDecayLambda = config.blockDecayLambda;
|
|
3237
3813
|
}
|
|
3238
3814
|
config;
|
|
3239
3815
|
models;
|
|
3240
3816
|
onIngestError;
|
|
3817
|
+
flushNativeSession;
|
|
3241
3818
|
folder = new TurnFolder();
|
|
3242
3819
|
spaces = /* @__PURE__ */ new Map();
|
|
3243
3820
|
batches = /* @__PURE__ */ new Map();
|
|
3244
3821
|
adopted = /* @__PURE__ */ new Map();
|
|
3245
3822
|
pendingUse = /* @__PURE__ */ new Set();
|
|
3246
3823
|
workspaceNames = /* @__PURE__ */ new Map();
|
|
3824
|
+
migrationTimers = /* @__PURE__ */ new Map();
|
|
3247
3825
|
ingestTail = Promise.resolve();
|
|
3248
3826
|
settingsTail = Promise.resolve();
|
|
3249
3827
|
batchSequence = 0;
|
|
@@ -3259,7 +3837,15 @@ var StrataGateRuntime = class {
|
|
|
3259
3837
|
}).then(async () => {
|
|
3260
3838
|
const memory = await this.space(session);
|
|
3261
3839
|
try {
|
|
3262
|
-
await this.models.run(session, () => memory.appendTurn(turn));
|
|
3840
|
+
const result = await this.models.run(session, () => memory.appendTurn(turn));
|
|
3841
|
+
if (result.sealedBlock) {
|
|
3842
|
+
const contexts = memory.getBlockContext(String(session.id));
|
|
3843
|
+
const sealedContext = contexts.find(({ id }) => id === result.sealedBlock.id);
|
|
3844
|
+
if (!sealedContext) throw new Error(`Missing context for sealed StrataGate block ${result.sealedBlock.id}`);
|
|
3845
|
+
this.replaceSealedSurface(session, result.sealedBlock, sealedContext, turn.dshTurn);
|
|
3846
|
+
this.syncDecayedBlockSurface(session, contexts);
|
|
3847
|
+
await this.flushNativeSession(session);
|
|
3848
|
+
}
|
|
3263
3849
|
} finally {
|
|
3264
3850
|
await this.persistSuccessfulResponses(memory);
|
|
3265
3851
|
}
|
|
@@ -3302,7 +3888,7 @@ var StrataGateRuntime = class {
|
|
|
3302
3888
|
}
|
|
3303
3889
|
async expandBlock(session, id, target) {
|
|
3304
3890
|
await this.flush();
|
|
3305
|
-
const result = await (await this.space(session)).expandBlock(id, target);
|
|
3891
|
+
const result = await (await this.space(session)).expandBlock(id, target, "agent");
|
|
3306
3892
|
return this.batch(session, [{
|
|
3307
3893
|
ref: `block:${result.id}:level:${result.level}`,
|
|
3308
3894
|
target: { eventIds: [], elementIds: [] }
|
|
@@ -3417,23 +4003,78 @@ var StrataGateRuntime = class {
|
|
|
3417
4003
|
await this.flush();
|
|
3418
4004
|
const memory = await this.space(session);
|
|
3419
4005
|
const threadId = String(session.id);
|
|
4006
|
+
const blockContexts = memory.getBlockContext(threadId);
|
|
4007
|
+
if (this.syncDecayedBlockSurface(session, blockContexts)) {
|
|
4008
|
+
await this.flushNativeSession(session);
|
|
4009
|
+
}
|
|
3420
4010
|
const openTail = memory.listOpenTail(threadId);
|
|
3421
4011
|
const activationQuery = [currentUserMessage(session), renderMessages(recentTurns(openTail, 2))].filter(Boolean).join("\n\n");
|
|
3422
|
-
const
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
4012
|
+
const eventHits = activationQuery ? await memory.searchEvents(activationQuery, { limit: 20 }) : [];
|
|
4013
|
+
let graphNodes = [];
|
|
4014
|
+
if (activationQuery && typeof memory.searchGraphNodes === "function") {
|
|
4015
|
+
graphNodes = (await memory.searchGraphNodes(activationQuery, 12)).map(({ node }) => node);
|
|
4016
|
+
} else if (activationQuery) {
|
|
4017
|
+
const legacy = activatedElements(memory, await memory.searchElements(activationQuery, { limit: 12 }));
|
|
4018
|
+
graphNodes = legacy.map((item) => ({
|
|
4019
|
+
id: item.elementId,
|
|
4020
|
+
name: item.name,
|
|
4021
|
+
type: item.type,
|
|
4022
|
+
aliases: [],
|
|
4023
|
+
currentState: "",
|
|
4024
|
+
status: "active",
|
|
4025
|
+
confidence: item.fact.confidence ?? 0.8,
|
|
4026
|
+
sourceEventIds: item.fact.sourceEventIds,
|
|
4027
|
+
facts: [{ ...item.fact, confidence: item.fact.confidence ?? 0.8, status: item.fact.status === "disputed" ? "disputed" : item.fact.status === "superseded" ? "superseded" : "active" }],
|
|
4028
|
+
createdAt: item.fact.createdAt,
|
|
4029
|
+
updatedAt: item.fact.updatedAt
|
|
4030
|
+
}));
|
|
4031
|
+
}
|
|
4032
|
+
const events = activatedEvents(memory, eventHits);
|
|
4033
|
+
const currentBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId).map(({ id }) => id));
|
|
4034
|
+
const currentEventIds = new Set(memory.listEvents().filter((event) => currentBlockIds.has(event.sourceBlockId)).map(({ id }) => id));
|
|
4035
|
+
const longTermEvents = events.filter((event) => !currentEventIds.has(event.id)).slice(0, AUTO_EVENT_LIMIT);
|
|
4036
|
+
graphNodes = graphNodes.filter((node) => !node.sourceEventIds.some((id) => currentEventIds.has(id))).slice(0, AUTO_ELEMENT_LIMIT);
|
|
4037
|
+
return renderActivatedMemory(longTermEvents, graphNodes);
|
|
4038
|
+
}
|
|
4039
|
+
/**
|
|
4040
|
+
* Replace the just-sealed DSH turns with one native surface message. The raw
|
|
4041
|
+
* events remain in the append-only log for transcript/evidence provenance,
|
|
4042
|
+
* while deriveMessages() sees only this compressed checkpoint.
|
|
4043
|
+
*/
|
|
4044
|
+
replaceSealedSurface(session, block, context, endTurn) {
|
|
4045
|
+
const sourceEventSeqs = sealedSurfaceSeqs(session, endTurn, block.endTurn - block.startTurn + 1);
|
|
4046
|
+
const start = sourceEventSeqs[0];
|
|
4047
|
+
const end = sourceEventSeqs.at(-1);
|
|
4048
|
+
if (start === void 0 || end === void 0) {
|
|
4049
|
+
throw new Error(`Cannot compact StrataGate block ${block.id}: no DSH surface range was found`);
|
|
4050
|
+
}
|
|
4051
|
+
session.append("user/message", createUserMessage2({
|
|
4052
|
+
content: [{ type: "text", text: renderBlockSurfaceMessage(context) }],
|
|
4053
|
+
source: { kind: "plugin", plugin: COMPACTION_SOURCE_PLUGIN }
|
|
4054
|
+
}), {
|
|
4055
|
+
surfaceOp: { op: "replace", start, end },
|
|
4056
|
+
sourceEventSeqs
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
/** Keep each native Block checkpoint synchronized with its current decay pointer. */
|
|
4060
|
+
syncDecayedBlockSurface(session, contexts) {
|
|
4061
|
+
const current = currentBlockSurfaceMessages(session);
|
|
4062
|
+
let changed = false;
|
|
4063
|
+
for (const context of contexts) {
|
|
4064
|
+
const node = current.get(context.id);
|
|
4065
|
+
if (!node) continue;
|
|
4066
|
+
const text3 = renderBlockSurfaceMessage(context);
|
|
4067
|
+
if (node.text === text3) continue;
|
|
4068
|
+
session.append("user/message", createUserMessage2({
|
|
4069
|
+
content: [{ type: "text", text: text3 }],
|
|
4070
|
+
source: { kind: "plugin", plugin: COMPACTION_SOURCE_PLUGIN }
|
|
4071
|
+
}), {
|
|
4072
|
+
surfaceOp: { op: "replace", start: node.seq, end: node.seq },
|
|
4073
|
+
sourceEventSeqs: [node.seq]
|
|
4074
|
+
});
|
|
4075
|
+
changed = true;
|
|
4076
|
+
}
|
|
4077
|
+
return changed;
|
|
3437
4078
|
}
|
|
3438
4079
|
// Keep the ingestion error for callers that explicitly require a flushed run.
|
|
3439
4080
|
async settleIngestion() {
|
|
@@ -3445,6 +4086,8 @@ var StrataGateRuntime = class {
|
|
|
3445
4086
|
async close() {
|
|
3446
4087
|
if (this.closed) return;
|
|
3447
4088
|
this.closed = true;
|
|
4089
|
+
for (const timer of this.migrationTimers.values()) clearTimeout(timer);
|
|
4090
|
+
this.migrationTimers.clear();
|
|
3448
4091
|
let flushError;
|
|
3449
4092
|
try {
|
|
3450
4093
|
await this.flush();
|
|
@@ -3527,6 +4170,38 @@ var StrataGateRuntime = class {
|
|
|
3527
4170
|
await update;
|
|
3528
4171
|
return value;
|
|
3529
4172
|
}
|
|
4173
|
+
async adminExpandBlock(namespace, id, target) {
|
|
4174
|
+
const key = namespace.trim();
|
|
4175
|
+
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
4176
|
+
const update = this.settingsTail.catch(() => {
|
|
4177
|
+
}).then(async () => {
|
|
4178
|
+
await this.flush();
|
|
4179
|
+
const active = this.spaces.get(key);
|
|
4180
|
+
if (active) return (await active).expandBlock(id, target, "user");
|
|
4181
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
|
|
4182
|
+
throw new Error(`Unknown StrataGate namespace: ${key}`);
|
|
4183
|
+
}
|
|
4184
|
+
const memory = await StrataGate.open({
|
|
4185
|
+
database: this.config.database,
|
|
4186
|
+
namespace: key,
|
|
4187
|
+
blockTurnSize: this.config.blockTurnSize,
|
|
4188
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
4189
|
+
summarizer: this.models.summarizer,
|
|
4190
|
+
extractor: this.models.extractor,
|
|
4191
|
+
graphProjector: this.models.graphProjector,
|
|
4192
|
+
disableElementProjection: true
|
|
4193
|
+
});
|
|
4194
|
+
try {
|
|
4195
|
+
return await memory.expandBlock(id, target, "user");
|
|
4196
|
+
} finally {
|
|
4197
|
+
await memory.close();
|
|
4198
|
+
}
|
|
4199
|
+
});
|
|
4200
|
+
this.settingsTail = update.then(() => {
|
|
4201
|
+
}, () => {
|
|
4202
|
+
});
|
|
4203
|
+
return update;
|
|
4204
|
+
}
|
|
3530
4205
|
async applyBlockDecayLambda(value) {
|
|
3531
4206
|
await this.flush();
|
|
3532
4207
|
this.blockDecayLambda = value;
|
|
@@ -3570,14 +4245,28 @@ var StrataGateRuntime = class {
|
|
|
3570
4245
|
blockDecayLambda: this.blockDecayLambda,
|
|
3571
4246
|
summarizer: this.models.summarizer,
|
|
3572
4247
|
extractor: this.models.extractor,
|
|
3573
|
-
|
|
4248
|
+
graphProjector: this.models.graphProjector,
|
|
4249
|
+
disableElementProjection: true
|
|
3574
4250
|
}).then(async (memory) => {
|
|
3575
4251
|
try {
|
|
3576
4252
|
try {
|
|
3577
|
-
await this.models.run(session, () => memory.resumePendingWork({ retrySkipped: true }));
|
|
4253
|
+
const resumed = await this.models.run(session, () => memory.resumePendingWork({ retrySkipped: true }));
|
|
4254
|
+
const contexts = memory.getBlockContext(String(session.id));
|
|
4255
|
+
for (const block of resumed.sealedBlocks) {
|
|
4256
|
+
if (block.threadId !== String(session.id)) continue;
|
|
4257
|
+
const endTurn = dshTurnAtBlockEnd(session, block);
|
|
4258
|
+
const context = contexts.find(({ id }) => id === block.id);
|
|
4259
|
+
if (!context) throw new Error(`Missing context for recovered StrataGate block ${block.id}`);
|
|
4260
|
+
this.replaceSealedSurface(session, block, context, endTurn);
|
|
4261
|
+
}
|
|
4262
|
+
this.syncDecayedBlockSurface(session, contexts);
|
|
4263
|
+
if (resumed.sealedBlocks.some((block) => block.threadId === String(session.id))) {
|
|
4264
|
+
await this.flushNativeSession(session);
|
|
4265
|
+
}
|
|
3578
4266
|
} finally {
|
|
3579
4267
|
await this.persistSuccessfulResponses(memory);
|
|
3580
4268
|
}
|
|
4269
|
+
this.scheduleGraphMigration(session, memory);
|
|
3581
4270
|
return memory;
|
|
3582
4271
|
} catch (error) {
|
|
3583
4272
|
await memory.close().catch(() => {
|
|
@@ -3592,6 +4281,46 @@ var StrataGateRuntime = class {
|
|
|
3592
4281
|
}
|
|
3593
4282
|
return opening;
|
|
3594
4283
|
}
|
|
4284
|
+
async searchGraph(session, query, limit = 8) {
|
|
4285
|
+
await this.flush();
|
|
4286
|
+
const results = await (await this.space(session)).searchGraphNodes(query, limit);
|
|
4287
|
+
return this.batch(session, results.map(({ node }) => ({
|
|
4288
|
+
ref: `graph-node:${node.id}`,
|
|
4289
|
+
target: { eventIds: node.sourceEventIds, elementIds: [] }
|
|
4290
|
+
})), results);
|
|
4291
|
+
}
|
|
4292
|
+
async expandGraphNode(session, id) {
|
|
4293
|
+
await this.flush();
|
|
4294
|
+
const memory = await this.space(session);
|
|
4295
|
+
const node = memory.listGraphNodes().find((candidate) => candidate.id === id);
|
|
4296
|
+
if (!node) throw new Error(`Unknown graph node: ${id}`);
|
|
4297
|
+
const edges = memory.listGraphEdges().filter(({ fromNodeId, toNodeId }) => fromNodeId === id || toNodeId === id);
|
|
4298
|
+
return this.batch(session, [{
|
|
4299
|
+
ref: `graph-node:${node.id}:expanded`,
|
|
4300
|
+
target: { eventIds: [.../* @__PURE__ */ new Set([...node.sourceEventIds, ...edges.flatMap(({ sourceEventIds }) => sourceEventIds)])], elementIds: [] }
|
|
4301
|
+
}], { node, edges });
|
|
4302
|
+
}
|
|
4303
|
+
scheduleGraphMigration(session, memory) {
|
|
4304
|
+
const namespace = this.namespaceFor(session);
|
|
4305
|
+
if (this.closed || this.migrationTimers.has(namespace)) return;
|
|
4306
|
+
if (typeof memory.listGraphProjectionJobs !== "function") return;
|
|
4307
|
+
const pending = memory.listGraphProjectionJobs().some(({ status }) => status === "pending" || status === "failed");
|
|
4308
|
+
if (!pending) return;
|
|
4309
|
+
const timer = setTimeout(() => {
|
|
4310
|
+
this.migrationTimers.delete(namespace);
|
|
4311
|
+
if (this.closed) return;
|
|
4312
|
+
const completedBefore = memory.listGraphProjectionJobs().filter(({ status }) => status === "completed").length;
|
|
4313
|
+
void this.models.run(session, () => memory.resumePendingWork()).then(async () => {
|
|
4314
|
+
await this.persistSuccessfulResponses(memory);
|
|
4315
|
+
const completedAfter = memory.listGraphProjectionJobs().filter(({ status }) => status === "completed").length;
|
|
4316
|
+
if (completedAfter > completedBefore) this.scheduleGraphMigration(session, memory);
|
|
4317
|
+
}).catch((error) => {
|
|
4318
|
+
this.onIngestError(error);
|
|
4319
|
+
});
|
|
4320
|
+
}, 1500);
|
|
4321
|
+
timer.unref?.();
|
|
4322
|
+
this.migrationTimers.set(namespace, timer);
|
|
4323
|
+
}
|
|
3595
4324
|
rememberWorkspace(namespace, cwd) {
|
|
3596
4325
|
const name2 = workspaceDisplayName(cwd);
|
|
3597
4326
|
this.workspaceNames.set(namespace, name2);
|
|
@@ -3660,12 +4389,75 @@ function renderMessages(messages) {
|
|
|
3660
4389
|
return details.join("\n");
|
|
3661
4390
|
}).join("\n\n");
|
|
3662
4391
|
}
|
|
3663
|
-
function
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
4392
|
+
function dshTurnAtBlockEnd(session, block) {
|
|
4393
|
+
const blockEnd = Date.parse(block.createdAt);
|
|
4394
|
+
for (let index = session.events.length - 1; index >= 0; index -= 1) {
|
|
4395
|
+
const event = session.events[index];
|
|
4396
|
+
if (event?.type === "turn/end" && event.time === blockEnd) return event.data.turn;
|
|
4397
|
+
}
|
|
4398
|
+
throw new Error(`Cannot match StrataGate block ${block.id} to its completed DSH turn`);
|
|
4399
|
+
}
|
|
4400
|
+
function sealedSurfaceSeqs(session, endTurn, turnCount) {
|
|
4401
|
+
const currentSurface = [...session.surface.nodes];
|
|
4402
|
+
const currentSet = new Set(currentSurface);
|
|
4403
|
+
const completed = [];
|
|
4404
|
+
let open;
|
|
4405
|
+
for (const event of session.events) {
|
|
4406
|
+
if (event.type === "turn/start") {
|
|
4407
|
+
open = { turn: event.data.turn, start: event.seq };
|
|
4408
|
+
continue;
|
|
4409
|
+
}
|
|
4410
|
+
if (event.type !== "turn/end" || !open || event.data.turn !== open.turn) continue;
|
|
4411
|
+
const turnEvents = session.events.slice(open.start + 1, event.seq);
|
|
4412
|
+
const hasHumanMessage = turnEvents.some((candidate) => candidate.type === "user/message" && candidate.data.source.kind === "user");
|
|
4413
|
+
if (hasHumanMessage && event.data.turn <= endTurn) {
|
|
4414
|
+
completed.push({
|
|
4415
|
+
turn: event.data.turn,
|
|
4416
|
+
start: open.start,
|
|
4417
|
+
end: event.seq,
|
|
4418
|
+
nodes: currentSurface.filter((seq) => seq > open.start && seq < event.seq && currentSet.has(seq))
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
open = void 0;
|
|
4422
|
+
}
|
|
4423
|
+
const selected = completed.slice(-turnCount);
|
|
4424
|
+
if (selected.length !== turnCount || selected.at(-1)?.turn !== endTurn) {
|
|
4425
|
+
throw new Error(`Cannot identify ${turnCount} completed DSH turns ending at turn ${endTurn}`);
|
|
4426
|
+
}
|
|
4427
|
+
const emptyTurn = selected.find((turn) => turn.nodes.length === 0);
|
|
4428
|
+
if (emptyTurn) {
|
|
4429
|
+
throw new Error(`Cannot compact DSH turn ${emptyTurn.turn}: its original messages are no longer on the surface`);
|
|
4430
|
+
}
|
|
4431
|
+
const start = selected[0].nodes[0];
|
|
4432
|
+
const end = selected.at(-1).nodes.at(-1);
|
|
4433
|
+
const startIndex = currentSurface.indexOf(start);
|
|
4434
|
+
const endIndex = currentSurface.indexOf(end);
|
|
4435
|
+
if (startIndex < 0 || endIndex < startIndex) {
|
|
4436
|
+
throw new Error(`Cannot identify a contiguous DSH surface range ending at turn ${endTurn}`);
|
|
4437
|
+
}
|
|
4438
|
+
return currentSurface.slice(startIndex, endIndex + 1);
|
|
4439
|
+
}
|
|
4440
|
+
function renderBlockSurfaceMessage(context) {
|
|
4441
|
+
return [
|
|
4442
|
+
"[StrataGate conversation block]",
|
|
4443
|
+
`Block: ${context.id}`,
|
|
4444
|
+
`Turns: ${context.turnRange[0]}-${context.turnRange[1]}`,
|
|
4445
|
+
`Level: L${context.level} (${context.label})`,
|
|
4446
|
+
"",
|
|
4447
|
+
context.content
|
|
4448
|
+
].join("\n");
|
|
4449
|
+
}
|
|
4450
|
+
function currentBlockSurfaceMessages(session) {
|
|
4451
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
4452
|
+
if (!session.surface?.nodes) return blocks;
|
|
4453
|
+
for (const seq of session.surface.nodes) {
|
|
4454
|
+
const event = session.events[seq];
|
|
4455
|
+
if (event?.type !== "user/message" || event.data.source.kind !== "plugin" || event.data.source.plugin !== COMPACTION_SOURCE_PLUGIN) continue;
|
|
4456
|
+
const text3 = event.data.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
4457
|
+
const blockId = text3.match(/^\[StrataGate conversation block\]\nBlock: ([^\n]+)/u)?.[1] ?? text3.match(/^\[StrataGate compressed conversation\]\nBlock ([^;\n]+);/u)?.[1];
|
|
4458
|
+
if (blockId) blocks.set(blockId, { seq, text: text3 });
|
|
4459
|
+
}
|
|
4460
|
+
return blocks;
|
|
3669
4461
|
}
|
|
3670
4462
|
function activatedEvents(memory, relevance) {
|
|
3671
4463
|
const allowed = new Map(relevance.map(({ event }) => [event.id, event]));
|
|
@@ -3709,7 +4501,7 @@ function activatedElements(memory, relevance) {
|
|
|
3709
4501
|
weight
|
|
3710
4502
|
]).map(({ item }) => item);
|
|
3711
4503
|
}
|
|
3712
|
-
function renderActivatedMemory(events,
|
|
4504
|
+
function renderActivatedMemory(events, graphNodes) {
|
|
3713
4505
|
const heading = [
|
|
3714
4506
|
"[Activated long-term memory]",
|
|
3715
4507
|
"Historical memory context.",
|
|
@@ -3719,7 +4511,7 @@ function renderActivatedMemory(events, elements) {
|
|
|
3719
4511
|
const lines = [...heading];
|
|
3720
4512
|
let tokens = estimateTokens(lines.join("\n"));
|
|
3721
4513
|
let eventCount = 0;
|
|
3722
|
-
let
|
|
4514
|
+
let nodeCount = 0;
|
|
3723
4515
|
for (const event of events) {
|
|
3724
4516
|
const rendered = JSON.stringify({
|
|
3725
4517
|
id: event.id,
|
|
@@ -3738,25 +4530,24 @@ Events:
|
|
|
3738
4530
|
tokens += cost;
|
|
3739
4531
|
eventCount += 1;
|
|
3740
4532
|
}
|
|
3741
|
-
for (const
|
|
4533
|
+
for (const node of graphNodes) {
|
|
3742
4534
|
const rendered = JSON.stringify({
|
|
3743
|
-
|
|
3744
|
-
name:
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
validTo: element.fact.validTo
|
|
4535
|
+
nodeId: node.id,
|
|
4536
|
+
name: node.name,
|
|
4537
|
+
type: node.type,
|
|
4538
|
+
currentState: node.currentState,
|
|
4539
|
+
facts: node.facts.filter(({ status }) => status === "active").map(({ key, value, validFrom, validTo }) => ({ key, value, validFrom, validTo }))
|
|
3749
4540
|
});
|
|
3750
4541
|
const cost = estimateTokens(`
|
|
3751
|
-
|
|
4542
|
+
KnowledgeGraph:
|
|
3752
4543
|
- ${rendered}`);
|
|
3753
4544
|
if (tokens + cost > AUTO_MEMORY_TOKEN_BUDGET) break;
|
|
3754
|
-
if (
|
|
4545
|
+
if (nodeCount === 0) lines.push("KnowledgeGraph:");
|
|
3755
4546
|
lines.push(`- ${rendered}`);
|
|
3756
4547
|
tokens += cost;
|
|
3757
|
-
|
|
4548
|
+
nodeCount += 1;
|
|
3758
4549
|
}
|
|
3759
|
-
if (eventCount === 0 &&
|
|
4550
|
+
if (eventCount === 0 && nodeCount === 0) lines.push("(no activated memory)");
|
|
3760
4551
|
return lines.join("\n");
|
|
3761
4552
|
}
|
|
3762
4553
|
function estimateTokens(value) {
|
|
@@ -3816,9 +4607,26 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3816
4607
|
...args.participants ? { participants: args.participants } : {}
|
|
3817
4608
|
})
|
|
3818
4609
|
}));
|
|
4610
|
+
ctx.tools.register(defineTool({
|
|
4611
|
+
name: "memory_search_graph",
|
|
4612
|
+
description: "Search the current Event-backed Knowledge Graph for people, projects, organizations, tools, places, facts, and relations.",
|
|
4613
|
+
parameters: {
|
|
4614
|
+
query: { type: "string", required: true },
|
|
4615
|
+
limit: { type: "integer", description: "Maximum results, 1-20." }
|
|
4616
|
+
},
|
|
4617
|
+
output: jsonOutput,
|
|
4618
|
+
execute: async (args, exec) => runtime.searchGraph(sessionOf(exec), args.query, args.limit ?? 8)
|
|
4619
|
+
}));
|
|
4620
|
+
ctx.tools.register(defineTool({
|
|
4621
|
+
name: "memory_expand_graph_node",
|
|
4622
|
+
description: "Expand one Knowledge Graph node with its current facts, directed edges, and supporting Event evidence.",
|
|
4623
|
+
parameters: { id: { type: "string", required: true } },
|
|
4624
|
+
output: jsonOutput,
|
|
4625
|
+
execute: async (args, exec) => runtime.expandGraphNode(sessionOf(exec), args.id)
|
|
4626
|
+
}));
|
|
3819
4627
|
ctx.tools.register(defineTool({
|
|
3820
4628
|
name: "memory_search_elements",
|
|
3821
|
-
description: "
|
|
4629
|
+
description: "Deprecated compatibility search for legacy Element-card data. Prefer memory_search_graph.",
|
|
3822
4630
|
parameters: {
|
|
3823
4631
|
query: { type: "string", required: true },
|
|
3824
4632
|
limit: { type: "integer" },
|
|
@@ -3888,7 +4696,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3888
4696
|
missing: { type: "string", required: true },
|
|
3889
4697
|
next_strategy: {
|
|
3890
4698
|
type: "string",
|
|
3891
|
-
enum: ["answer", "search_events", "expand_event", "search_elements", "expand_element", "search_raw_memory", "expand_block"],
|
|
4699
|
+
enum: ["answer", "search_events", "expand_event", "search_graph", "expand_graph_node", "search_elements", "expand_element", "search_raw_memory", "expand_block"],
|
|
3892
4700
|
required: true
|
|
3893
4701
|
}
|
|
3894
4702
|
},
|
|
@@ -3911,6 +4719,20 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3911
4719
|
}
|
|
3912
4720
|
|
|
3913
4721
|
// src/web.ts
|
|
4722
|
+
import { createRequire } from "node:module";
|
|
4723
|
+
var STRATAGATE_DSH_VERSION = "0.2.22";
|
|
4724
|
+
var LEGACY_THREAD_ID = "__legacy__";
|
|
4725
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
4726
|
+
function installedPackageVersion(names) {
|
|
4727
|
+
for (const name2 of names) {
|
|
4728
|
+
try {
|
|
4729
|
+
const value = nodeRequire(`${name2}/package.json`);
|
|
4730
|
+
if (typeof value.version === "string" && value.version.trim()) return value.version;
|
|
4731
|
+
} catch {
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
return "unknown";
|
|
4735
|
+
}
|
|
3914
4736
|
function sendJson(res, status, body) {
|
|
3915
4737
|
res.statusCode = status;
|
|
3916
4738
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -3921,8 +4743,8 @@ function numeric(value, fallback, minimum, maximum) {
|
|
|
3921
4743
|
const parsed = Number(value);
|
|
3922
4744
|
return Number.isFinite(parsed) ? Math.min(maximum, Math.max(minimum, Math.floor(parsed))) : fallback;
|
|
3923
4745
|
}
|
|
3924
|
-
function redact(
|
|
3925
|
-
return
|
|
4746
|
+
function redact(text3) {
|
|
4747
|
+
return text3.replace(/\b(?:sk|gh[opasu]|github_pat)_[A-Za-z0-9_-]{12,}\b/g, "[REDACTED_TOKEN]").replace(/\b(Bearer\s+)[A-Za-z0-9._~+/-]{12,}={0,2}\b/gi, "$1[REDACTED]").replace(/\b(api[_-]?key|token|password|secret)\s*[:=]\s*([^\s,;]+)/gi, "$1=[REDACTED]");
|
|
3926
4748
|
}
|
|
3927
4749
|
function redactValue(value) {
|
|
3928
4750
|
if (typeof value === "string") return redact(value);
|
|
@@ -3953,11 +4775,23 @@ function sourceMessages(snapshot, ids) {
|
|
|
3953
4775
|
}
|
|
3954
4776
|
return output;
|
|
3955
4777
|
}
|
|
4778
|
+
function blockLayers(block) {
|
|
4779
|
+
return [
|
|
4780
|
+
{ level: 0, content: `${block.l0Title}
|
|
4781
|
+
\u6807\u7B7E\uFF1A${block.l0Tags.join("\u3001") || "\u65E0"}` },
|
|
4782
|
+
{ level: 1, content: block.l1Summary || block.l0Title },
|
|
4783
|
+
{ level: 2, content: block.l2Keypoints.map((point) => `\u2022 ${point}`).join("\n") || block.l1Summary || block.l0Title },
|
|
4784
|
+
{ level: 3, content: block.l3Condensed || block.l2Keypoints.join("\n") || block.l1Summary },
|
|
4785
|
+
{ level: 4, content: block.l4Readable || block.l3Condensed },
|
|
4786
|
+
{ level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
4787
|
+
];
|
|
4788
|
+
}
|
|
3956
4789
|
function eventSummary(event) {
|
|
3957
4790
|
return {
|
|
3958
4791
|
id: event.id,
|
|
3959
4792
|
title: event.title,
|
|
3960
4793
|
summary: event.summary,
|
|
4794
|
+
narrative: event.narrative,
|
|
3961
4795
|
tags: event.tags,
|
|
3962
4796
|
sourceBlockId: event.sourceBlockId,
|
|
3963
4797
|
sourceMessageIds: event.sourceMessageIds,
|
|
@@ -4009,8 +4843,8 @@ async function overview(runtime) {
|
|
|
4009
4843
|
for (const namespace of namespaces) {
|
|
4010
4844
|
const snapshot = await runtime.adminSnapshot(namespace);
|
|
4011
4845
|
if (!snapshot) continue;
|
|
4012
|
-
const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.
|
|
4013
|
-
const processingJobs = snapshot.extractionJobs.filter(({ status }) => status === "running").length + snapshot.
|
|
4846
|
+
const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
|
|
4847
|
+
const processingJobs = snapshot.extractionJobs.filter(({ status }) => status === "running").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").length;
|
|
4014
4848
|
const failedJobDetails = [
|
|
4015
4849
|
...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => ({
|
|
4016
4850
|
id: job.blockId,
|
|
@@ -4020,9 +4854,9 @@ async function overview(runtime) {
|
|
|
4020
4854
|
lastErrorFull: job.lastError,
|
|
4021
4855
|
updatedAt: job.updatedAt
|
|
4022
4856
|
})),
|
|
4023
|
-
...snapshot.
|
|
4857
|
+
...snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").map((job) => ({
|
|
4024
4858
|
id: job.id,
|
|
4025
|
-
kind: "
|
|
4859
|
+
kind: "graph-projection",
|
|
4026
4860
|
attempts: job.attempts,
|
|
4027
4861
|
lastError: job.lastError?.slice(0, 500) ?? null,
|
|
4028
4862
|
lastErrorFull: job.lastError,
|
|
@@ -4033,6 +4867,7 @@ async function overview(runtime) {
|
|
|
4033
4867
|
...snapshot.blocks.map(({ createdAt }) => createdAt),
|
|
4034
4868
|
...snapshot.events.map(({ updatedAt }) => updatedAt),
|
|
4035
4869
|
...snapshot.elements.map(({ updatedAt }) => updatedAt),
|
|
4870
|
+
...snapshot.graphNodes.map(({ updatedAt }) => updatedAt),
|
|
4036
4871
|
...snapshot.usageReceipts.map(({ createdAt }) => createdAt)
|
|
4037
4872
|
].sort();
|
|
4038
4873
|
rows.push({
|
|
@@ -4047,6 +4882,15 @@ async function overview(runtime) {
|
|
|
4047
4882
|
events: snapshot.events.length,
|
|
4048
4883
|
activeEvents: snapshot.events.filter(({ status }) => status === "active").length,
|
|
4049
4884
|
elements: snapshot.elements.length,
|
|
4885
|
+
graphNodes: snapshot.graphNodes.length,
|
|
4886
|
+
graphEdges: snapshot.graphEdges.length,
|
|
4887
|
+
graphMigration: (() => {
|
|
4888
|
+
const projected = new Set(snapshot.graphProjectionJobs.filter(({ status, projectorVersion }) => status === "completed" && projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION).flatMap(({ sourceEventIds }) => sourceEventIds)).size;
|
|
4889
|
+
const failed = snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
|
|
4890
|
+
const running = snapshot.graphProjectionJobs.filter(({ status }) => status === "running").length;
|
|
4891
|
+
const total = snapshot.events.filter(({ status }) => status !== "forgotten" && status !== "archived").length;
|
|
4892
|
+
return { projected, total, failed, running, complete: projected >= total };
|
|
4893
|
+
})(),
|
|
4050
4894
|
usageReceipts: snapshot.usageReceipts.length,
|
|
4051
4895
|
memoryUseCount: snapshot.usageReceipts.filter((receipt) => receipt.eventIds.length > 0 || receipt.elementIds.length > 0).length,
|
|
4052
4896
|
failedJobs,
|
|
@@ -4056,7 +4900,13 @@ async function overview(runtime) {
|
|
|
4056
4900
|
lastActivityAt: timestamps.at(-1) ?? null
|
|
4057
4901
|
});
|
|
4058
4902
|
}
|
|
4059
|
-
return {
|
|
4903
|
+
return {
|
|
4904
|
+
readonly: true,
|
|
4905
|
+
settingsWritable: true,
|
|
4906
|
+
pluginVersion: STRATAGATE_DSH_VERSION,
|
|
4907
|
+
harnessVersion: installedPackageVersion(["@deepseek-ai/dsh", "@deepseek-ai/dsh-session"]),
|
|
4908
|
+
namespaces: rows
|
|
4909
|
+
};
|
|
4060
4910
|
}
|
|
4061
4911
|
async function updateSettings(runtime, url) {
|
|
4062
4912
|
const raw = url.searchParams.get("blockDecayLambda")?.trim() ?? "";
|
|
@@ -4066,6 +4916,126 @@ async function updateSettings(runtime, url) {
|
|
|
4066
4916
|
}
|
|
4067
4917
|
return { blockDecayLambda: await runtime.adminSetBlockDecayLambda(value) };
|
|
4068
4918
|
}
|
|
4919
|
+
function receiptThreadId(id) {
|
|
4920
|
+
const match = /^dsh:(.+):turn:\d+$/.exec(id);
|
|
4921
|
+
return match?.[1]?.trim() || null;
|
|
4922
|
+
}
|
|
4923
|
+
function timestampKey(value) {
|
|
4924
|
+
const parsed = Date.parse(value);
|
|
4925
|
+
return Number.isFinite(parsed) ? String(parsed) : value;
|
|
4926
|
+
}
|
|
4927
|
+
function recoverSnapshotView(snapshot) {
|
|
4928
|
+
const receiptThreads = /* @__PURE__ */ new Map();
|
|
4929
|
+
const receiptActivity = /* @__PURE__ */ new Map();
|
|
4930
|
+
const receiptCandidates = /* @__PURE__ */ new Map();
|
|
4931
|
+
for (const receipt of snapshot.ingestionReceipts) {
|
|
4932
|
+
const threadId = receiptThreadId(receipt.id);
|
|
4933
|
+
if (!threadId) continue;
|
|
4934
|
+
receiptThreads.set(receipt.id, threadId);
|
|
4935
|
+
const currentActivity = receiptActivity.get(threadId);
|
|
4936
|
+
if (!currentActivity || receipt.createdAt > currentActivity) receiptActivity.set(threadId, receipt.createdAt);
|
|
4937
|
+
const key = timestampKey(receipt.createdAt);
|
|
4938
|
+
const candidates = receiptCandidates.get(key) ?? /* @__PURE__ */ new Set();
|
|
4939
|
+
candidates.add(threadId);
|
|
4940
|
+
receiptCandidates.set(key, candidates);
|
|
4941
|
+
}
|
|
4942
|
+
const exactThreadAt = new Map([...receiptCandidates].filter(([, ids]) => ids.size === 1).map(([createdAt, ids]) => [createdAt, [...ids][0]]));
|
|
4943
|
+
const recoverMessages = (messages) => {
|
|
4944
|
+
let precedingThreadId = null;
|
|
4945
|
+
return messages.map((message) => {
|
|
4946
|
+
const explicit = message.threadId?.trim();
|
|
4947
|
+
const exact = exactThreadAt.get(timestampKey(message.createdAt));
|
|
4948
|
+
const recovered = explicit || exact || (message.role === "assistant" ? precedingThreadId : null);
|
|
4949
|
+
const threadId = recovered || LEGACY_THREAD_ID;
|
|
4950
|
+
if (message.role === "user" || explicit || exact) precedingThreadId = threadId;
|
|
4951
|
+
return { message, threadId };
|
|
4952
|
+
});
|
|
4953
|
+
};
|
|
4954
|
+
const blocks = [];
|
|
4955
|
+
for (const source of snapshot.blocks) {
|
|
4956
|
+
const recovered = recoverMessages(source.l5Raw);
|
|
4957
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4958
|
+
for (const item of recovered) {
|
|
4959
|
+
const messages = groups.get(item.threadId) ?? [];
|
|
4960
|
+
messages.push(item.message);
|
|
4961
|
+
groups.set(item.threadId, messages);
|
|
4962
|
+
}
|
|
4963
|
+
const entries = [...groups];
|
|
4964
|
+
for (const [threadId, messages] of entries) {
|
|
4965
|
+
const virtual = !source.threadId && (entries.length > 1 || threadId !== LEGACY_THREAD_ID);
|
|
4966
|
+
blocks.push({
|
|
4967
|
+
id: entries.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
|
|
4968
|
+
source,
|
|
4969
|
+
threadId,
|
|
4970
|
+
messages,
|
|
4971
|
+
virtual,
|
|
4972
|
+
turnRange: [0, 0]
|
|
4973
|
+
});
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
const turnCounters = /* @__PURE__ */ new Map();
|
|
4977
|
+
for (const block of blocks) {
|
|
4978
|
+
if (block.source.threadId) {
|
|
4979
|
+
block.turnRange = [block.source.startTurn, block.source.endTurn];
|
|
4980
|
+
turnCounters.set(block.threadId, Math.max(turnCounters.get(block.threadId) ?? 0, block.source.endTurn));
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4983
|
+
const turns = Math.max(1, block.messages.filter(({ role }) => role === "user").length);
|
|
4984
|
+
const start = (turnCounters.get(block.threadId) ?? 0) + 1;
|
|
4985
|
+
block.turnRange = [start, start + turns - 1];
|
|
4986
|
+
turnCounters.set(block.threadId, start + turns - 1);
|
|
4987
|
+
}
|
|
4988
|
+
return {
|
|
4989
|
+
blocks,
|
|
4990
|
+
openMessages: recoverMessages(snapshot.openTail),
|
|
4991
|
+
receiptThreads,
|
|
4992
|
+
receiptActivity
|
|
4993
|
+
};
|
|
4994
|
+
}
|
|
4995
|
+
function virtualBlockLayers(block) {
|
|
4996
|
+
if (!block.virtual || block.messages.length === block.source.l5Raw.length) return blockLayers(block.source);
|
|
4997
|
+
const deterministic = deterministicBlockLayers(block.messages);
|
|
4998
|
+
const natural = block.messages.filter(({ role }) => role === "user" || role === "assistant");
|
|
4999
|
+
const firstUser = natural.find(({ role, content }) => role === "user" && content.trim());
|
|
5000
|
+
const title = firstUser?.content.replace(/\s+/g, " ").trim().slice(0, 80) || "\u65E7\u4F1A\u8BDD\u7247\u6BB5";
|
|
5001
|
+
const summary = natural.map(({ content }) => content.replace(/\s+/g, " ").trim()).filter(Boolean).join(" ").slice(0, 500);
|
|
5002
|
+
const keypoints = natural.filter(({ role }) => role === "user").map(({ content }) => content.replace(/\s+/g, " ").trim().slice(0, 160));
|
|
5003
|
+
return [
|
|
5004
|
+
{ level: 0, content: title },
|
|
5005
|
+
{ level: 1, content: summary || title },
|
|
5006
|
+
{ level: 2, content: keypoints.map((point) => `\u2022 ${point}`).join("\n") || summary || title },
|
|
5007
|
+
{ level: 3, content: deterministic.l3Condensed },
|
|
5008
|
+
{ level: 4, content: deterministic.l4Readable },
|
|
5009
|
+
{ level: 5, content: block.messages.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
5010
|
+
];
|
|
5011
|
+
}
|
|
5012
|
+
function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
|
|
5013
|
+
const ids = /* @__PURE__ */ new Set([
|
|
5014
|
+
...view.blocks.map((block) => block.threadId),
|
|
5015
|
+
...view.openMessages.map(({ threadId }) => threadId),
|
|
5016
|
+
...view.receiptThreads.values()
|
|
5017
|
+
]);
|
|
5018
|
+
return [...ids].map((id) => {
|
|
5019
|
+
const blocks = view.blocks.filter((block) => block.threadId === id);
|
|
5020
|
+
const messages = [
|
|
5021
|
+
...blocks.flatMap((block) => block.messages),
|
|
5022
|
+
...view.openMessages.filter((message) => message.threadId === id).map(({ message }) => message)
|
|
5023
|
+
];
|
|
5024
|
+
const firstUser = messages.find(({ role, content }) => role === "user" && content.trim());
|
|
5025
|
+
const title = firstUser?.content.replace(/\s+/g, " ").trim().slice(0, 28);
|
|
5026
|
+
const timestamps = [
|
|
5027
|
+
...blocks.map(({ source }) => source.createdAt),
|
|
5028
|
+
...messages.map(({ createdAt }) => createdAt),
|
|
5029
|
+
...view.receiptActivity.get(id) ? [view.receiptActivity.get(id)] : []
|
|
5030
|
+
].sort();
|
|
5031
|
+
return {
|
|
5032
|
+
id,
|
|
5033
|
+
label: id === LEGACY_THREAD_ID ? "\u5386\u53F2\u5BF9\u8BDD" : title || `\u5BF9\u8BDD ${id.slice(0, 8)}`,
|
|
5034
|
+
blocks: blocks.length,
|
|
5035
|
+
lastActivityAt: timestamps.at(-1) ?? null
|
|
5036
|
+
};
|
|
5037
|
+
}).sort((left, right) => String(right.lastActivityAt).localeCompare(String(left.lastActivityAt)));
|
|
5038
|
+
}
|
|
4069
5039
|
async function memories(runtime, url) {
|
|
4070
5040
|
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
4071
5041
|
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
@@ -4075,49 +5045,117 @@ async function memories(runtime, url) {
|
|
|
4075
5045
|
const offset = numeric(url.searchParams.get("offset"), 0, 0, Number.MAX_SAFE_INTEGER);
|
|
4076
5046
|
const limit = numeric(url.searchParams.get("limit"), 100, 1, 200);
|
|
4077
5047
|
let values;
|
|
4078
|
-
if (kind === "events") values = snapshot.events.
|
|
5048
|
+
if (kind === "events") values = [...snapshot.events].sort((left, right) => {
|
|
5049
|
+
const time = (event) => event.temporal.happenedStart ?? event.temporal.happenedEnd ?? event.temporal.mentionedAt ?? event.createdAt;
|
|
5050
|
+
return time(right).localeCompare(time(left));
|
|
5051
|
+
}).map((event) => ({
|
|
4079
5052
|
...eventSummary(event),
|
|
5053
|
+
relatedNodes: snapshot.graphNodes.filter(({ id, sourceEventIds }) => sourceEventIds.includes(event.id) || (event.temporal.participantNodeIds ?? []).includes(id)).map(({ id, name: name2, type }) => ({ id, name: name2, type })),
|
|
4080
5054
|
relatedElements: snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.includes(event.id)).map(({ id, name: name2 }) => ({ id, name: name2 }))
|
|
4081
5055
|
}));
|
|
4082
|
-
else if (kind === "
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
5056
|
+
else if (kind === "graph") {
|
|
5057
|
+
const eventMap = new Map(snapshot.events.map((event) => [event.id, event]));
|
|
5058
|
+
return {
|
|
5059
|
+
namespace,
|
|
5060
|
+
kind,
|
|
5061
|
+
projectorVersion: KNOWLEDGE_GRAPH_PROJECTOR_VERSION,
|
|
5062
|
+
nodes: snapshot.graphNodes.map((node) => ({
|
|
5063
|
+
...node,
|
|
5064
|
+
supportingEvents: node.sourceEventIds.flatMap((id) => eventMap.get(id) ?? []).map(eventSummary)
|
|
5065
|
+
})),
|
|
5066
|
+
edges: snapshot.graphEdges,
|
|
5067
|
+
migration: (() => {
|
|
5068
|
+
const projected = new Set(snapshot.graphProjectionJobs.filter(({ status, projectorVersion }) => status === "completed" && projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION).flatMap(({ sourceEventIds }) => sourceEventIds)).size;
|
|
5069
|
+
return {
|
|
5070
|
+
projected,
|
|
5071
|
+
total: snapshot.events.filter(({ status }) => status !== "forgotten" && status !== "archived").length,
|
|
5072
|
+
pending: snapshot.graphProjectionJobs.filter(({ status }) => status === "pending").length,
|
|
5073
|
+
running: snapshot.graphProjectionJobs.filter(({ status }) => status === "running").length,
|
|
5074
|
+
failed: snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length,
|
|
5075
|
+
complete: projected >= snapshot.events.filter(({ status }) => status !== "forgotten" && status !== "archived").length
|
|
5076
|
+
};
|
|
5077
|
+
})()
|
|
5078
|
+
};
|
|
5079
|
+
} else if (kind === "elements") values = snapshot.elements.map(elementSummary);
|
|
5080
|
+
else if (kind === "blocks") {
|
|
5081
|
+
const recovered = recoverSnapshotView(snapshot);
|
|
5082
|
+
const conversations = conversationRows(snapshot, recovered);
|
|
5083
|
+
const requestedThreadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
5084
|
+
const activeThreadId = requestedThreadId || conversations[0]?.id || null;
|
|
5085
|
+
const scopedBlocks = activeThreadId ? recovered.blocks.filter((block) => block.threadId === activeThreadId) : [];
|
|
5086
|
+
values = scopedBlocks.map((block) => {
|
|
5087
|
+
const source = block.source;
|
|
5088
|
+
const extraction = snapshot.extractionJobs.find(({ blockId }) => blockId === source.id);
|
|
5089
|
+
const blockMessageIds = new Set(block.messages.map(({ id }) => id));
|
|
5090
|
+
const relatedEvents = snapshot.events.filter((event) => event.sourceBlockId === source.id && (!block.virtual || event.sourceMessageIds.some((id) => blockMessageIds.has(id))));
|
|
5091
|
+
const eventIds = new Set(relatedEvents.map(({ id }) => id));
|
|
5092
|
+
const projections = snapshot.graphProjectionJobs.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
5093
|
+
const relatedNodes = snapshot.graphNodes.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id))).map(({ id, name: name2, type }) => ({ id, name: name2, type }));
|
|
5094
|
+
const failedProjection = projections.find(({ status: status2 }) => status2 === "failed");
|
|
5095
|
+
const pendingProjection = projections.some(({ status: status2 }) => status2 === "pending" || status2 === "running");
|
|
5096
|
+
const needsExtraction = source.shouldExtract === true;
|
|
5097
|
+
const status = extraction?.status === "failed" || failedProjection ? "failed" : extraction?.status === "succeeded" || extraction?.status === "skipped" ? pendingProjection ? "processing" : "organized" : needsExtraction ? "waiting" : "organized";
|
|
5098
|
+
const blockPosition = scopedBlocks.findIndex(({ id }) => id === block.id) + 1;
|
|
5099
|
+
const latestBlockPosition = scopedBlocks.length;
|
|
5100
|
+
const currentLevel = getDecayedBlockLevel(
|
|
5101
|
+
source.pointerAnchorLevel,
|
|
5102
|
+
source.threadId ? source.pointerAnchorBlockPosition : Math.min(source.pointerAnchorBlockPosition, blockPosition),
|
|
5103
|
+
latestBlockPosition,
|
|
5104
|
+
snapshot.blockDecayLambda
|
|
5105
|
+
);
|
|
5106
|
+
return {
|
|
5107
|
+
id: block.id,
|
|
5108
|
+
sourceBlockId: source.id,
|
|
5109
|
+
threadId: block.threadId,
|
|
5110
|
+
sequence: source.sequence,
|
|
5111
|
+
turnRange: block.turnRange,
|
|
5112
|
+
title: block.virtual && block.messages.length !== source.l5Raw.length ? block.messages.find(({ role }) => role === "user")?.content.replace(/\s+/g, " ").trim().slice(0, 80) || "\u65E7\u4F1A\u8BDD\u7247\u6BB5" : source.l0Title,
|
|
5113
|
+
tags: source.l0Tags,
|
|
5114
|
+
summary: source.l1Summary,
|
|
5115
|
+
keypoints: source.l2Keypoints,
|
|
5116
|
+
currentLevel,
|
|
5117
|
+
distanceFromLatest: Math.max(0, latestBlockPosition - blockPosition),
|
|
5118
|
+
expansionSource: source.lastLiftedAt ? source.lastLiftedBy ?? "legacy" : null,
|
|
5119
|
+
lastLiftedAt: source.lastLiftedAt,
|
|
5120
|
+
sourceMessages: block.messages.length,
|
|
5121
|
+
createdAt: source.createdAt,
|
|
5122
|
+
virtual: block.virtual,
|
|
5123
|
+
status,
|
|
5124
|
+
eventExtraction: extraction ? {
|
|
5125
|
+
status: extraction.status,
|
|
5126
|
+
attempts: extraction.attempts,
|
|
5127
|
+
updatedAt: extraction.updatedAt,
|
|
5128
|
+
lastError: extraction.lastError
|
|
5129
|
+
} : null,
|
|
5130
|
+
graphProjection: projections.length ? {
|
|
5131
|
+
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
5132
|
+
jobs: projections.length,
|
|
5133
|
+
lastError: failedProjection?.lastError ?? null
|
|
5134
|
+
} : null,
|
|
5135
|
+
relatedEvents: relatedEvents.map(eventSummary),
|
|
5136
|
+
relatedNodes
|
|
5137
|
+
};
|
|
5138
|
+
});
|
|
5139
|
+
const filtered2 = values.filter((value) => matchesQuery(value, query));
|
|
5140
|
+
const latestSealedTurn = scopedBlocks.reduce((latest, block) => Math.max(latest, block.turnRange[1]), 0);
|
|
5141
|
+
const openMessages = activeThreadId ? recovered.openMessages.filter((message) => message.threadId === activeThreadId).map(({ message }) => message) : [];
|
|
5142
|
+
const openTurns = openMessages.filter(({ role }) => role === "user").length;
|
|
4093
5143
|
return {
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
attempts: extraction.attempts,
|
|
4108
|
-
updatedAt: extraction.updatedAt,
|
|
4109
|
-
lastError: extraction.lastError
|
|
4110
|
-
} : null,
|
|
4111
|
-
elementProjection: projections.length ? {
|
|
4112
|
-
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
4113
|
-
jobs: projections.length,
|
|
4114
|
-
lastError: failedProjection?.lastError ?? null
|
|
4115
|
-
} : null,
|
|
4116
|
-
relatedEvents: relatedEvents.map(eventSummary),
|
|
4117
|
-
relatedElements
|
|
5144
|
+
namespace,
|
|
5145
|
+
kind,
|
|
5146
|
+
total: filtered2.length,
|
|
5147
|
+
offset,
|
|
5148
|
+
limit,
|
|
5149
|
+
items: filtered2.slice(offset, offset + limit),
|
|
5150
|
+
openBlock: {
|
|
5151
|
+
turnRange: openTurns > 0 ? [latestSealedTurn + 1, latestSealedTurn + openTurns] : null,
|
|
5152
|
+
messages: openMessages.length,
|
|
5153
|
+
status: "open"
|
|
5154
|
+
},
|
|
5155
|
+
conversations,
|
|
5156
|
+
activeThreadId
|
|
4118
5157
|
};
|
|
4119
|
-
});
|
|
4120
|
-
else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
5158
|
+
} else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
4121
5159
|
const filtered = values.filter((value) => matchesQuery(value, query));
|
|
4122
5160
|
return { namespace, kind, total: filtered.length, offset, limit, items: filtered.slice(offset, offset + limit) };
|
|
4123
5161
|
}
|
|
@@ -4126,6 +5164,7 @@ async function sources(runtime, url) {
|
|
|
4126
5164
|
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
4127
5165
|
const snapshot = await requiredSnapshot(runtime, namespace);
|
|
4128
5166
|
const eventId = url.searchParams.get("eventId");
|
|
5167
|
+
const nodeId = url.searchParams.get("nodeId");
|
|
4129
5168
|
const elementId = url.searchParams.get("elementId");
|
|
4130
5169
|
const blockId = url.searchParams.get("blockId");
|
|
4131
5170
|
let events = [];
|
|
@@ -4136,6 +5175,18 @@ async function sources(runtime, url) {
|
|
|
4136
5175
|
if (!event) throw new AdminHttpError(404, `Unknown event: ${eventId}`);
|
|
4137
5176
|
events = [event];
|
|
4138
5177
|
ids = new Set(event.sourceMessageIds);
|
|
5178
|
+
} else if (nodeId) {
|
|
5179
|
+
const node = snapshot.graphNodes.find(({ id }) => id === nodeId);
|
|
5180
|
+
if (!node) throw new AdminHttpError(404, `Unknown graph node: ${nodeId}`);
|
|
5181
|
+
events = snapshot.events.filter(({ id }) => node.sourceEventIds.includes(id));
|
|
5182
|
+
ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
5183
|
+
return {
|
|
5184
|
+
namespace,
|
|
5185
|
+
node,
|
|
5186
|
+
edges: snapshot.graphEdges.filter(({ fromNodeId, toNodeId }) => fromNodeId === node.id || toNodeId === node.id),
|
|
5187
|
+
events: events.map(eventSummary),
|
|
5188
|
+
messages: sourceMessages(snapshot, ids)
|
|
5189
|
+
};
|
|
4139
5190
|
} else if (elementId) {
|
|
4140
5191
|
const element = snapshot.elements.find(({ id }) => id === elementId);
|
|
4141
5192
|
if (!element) throw new AdminHttpError(404, `Unknown element: ${elementId}`);
|
|
@@ -4143,22 +5194,43 @@ async function sources(runtime, url) {
|
|
|
4143
5194
|
events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
|
|
4144
5195
|
ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
4145
5196
|
} else if (blockId) {
|
|
4146
|
-
const
|
|
5197
|
+
const displayBlock = recoverSnapshotView(snapshot).blocks.find(({ id }) => id === blockId);
|
|
5198
|
+
const block = displayBlock?.source ?? snapshot.blocks.find(({ id }) => id === blockId);
|
|
4147
5199
|
if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
|
|
4148
|
-
|
|
4149
|
-
|
|
5200
|
+
const messages = displayBlock?.messages ?? block.l5Raw;
|
|
5201
|
+
ids = new Set(messages.map(({ id }) => id));
|
|
5202
|
+
events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) => ids.has(id))));
|
|
4150
5203
|
const eventIds = new Set(events.map(({ id }) => id));
|
|
4151
5204
|
elements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
5205
|
+
return {
|
|
5206
|
+
namespace,
|
|
5207
|
+
events: events.map(eventSummary),
|
|
5208
|
+
elements: elements.map(elementSummary),
|
|
5209
|
+
messages: sourceMessages(snapshot, ids),
|
|
5210
|
+
layers: displayBlock ? virtualBlockLayers(displayBlock) : blockLayers(block),
|
|
5211
|
+
virtual: displayBlock?.virtual ?? false
|
|
5212
|
+
};
|
|
4152
5213
|
} else {
|
|
4153
|
-
throw new AdminHttpError(400, "eventId, elementId, or blockId is required");
|
|
5214
|
+
throw new AdminHttpError(400, "eventId, nodeId, elementId, or blockId is required");
|
|
4154
5215
|
}
|
|
4155
5216
|
return {
|
|
4156
5217
|
namespace,
|
|
4157
5218
|
events: events.map(eventSummary),
|
|
4158
5219
|
elements: elements.map(elementSummary),
|
|
5220
|
+
blocks: events.map((event) => snapshot.blocks.find(({ id }) => id === event.sourceBlockId)).filter((block) => Boolean(block)).map((block) => ({ id: block.id, title: block.l0Title, createdAt: block.createdAt })),
|
|
4159
5221
|
messages: sourceMessages(snapshot, ids)
|
|
4160
5222
|
};
|
|
4161
5223
|
}
|
|
5224
|
+
async function expandBlock(runtime, url) {
|
|
5225
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
5226
|
+
const blockId = url.searchParams.get("blockId")?.trim() ?? "";
|
|
5227
|
+
const target = url.searchParams.get("level")?.trim() ?? "";
|
|
5228
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
5229
|
+
if (!blockId) throw new AdminHttpError(400, "blockId is required");
|
|
5230
|
+
if (blockId.startsWith("virtual:")) throw new AdminHttpError(409, "Recovered legacy fragments are read-only display data");
|
|
5231
|
+
if (!/^L?[0-5]$/i.test(target)) throw new AdminHttpError(400, "level must be L0 through L5");
|
|
5232
|
+
return runtime.adminExpandBlock(namespace, blockId, target);
|
|
5233
|
+
}
|
|
4162
5234
|
function receiptSources(snapshot, receipt) {
|
|
4163
5235
|
const events = snapshot.events.filter(({ id }) => receipt.eventIds.includes(id));
|
|
4164
5236
|
const elements = snapshot.elements.filter(({ id }) => receipt.elementIds.includes(id));
|
|
@@ -4194,6 +5266,9 @@ async function handleAdminRequest(runtime, req, res) {
|
|
|
4194
5266
|
if (path === "/api/stratagate/settings") {
|
|
4195
5267
|
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate settings require PATCH");
|
|
4196
5268
|
sendJson(res, 200, await updateSettings(runtime, url));
|
|
5269
|
+
} else if (path === "/api/stratagate/blocks/expand") {
|
|
5270
|
+
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate Block expansion requires PATCH");
|
|
5271
|
+
sendJson(res, 200, await expandBlock(runtime, url));
|
|
4197
5272
|
} else if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate memory data is read-only");
|
|
4198
5273
|
else if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
4199
5274
|
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|
|
@@ -4223,7 +5298,7 @@ var MEMORY_PROTOCOL = `[StrataGate memory protocol]
|
|
|
4223
5298
|
StrataGate provides durable, evidence-gated memory through memory_* tools.
|
|
4224
5299
|
|
|
4225
5300
|
- Search memory when the current task could depend on prior project decisions, user preferences, people, tools, historical outcomes, or unresolved work. Do not search for facts already established in the current conversation.
|
|
4226
|
-
- Start with memory_search_events for decisions and history, or
|
|
5301
|
+
- Start with memory_search_events for decisions and history, or memory_search_graph for the current state of a person/project/tool/place/organization.
|
|
4227
5302
|
- Every retrieval replaces the latest batch. Call memory_assess after each batch before relying on it. Cite only evidenceRefs returned by that exact latest batch.
|
|
4228
5303
|
- If assessment is partial or wrong, follow nextStrategy: refine the search, expand an Element/block, or search raw memory. Do not present uncertain memory as fact.
|
|
4229
5304
|
- Every retrieval batch must be closed with memory_record_use before the turn can end. Pass evidence_refs containing exactly the refs actually used, or [] when no retrieved evidence was used. Non-empty refs require a sufficient assessment of that latest batch. Never use a numeric increment; StrataGate applies one reinforcement per selected card.
|
|
@@ -4237,6 +5312,8 @@ async function apply(ctx, config) {
|
|
|
4237
5312
|
const models = new DshModelBridge(ctx, resolved);
|
|
4238
5313
|
const runtime = new StrataGateRuntime(resolved, models, (error) => {
|
|
4239
5314
|
ctx.logger.error(`stratagate-memory ingestion failed: ${renderError(error)}`);
|
|
5315
|
+
}, async (session) => {
|
|
5316
|
+
await ctx.sessions.flush(session);
|
|
4240
5317
|
});
|
|
4241
5318
|
await runtime.syncConfiguredSettings();
|
|
4242
5319
|
ctx.systemPrompt.section({ name: "tool:stratagate-memory", order: 113, text: MEMORY_PROTOCOL });
|
|
@@ -4245,10 +5322,10 @@ async function apply(ctx, config) {
|
|
|
4245
5322
|
const session = context.agent?.session;
|
|
4246
5323
|
if (!session) return assembled;
|
|
4247
5324
|
try {
|
|
4248
|
-
const
|
|
5325
|
+
const text3 = await runtime.buildAutoContext(session);
|
|
4249
5326
|
return {
|
|
4250
5327
|
...assembled,
|
|
4251
|
-
contexts: [...assembled.contexts, { name: "stratagate:auto-memory", text:
|
|
5328
|
+
contexts: [...assembled.contexts, { name: "stratagate:auto-memory", text: text3 }]
|
|
4252
5329
|
};
|
|
4253
5330
|
} catch (error) {
|
|
4254
5331
|
ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
|
|
@@ -4257,7 +5334,7 @@ async function apply(ctx, config) {
|
|
|
4257
5334
|
});
|
|
4258
5335
|
ctx.on("agent/turn-stopping", ({ agent }) => {
|
|
4259
5336
|
if (!runtime.needsRecordUse(agent.session)) return;
|
|
4260
|
-
agent.steer(
|
|
5337
|
+
agent.steer(createUserMessage3({
|
|
4261
5338
|
content: [{
|
|
4262
5339
|
type: "text",
|
|
4263
5340
|
text: "A StrataGate retrieval batch is still unresolved. Before ending this turn, call memory_record_use with evidence_refs set to exactly the retrieved refs used in the answer, or [] if none were used."
|