stratagate-dsh 0.2.21 → 0.2.26
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 +30 -0
- package/README.md +14 -11
- package/dist/client.js +315 -53
- package/dist/index.js +954 -142
- package/dist/index.js.map +1 -1
- package/docs/README.zh-CN.md +14 -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,6 +553,9 @@ 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;
|
|
@@ -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,15 +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()
|
|
434
619
|
};
|
|
435
620
|
} else if (schemaVersion === 6) {
|
|
436
621
|
const legacy = value;
|
|
437
622
|
snapshot = {
|
|
438
623
|
...structuredClone(legacy),
|
|
439
624
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
440
|
-
blocks: legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null }))
|
|
625
|
+
blocks: legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null })),
|
|
626
|
+
...emptyGraph()
|
|
441
627
|
};
|
|
628
|
+
} else if (schemaVersion === 7) {
|
|
629
|
+
const legacy = value;
|
|
630
|
+
snapshot = { ...structuredClone(legacy), schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION, ...emptyGraph() };
|
|
442
631
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
443
632
|
snapshot = structuredClone(value);
|
|
444
633
|
} else {
|
|
@@ -453,10 +642,13 @@ function normalizeSnapshot(value) {
|
|
|
453
642
|
if (!Number.isFinite(snapshot.blockDecayLambda) || snapshot.blockDecayLambda < 0) {
|
|
454
643
|
throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
|
|
455
644
|
}
|
|
456
|
-
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"]) {
|
|
457
646
|
if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
|
|
458
647
|
}
|
|
459
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
|
+
}
|
|
460
652
|
for (const block of snapshot.blocks) {
|
|
461
653
|
if (!Number.isSafeInteger(block.pointerAnchorBlockPosition) || block.pointerAnchorBlockPosition < 1) {
|
|
462
654
|
throw new TypeError("Invalid StrataGate snapshot: pointerAnchorBlockPosition must be a positive integer");
|
|
@@ -817,6 +1009,15 @@ CREATE TABLE IF NOT EXISTS element_projection_jobs (
|
|
|
817
1009
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
818
1010
|
) STRICT;
|
|
819
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
|
+
|
|
820
1021
|
CREATE TABLE IF NOT EXISTS usage_receipts (
|
|
821
1022
|
namespace TEXT NOT NULL,
|
|
822
1023
|
receipt_id TEXT NOT NULL,
|
|
@@ -954,7 +1155,10 @@ var SqliteStorage = class {
|
|
|
954
1155
|
quotes: parseJson(row.quotes_json, "events.quotes_json"),
|
|
955
1156
|
sourceMessageIds: sourcesByEvent.get(row.id) ?? [],
|
|
956
1157
|
sourceBlockId: row.source_block_id,
|
|
957
|
-
temporal:
|
|
1158
|
+
temporal: (() => {
|
|
1159
|
+
const temporal = parseJson(row.temporal_json, "events.temporal_json");
|
|
1160
|
+
return { ...temporal, eventType: normalizeStandardEventType(temporal.eventType) };
|
|
1161
|
+
})(),
|
|
958
1162
|
scope: row.scope,
|
|
959
1163
|
criticality: row.criticality,
|
|
960
1164
|
confidence: row.confidence,
|
|
@@ -1092,6 +1296,12 @@ var SqliteStorage = class {
|
|
|
1092
1296
|
id: row.receipt_id,
|
|
1093
1297
|
createdAt: row.created_at
|
|
1094
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") : [];
|
|
1095
1305
|
const snapshot = {
|
|
1096
1306
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1097
1307
|
currentTurn: space.current_turn,
|
|
@@ -1100,6 +1310,9 @@ var SqliteStorage = class {
|
|
|
1100
1310
|
openTail,
|
|
1101
1311
|
blocks,
|
|
1102
1312
|
events,
|
|
1313
|
+
graphNodes,
|
|
1314
|
+
graphEdges,
|
|
1315
|
+
graphProjectionJobs,
|
|
1103
1316
|
elements,
|
|
1104
1317
|
extractionJobs,
|
|
1105
1318
|
elementProjectionJobs,
|
|
@@ -1432,6 +1645,21 @@ var SqliteStorage = class {
|
|
|
1432
1645
|
job.updatedAt
|
|
1433
1646
|
);
|
|
1434
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
|
+
);
|
|
1435
1663
|
const insertReceipt = this.database.prepare(`
|
|
1436
1664
|
INSERT INTO usage_receipts (namespace, receipt_id, event_ids_json, element_ids_json, audit_json, created_at)
|
|
1437
1665
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -1476,7 +1704,7 @@ var SqliteStorage = class {
|
|
|
1476
1704
|
this.database.exec(THREAD_INDEXES);
|
|
1477
1705
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1478
1706
|
});
|
|
1479
|
-
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5 || version === 6) {
|
|
1707
|
+
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5 || version === 6 || version === 7) {
|
|
1480
1708
|
this.immediateTransaction(() => {
|
|
1481
1709
|
this.database.exec(SCHEMA);
|
|
1482
1710
|
if (version === 1) {
|
|
@@ -1566,6 +1794,9 @@ function defaultIdFactory(prefix) {
|
|
|
1566
1794
|
function defaultElementIdFactory(prefix) {
|
|
1567
1795
|
return `${prefix}_${crypto.randomUUID()}`;
|
|
1568
1796
|
}
|
|
1797
|
+
function defaultGraphIdFactory(prefix) {
|
|
1798
|
+
return `${prefix}_${crypto.randomUUID()}`;
|
|
1799
|
+
}
|
|
1569
1800
|
function defaultSummary(messages) {
|
|
1570
1801
|
const natural = messages.filter((message) => message.role === "user" || message.role === "assistant");
|
|
1571
1802
|
const firstUser = natural.find((message) => message.role === "user");
|
|
@@ -1603,15 +1834,21 @@ var StrataGate = class _StrataGate {
|
|
|
1603
1834
|
summarizer;
|
|
1604
1835
|
extractor;
|
|
1605
1836
|
elementProjector;
|
|
1837
|
+
disableElementProjection;
|
|
1838
|
+
graphProjector;
|
|
1606
1839
|
now;
|
|
1607
1840
|
idFactory;
|
|
1608
1841
|
elementIdFactory;
|
|
1842
|
+
graphIdFactory;
|
|
1609
1843
|
openTail = [];
|
|
1610
1844
|
blocks = [];
|
|
1611
1845
|
events = [];
|
|
1612
1846
|
elements = [];
|
|
1847
|
+
graphNodes = [];
|
|
1848
|
+
graphEdges = [];
|
|
1613
1849
|
extractionJobs = /* @__PURE__ */ new Map();
|
|
1614
1850
|
elementProjectionJobs = /* @__PURE__ */ new Map();
|
|
1851
|
+
graphProjectionJobs = /* @__PURE__ */ new Map();
|
|
1615
1852
|
usageReceipts = /* @__PURE__ */ new Map();
|
|
1616
1853
|
successfulModelResponses = [];
|
|
1617
1854
|
ingestionReceipts = /* @__PURE__ */ new Map();
|
|
@@ -1633,9 +1870,12 @@ var StrataGate = class _StrataGate {
|
|
|
1633
1870
|
this.summarizer = options.summarizer;
|
|
1634
1871
|
this.extractor = options.extractor;
|
|
1635
1872
|
this.elementProjector = options.elementProjector;
|
|
1873
|
+
this.disableElementProjection = options.disableElementProjection ?? false;
|
|
1874
|
+
this.graphProjector = options.graphProjector;
|
|
1636
1875
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1637
1876
|
this.idFactory = options.idFactory ?? defaultIdFactory;
|
|
1638
1877
|
this.elementIdFactory = options.elementIdFactory ?? defaultElementIdFactory;
|
|
1878
|
+
this.graphIdFactory = options.graphIdFactory ?? defaultGraphIdFactory;
|
|
1639
1879
|
}
|
|
1640
1880
|
static inMemory(options = {}) {
|
|
1641
1881
|
return new _StrataGate(options, STRATAGATE_CONSTRUCTOR_TOKEN);
|
|
@@ -1656,9 +1896,12 @@ var StrataGate = class _StrataGate {
|
|
|
1656
1896
|
...options.summarizer ? { summarizer: options.summarizer } : {},
|
|
1657
1897
|
...options.extractor ? { extractor: options.extractor } : {},
|
|
1658
1898
|
...options.elementProjector ? { elementProjector: options.elementProjector } : {},
|
|
1899
|
+
...options.disableElementProjection !== void 0 ? { disableElementProjection: options.disableElementProjection } : {},
|
|
1900
|
+
...options.graphProjector ? { graphProjector: options.graphProjector } : {},
|
|
1659
1901
|
...options.now ? { now: options.now } : {},
|
|
1660
1902
|
...options.idFactory ? { idFactory: options.idFactory } : {},
|
|
1661
|
-
...options.elementIdFactory ? { elementIdFactory: options.elementIdFactory } : {}
|
|
1903
|
+
...options.elementIdFactory ? { elementIdFactory: options.elementIdFactory } : {},
|
|
1904
|
+
...options.graphIdFactory ? { graphIdFactory: options.graphIdFactory } : {}
|
|
1662
1905
|
});
|
|
1663
1906
|
} catch (error) {
|
|
1664
1907
|
await storage.close();
|
|
@@ -1700,9 +1943,12 @@ var StrataGate = class _StrataGate {
|
|
|
1700
1943
|
if (options.summarizer) memoryOptions.summarizer = options.summarizer;
|
|
1701
1944
|
if (options.extractor) memoryOptions.extractor = options.extractor;
|
|
1702
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;
|
|
1703
1948
|
if (options.now) memoryOptions.now = options.now;
|
|
1704
1949
|
if (options.idFactory) memoryOptions.idFactory = options.idFactory;
|
|
1705
1950
|
if (options.elementIdFactory) memoryOptions.elementIdFactory = options.elementIdFactory;
|
|
1951
|
+
if (options.graphIdFactory) memoryOptions.graphIdFactory = options.graphIdFactory;
|
|
1706
1952
|
const memory = new _StrataGate(memoryOptions, STRATAGATE_CONSTRUCTOR_TOKEN);
|
|
1707
1953
|
memory.storage = options.storage;
|
|
1708
1954
|
memory.namespace = namespace;
|
|
@@ -1737,6 +1983,21 @@ var StrataGate = class _StrataGate {
|
|
|
1737
1983
|
}
|
|
1738
1984
|
});
|
|
1739
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());
|
|
1740
2001
|
} else {
|
|
1741
2002
|
await memory.persist();
|
|
1742
2003
|
}
|
|
@@ -1769,6 +2030,12 @@ var StrataGate = class _StrataGate {
|
|
|
1769
2030
|
listElements() {
|
|
1770
2031
|
return this.elements;
|
|
1771
2032
|
}
|
|
2033
|
+
listGraphNodes() {
|
|
2034
|
+
return this.graphNodes;
|
|
2035
|
+
}
|
|
2036
|
+
listGraphEdges() {
|
|
2037
|
+
return this.graphEdges;
|
|
2038
|
+
}
|
|
1772
2039
|
listOpenTail(threadId) {
|
|
1773
2040
|
if (threadId === void 0) return this.openTail;
|
|
1774
2041
|
return this.openTail.filter((message) => message.threadId === threadId);
|
|
@@ -1779,6 +2046,9 @@ var StrataGate = class _StrataGate {
|
|
|
1779
2046
|
listElementProjectionJobs() {
|
|
1780
2047
|
return [...this.elementProjectionJobs.values()];
|
|
1781
2048
|
}
|
|
2049
|
+
listGraphProjectionJobs() {
|
|
2050
|
+
return [...this.graphProjectionJobs.values()];
|
|
2051
|
+
}
|
|
1782
2052
|
listUsageReceipts() {
|
|
1783
2053
|
return [...this.usageReceipts.values()];
|
|
1784
2054
|
}
|
|
@@ -1806,6 +2076,9 @@ var StrataGate = class _StrataGate {
|
|
|
1806
2076
|
openTail: this.openTail,
|
|
1807
2077
|
blocks: this.blocks,
|
|
1808
2078
|
events: this.events,
|
|
2079
|
+
graphNodes: this.graphNodes,
|
|
2080
|
+
graphEdges: this.graphEdges,
|
|
2081
|
+
graphProjectionJobs: [...this.graphProjectionJobs.values()],
|
|
1809
2082
|
elements: this.elements,
|
|
1810
2083
|
extractionJobs: [...this.extractionJobs.values()],
|
|
1811
2084
|
elementProjectionJobs: [...this.elementProjectionJobs.values()],
|
|
@@ -1856,11 +2129,13 @@ var StrataGate = class _StrataGate {
|
|
|
1856
2129
|
}
|
|
1857
2130
|
if (this.threadOpenTail(threadId).filter((message) => message.role === "user").length < this.blockTurnSize) {
|
|
1858
2131
|
const projectedElements2 = await this.projectEligibleElements() ?? [];
|
|
2132
|
+
await this.projectEligibleGraph();
|
|
1859
2133
|
return { sealedBlock: null, extractedEvents: [], projectedElements: projectedElements2 };
|
|
1860
2134
|
}
|
|
1861
2135
|
const sealedBlock = await this.sealOpenTail(threadId);
|
|
1862
2136
|
const extractedEvents = await this.extractEligibleBlock() ?? [];
|
|
1863
2137
|
const projectedElements = await this.projectEligibleElements() ?? [];
|
|
2138
|
+
await this.projectEligibleGraph();
|
|
1864
2139
|
return { sealedBlock, extractedEvents, projectedElements };
|
|
1865
2140
|
}
|
|
1866
2141
|
async resumePendingWork(options = {}) {
|
|
@@ -1873,12 +2148,14 @@ var StrataGate = class _StrataGate {
|
|
|
1873
2148
|
sealedBlocks.push(await this.sealOpenTail(sealable.threadId));
|
|
1874
2149
|
extractedEvents.push(...await this.extractEligibleBlock() ?? []);
|
|
1875
2150
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2151
|
+
await this.projectEligibleGraph();
|
|
1876
2152
|
}
|
|
1877
2153
|
while (true) {
|
|
1878
2154
|
const extracted = await this.extractEligibleBlock();
|
|
1879
2155
|
if (extracted === null) break;
|
|
1880
2156
|
extractedEvents.push(...extracted);
|
|
1881
2157
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2158
|
+
await this.projectEligibleGraph();
|
|
1882
2159
|
}
|
|
1883
2160
|
if (options.retrySkipped === true) {
|
|
1884
2161
|
const skippedBlockIds = this.blocks.filter((block) => this.nextBlockInThread(block) !== null && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
|
|
@@ -1887,6 +2164,7 @@ var StrataGate = class _StrataGate {
|
|
|
1887
2164
|
if (extracted === null) continue;
|
|
1888
2165
|
extractedEvents.push(...extracted);
|
|
1889
2166
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2167
|
+
await this.projectEligibleGraph();
|
|
1890
2168
|
}
|
|
1891
2169
|
}
|
|
1892
2170
|
while (true) {
|
|
@@ -1894,12 +2172,14 @@ var StrataGate = class _StrataGate {
|
|
|
1894
2172
|
if (projected === null) break;
|
|
1895
2173
|
projectedElements.push(...projected);
|
|
1896
2174
|
}
|
|
2175
|
+
await this.projectEligibleGraph();
|
|
1897
2176
|
return { sealedBlocks, extractedEvents, projectedElements };
|
|
1898
2177
|
}
|
|
1899
2178
|
async addEvent(input) {
|
|
1900
2179
|
return this.commitMutation(() => {
|
|
1901
2180
|
const event = this.addEventInMemory(input);
|
|
1902
2181
|
this.queueElementProjection([event.id]);
|
|
2182
|
+
this.queueGraphProjection([event.id], 1e3);
|
|
1903
2183
|
return event;
|
|
1904
2184
|
});
|
|
1905
2185
|
}
|
|
@@ -1929,10 +2209,10 @@ var StrataGate = class _StrataGate {
|
|
|
1929
2209
|
[event.temporal.originalText ?? "", 4],
|
|
1930
2210
|
[`${event.temporal.happenedStart ?? ""} ${event.temporal.happenedEnd ?? ""}`, 4]
|
|
1931
2211
|
])).map(({ item }) => item);
|
|
1932
|
-
const
|
|
2212
|
+
const chronology2 = (event) => event.temporal.happenedStart ?? event.temporal.happenedEnd ?? event.temporal.mentionedAt ?? event.createdAt;
|
|
1933
2213
|
const structured = (items) => [...items].sort((left, right) => {
|
|
1934
|
-
if (options.temporalIntent === "first") return
|
|
1935
|
-
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));
|
|
1936
2216
|
return memoryWeightAt(right, this.currentTurn) - memoryWeightAt(left, this.currentTurn) || right.updatedAt.localeCompare(left.updatedAt);
|
|
1937
2217
|
});
|
|
1938
2218
|
const participantIds = new Set(participantMatches.map(({ id }) => id));
|
|
@@ -2018,6 +2298,66 @@ var StrataGate = class _StrataGate {
|
|
|
2018
2298
|
job.updatedAt = toUtc8Iso(this.now());
|
|
2019
2299
|
});
|
|
2020
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
|
+
}
|
|
2021
2361
|
async searchElements(query, options = {}) {
|
|
2022
2362
|
const normalizedName = normalizeSearchText(options.name ?? "");
|
|
2023
2363
|
const candidates = this.elements.flatMap((element) => element.facts.map((fact) => ({
|
|
@@ -2239,7 +2579,10 @@ var StrataGate = class _StrataGate {
|
|
|
2239
2579
|
quotes: [...new Set(input.quotes ?? [])].slice(0, 12),
|
|
2240
2580
|
sourceMessageIds,
|
|
2241
2581
|
sourceBlockId: sourceBlock.id,
|
|
2242
|
-
temporal:
|
|
2582
|
+
temporal: {
|
|
2583
|
+
...input.temporal ? { ...input.temporal } : { mentionedAt: now },
|
|
2584
|
+
eventType: normalizeStandardEventType(input.temporal?.eventType)
|
|
2585
|
+
},
|
|
2243
2586
|
scope: input.scope ?? "user",
|
|
2244
2587
|
criticality,
|
|
2245
2588
|
confidence: Math.max(0, Math.min(1, input.confidence ?? 1)),
|
|
@@ -2278,7 +2621,59 @@ var StrataGate = class _StrataGate {
|
|
|
2278
2621
|
if (!job) throw new Error(`Unknown element projection: ${id}`);
|
|
2279
2622
|
return job;
|
|
2280
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
|
+
}
|
|
2281
2675
|
queueElementProjection(sourceEventIds) {
|
|
2676
|
+
if (this.disableElementProjection) return null;
|
|
2282
2677
|
const ids = [...new Set(sourceEventIds.filter((id) => this.events.some((event) => event.id === id)))];
|
|
2283
2678
|
if (ids.length === 0) return null;
|
|
2284
2679
|
const now = toUtc8Iso(this.now());
|
|
@@ -2447,7 +2842,11 @@ var StrataGate = class _StrataGate {
|
|
|
2447
2842
|
}
|
|
2448
2843
|
return this.commitMutation(() => {
|
|
2449
2844
|
const extracted = result.shouldExtract ? result.events.map((event) => this.addEventInMemory({ ...event, sourceBlockId: target.id })) : [];
|
|
2450
|
-
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
|
+
}
|
|
2451
2850
|
const job = this.extractionJobs.get(target.id);
|
|
2452
2851
|
if (!job) throw new Error(`Missing extraction job for block: ${target.id}`);
|
|
2453
2852
|
this.extractionJobs.set(target.id, {
|
|
@@ -2471,6 +2870,17 @@ var StrataGate = class _StrataGate {
|
|
|
2471
2870
|
throw error;
|
|
2472
2871
|
}
|
|
2473
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
|
+
}
|
|
2474
2884
|
async commitMutation(mutation) {
|
|
2475
2885
|
const previous = this.mutationQueue;
|
|
2476
2886
|
let release;
|
|
@@ -2514,11 +2924,15 @@ var StrataGate = class _StrataGate {
|
|
|
2514
2924
|
this.openTail.splice(0, this.openTail.length, ...copy.openTail);
|
|
2515
2925
|
this.blocks.splice(0, this.blocks.length, ...copy.blocks);
|
|
2516
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);
|
|
2517
2929
|
this.elements.splice(0, this.elements.length, ...copy.elements);
|
|
2518
2930
|
this.extractionJobs.clear();
|
|
2519
2931
|
for (const job of copy.extractionJobs) this.extractionJobs.set(job.blockId, job);
|
|
2520
2932
|
this.elementProjectionJobs.clear();
|
|
2521
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);
|
|
2522
2936
|
this.usageReceipts.clear();
|
|
2523
2937
|
for (const receipt of copy.usageReceipts) this.usageReceipts.set(receipt.id, receipt);
|
|
2524
2938
|
this.ingestionReceipts.clear();
|
|
@@ -2582,6 +2996,42 @@ var StrataGate = class _StrataGate {
|
|
|
2582
2996
|
if (!elementIds.has(elementId)) throw new Error(`Element projection ${job.id} references unknown element ${elementId}`);
|
|
2583
2997
|
}
|
|
2584
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
|
+
}
|
|
2585
3035
|
}
|
|
2586
3036
|
};
|
|
2587
3037
|
|
|
@@ -2718,10 +3168,10 @@ var CRITICALITIES = /* @__PURE__ */ new Set(["routine", "preference", "identity"
|
|
|
2718
3168
|
function object(value) {
|
|
2719
3169
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2720
3170
|
}
|
|
2721
|
-
function
|
|
3171
|
+
function strings2(value) {
|
|
2722
3172
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2723
3173
|
}
|
|
2724
|
-
function
|
|
3174
|
+
function text2(value, fallback = "") {
|
|
2725
3175
|
return typeof value === "string" ? value.trim() : fallback;
|
|
2726
3176
|
}
|
|
2727
3177
|
function l2Neighbor(block) {
|
|
@@ -2751,7 +3201,8 @@ var RETRY_MAX_TOKENS = 1e4;
|
|
|
2751
3201
|
var STRUCTURED_FIELDS = {
|
|
2752
3202
|
summarizer: ["l0Title", "l0Tags", "l1Summary", "l2Keypoints", "shouldExtract"],
|
|
2753
3203
|
extractor: ["shouldExtract", "reason", "events"],
|
|
2754
|
-
projector: ["reason", "changes"]
|
|
3204
|
+
projector: ["reason", "changes"],
|
|
3205
|
+
graphProjector: ["reason", "nodes", "edges"]
|
|
2755
3206
|
};
|
|
2756
3207
|
var STRING_ARRAY = { type: "array", items: { type: "string" } };
|
|
2757
3208
|
var OPEN_OBJECT = { type: "object", additionalProperties: true };
|
|
@@ -2817,6 +3268,47 @@ var PROJECTOR_PARAMETERS = {
|
|
|
2817
3268
|
reason: { type: "string", required: true },
|
|
2818
3269
|
changes: { type: "array", items: ELEMENT_CHANGE, required: true }
|
|
2819
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
|
+
};
|
|
2820
3312
|
var STRUCTURED_TOOLS = {
|
|
2821
3313
|
summarizer: {
|
|
2822
3314
|
name: "stratagate_summarize_block",
|
|
@@ -2832,6 +3324,11 @@ var STRUCTURED_TOOLS = {
|
|
|
2832
3324
|
name: "stratagate_project_element_cards",
|
|
2833
3325
|
description: "Submit element-card changes supported by the supplied event cards.",
|
|
2834
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
|
|
2835
3332
|
}
|
|
2836
3333
|
};
|
|
2837
3334
|
function toolSchema(kind) {
|
|
@@ -2869,10 +3366,10 @@ var DshModelBridge = class {
|
|
|
2869
3366
|
{ messages }
|
|
2870
3367
|
));
|
|
2871
3368
|
return {
|
|
2872
|
-
l0Title:
|
|
2873
|
-
l0Tags:
|
|
2874
|
-
l1Summary:
|
|
2875
|
-
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),
|
|
2876
3373
|
shouldExtract: raw.shouldExtract === true
|
|
2877
3374
|
};
|
|
2878
3375
|
};
|
|
@@ -2880,21 +3377,21 @@ var DshModelBridge = class {
|
|
|
2880
3377
|
const validMessageIds = new Set(context.target.l5Raw.map((message) => message.id));
|
|
2881
3378
|
const raw = object(await this.callStructured(
|
|
2882
3379
|
"extractor",
|
|
2883
|
-
`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.`,
|
|
2884
3381
|
extractorPayload(context)
|
|
2885
3382
|
));
|
|
2886
3383
|
const events = (Array.isArray(raw.events) ? raw.events : []).map((candidate) => {
|
|
2887
3384
|
const item = object(candidate);
|
|
2888
|
-
const sourceMessageIds =
|
|
3385
|
+
const sourceMessageIds = strings2(item.sourceMessageIds).filter((id) => validMessageIds.has(id));
|
|
2889
3386
|
const scope = SCOPES.has(item.scope) ? item.scope : "project";
|
|
2890
3387
|
const criticality = CRITICALITIES.has(item.criticality) ? item.criticality : "routine";
|
|
2891
|
-
if (!
|
|
3388
|
+
if (!text2(item.title) || !text2(item.summary) || sourceMessageIds.length === 0) return null;
|
|
2892
3389
|
return {
|
|
2893
|
-
title:
|
|
2894
|
-
summary:
|
|
2895
|
-
narrative:
|
|
2896
|
-
tags:
|
|
2897
|
-
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),
|
|
2898
3395
|
sourceMessageIds,
|
|
2899
3396
|
sourceBlockId: context.target.id,
|
|
2900
3397
|
temporal: object(item.temporal),
|
|
@@ -2905,7 +3402,7 @@ var DshModelBridge = class {
|
|
|
2905
3402
|
}).filter((event) => event !== null);
|
|
2906
3403
|
return {
|
|
2907
3404
|
shouldExtract: raw.shouldExtract === true,
|
|
2908
|
-
reason:
|
|
3405
|
+
reason: text2(raw.reason, events.length ? "Durable evidence extracted." : "No durable evidence."),
|
|
2909
3406
|
events
|
|
2910
3407
|
};
|
|
2911
3408
|
};
|
|
@@ -2920,27 +3417,82 @@ var DshModelBridge = class {
|
|
|
2920
3417
|
const item = object(candidate);
|
|
2921
3418
|
const element = object(item.element);
|
|
2922
3419
|
const type = element.type;
|
|
2923
|
-
const sourceEventIds =
|
|
3420
|
+
const sourceEventIds = strings2(item.sourceEventIds).filter((id) => eventIds.has(id));
|
|
2924
3421
|
const operation = item.operation;
|
|
2925
3422
|
const mode = item.mode;
|
|
2926
3423
|
const value = item.value;
|
|
2927
|
-
if (!
|
|
3424
|
+
if (!text2(element.name) || !ELEMENT_TYPES2.has(type) || sourceEventIds.length === 0) return [];
|
|
2928
3425
|
if (!["set_state", "add_set_item", "set_relation"].includes(String(operation))) return [];
|
|
2929
3426
|
if (!["state", "set", "relation"].includes(String(mode))) return [];
|
|
2930
3427
|
if (!(typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string"))) return [];
|
|
2931
3428
|
return [{
|
|
2932
|
-
element: { name:
|
|
3429
|
+
element: { name: text2(element.name), type, aliases: strings2(element.aliases) },
|
|
2933
3430
|
operation,
|
|
2934
|
-
key:
|
|
3431
|
+
key: text2(item.key, "state"),
|
|
2935
3432
|
mode,
|
|
2936
3433
|
value,
|
|
2937
|
-
...
|
|
2938
|
-
...
|
|
3434
|
+
...text2(item.validFrom) ? { validFrom: text2(item.validFrom) } : {},
|
|
3435
|
+
...text2(item.validTo) ? { validTo: text2(item.validTo) } : {},
|
|
2939
3436
|
sourceEventIds,
|
|
2940
3437
|
...typeof item.confidence === "number" ? { confidence: item.confidence } : {}
|
|
2941
3438
|
}];
|
|
2942
3439
|
});
|
|
2943
|
-
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 };
|
|
2944
3496
|
};
|
|
2945
3497
|
async callStructured(kind, system, payload) {
|
|
2946
3498
|
const session = this.sessions.getStore();
|
|
@@ -3063,6 +3615,7 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
3063
3615
|
import { createHash } from "node:crypto";
|
|
3064
3616
|
import { existsSync } from "node:fs";
|
|
3065
3617
|
import { resolve } from "node:path";
|
|
3618
|
+
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
3066
3619
|
|
|
3067
3620
|
// src/fold.ts
|
|
3068
3621
|
function renderBlocks(blocks) {
|
|
@@ -3116,13 +3669,13 @@ var TurnFolder = class {
|
|
|
3116
3669
|
if (event.data.source.kind !== "user") return null;
|
|
3117
3670
|
const turn = this.activeTurn.get(sessionId);
|
|
3118
3671
|
if (turn === void 0) return null;
|
|
3119
|
-
const
|
|
3120
|
-
if (
|
|
3672
|
+
const text3 = renderBlocks(event.data.content);
|
|
3673
|
+
if (text3) this.pending(sessionId, turn).user.push(text3);
|
|
3121
3674
|
return null;
|
|
3122
3675
|
}
|
|
3123
3676
|
case "assistant/message": {
|
|
3124
|
-
const
|
|
3125
|
-
if (
|
|
3677
|
+
const text3 = renderBlocks(event.data.message.content);
|
|
3678
|
+
if (text3) this.pending(sessionId, event.data.turn).assistant.push(text3);
|
|
3126
3679
|
return null;
|
|
3127
3680
|
}
|
|
3128
3681
|
case "tool/call": {
|
|
@@ -3158,7 +3711,8 @@ var TurnFolder = class {
|
|
|
3158
3711
|
threadId: sessionId,
|
|
3159
3712
|
assistantToolCalls: [...pending.tools.values()],
|
|
3160
3713
|
createdAt: toUtc8Iso(event.time),
|
|
3161
|
-
receiptId: `dsh:${sessionId}:turn:${event.data.turn}
|
|
3714
|
+
receiptId: `dsh:${sessionId}:turn:${event.data.turn}`,
|
|
3715
|
+
dshTurn: event.data.turn
|
|
3162
3716
|
};
|
|
3163
3717
|
}
|
|
3164
3718
|
default:
|
|
@@ -3238,6 +3792,7 @@ var DshMetadataStore = class {
|
|
|
3238
3792
|
var AUTO_EVENT_LIMIT = 4;
|
|
3239
3793
|
var AUTO_ELEMENT_LIMIT = 4;
|
|
3240
3794
|
var AUTO_MEMORY_TOKEN_BUDGET = 900;
|
|
3795
|
+
var COMPACTION_SOURCE_PLUGIN = "stratagate-memory";
|
|
3241
3796
|
function projectKey(cwd) {
|
|
3242
3797
|
const canonical = resolve(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
|
|
3243
3798
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
@@ -3248,21 +3803,25 @@ function workspaceDisplayName(cwd) {
|
|
|
3248
3803
|
}
|
|
3249
3804
|
var StrataGateRuntime = class {
|
|
3250
3805
|
constructor(config, models, onIngestError = () => {
|
|
3806
|
+
}, flushNativeSession = async () => {
|
|
3251
3807
|
}) {
|
|
3252
3808
|
this.config = config;
|
|
3253
3809
|
this.models = models;
|
|
3254
3810
|
this.onIngestError = onIngestError;
|
|
3811
|
+
this.flushNativeSession = flushNativeSession;
|
|
3255
3812
|
this.blockDecayLambda = config.blockDecayLambda;
|
|
3256
3813
|
}
|
|
3257
3814
|
config;
|
|
3258
3815
|
models;
|
|
3259
3816
|
onIngestError;
|
|
3817
|
+
flushNativeSession;
|
|
3260
3818
|
folder = new TurnFolder();
|
|
3261
3819
|
spaces = /* @__PURE__ */ new Map();
|
|
3262
3820
|
batches = /* @__PURE__ */ new Map();
|
|
3263
3821
|
adopted = /* @__PURE__ */ new Map();
|
|
3264
3822
|
pendingUse = /* @__PURE__ */ new Set();
|
|
3265
3823
|
workspaceNames = /* @__PURE__ */ new Map();
|
|
3824
|
+
migrationTimers = /* @__PURE__ */ new Map();
|
|
3266
3825
|
ingestTail = Promise.resolve();
|
|
3267
3826
|
settingsTail = Promise.resolve();
|
|
3268
3827
|
batchSequence = 0;
|
|
@@ -3278,7 +3837,15 @@ var StrataGateRuntime = class {
|
|
|
3278
3837
|
}).then(async () => {
|
|
3279
3838
|
const memory = await this.space(session);
|
|
3280
3839
|
try {
|
|
3281
|
-
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
|
+
}
|
|
3282
3849
|
} finally {
|
|
3283
3850
|
await this.persistSuccessfulResponses(memory);
|
|
3284
3851
|
}
|
|
@@ -3436,23 +4003,78 @@ var StrataGateRuntime = class {
|
|
|
3436
4003
|
await this.flush();
|
|
3437
4004
|
const memory = await this.space(session);
|
|
3438
4005
|
const threadId = String(session.id);
|
|
4006
|
+
const blockContexts = memory.getBlockContext(threadId);
|
|
4007
|
+
if (this.syncDecayedBlockSurface(session, blockContexts)) {
|
|
4008
|
+
await this.flushNativeSession(session);
|
|
4009
|
+
}
|
|
3439
4010
|
const openTail = memory.listOpenTail(threadId);
|
|
3440
4011
|
const activationQuery = [currentUserMessage(session), renderMessages(recentTurns(openTail, 2))].filter(Boolean).join("\n\n");
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
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;
|
|
3456
4078
|
}
|
|
3457
4079
|
// Keep the ingestion error for callers that explicitly require a flushed run.
|
|
3458
4080
|
async settleIngestion() {
|
|
@@ -3464,6 +4086,8 @@ var StrataGateRuntime = class {
|
|
|
3464
4086
|
async close() {
|
|
3465
4087
|
if (this.closed) return;
|
|
3466
4088
|
this.closed = true;
|
|
4089
|
+
for (const timer of this.migrationTimers.values()) clearTimeout(timer);
|
|
4090
|
+
this.migrationTimers.clear();
|
|
3467
4091
|
let flushError;
|
|
3468
4092
|
try {
|
|
3469
4093
|
await this.flush();
|
|
@@ -3564,7 +4188,8 @@ var StrataGateRuntime = class {
|
|
|
3564
4188
|
blockDecayLambda: this.blockDecayLambda,
|
|
3565
4189
|
summarizer: this.models.summarizer,
|
|
3566
4190
|
extractor: this.models.extractor,
|
|
3567
|
-
|
|
4191
|
+
graphProjector: this.models.graphProjector,
|
|
4192
|
+
disableElementProjection: true
|
|
3568
4193
|
});
|
|
3569
4194
|
try {
|
|
3570
4195
|
return await memory.expandBlock(id, target, "user");
|
|
@@ -3620,14 +4245,28 @@ var StrataGateRuntime = class {
|
|
|
3620
4245
|
blockDecayLambda: this.blockDecayLambda,
|
|
3621
4246
|
summarizer: this.models.summarizer,
|
|
3622
4247
|
extractor: this.models.extractor,
|
|
3623
|
-
|
|
4248
|
+
graphProjector: this.models.graphProjector,
|
|
4249
|
+
disableElementProjection: true
|
|
3624
4250
|
}).then(async (memory) => {
|
|
3625
4251
|
try {
|
|
3626
4252
|
try {
|
|
3627
|
-
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
|
+
}
|
|
3628
4266
|
} finally {
|
|
3629
4267
|
await this.persistSuccessfulResponses(memory);
|
|
3630
4268
|
}
|
|
4269
|
+
this.scheduleGraphMigration(session, memory);
|
|
3631
4270
|
return memory;
|
|
3632
4271
|
} catch (error) {
|
|
3633
4272
|
await memory.close().catch(() => {
|
|
@@ -3642,6 +4281,46 @@ var StrataGateRuntime = class {
|
|
|
3642
4281
|
}
|
|
3643
4282
|
return opening;
|
|
3644
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
|
+
}
|
|
3645
4324
|
rememberWorkspace(namespace, cwd) {
|
|
3646
4325
|
const name2 = workspaceDisplayName(cwd);
|
|
3647
4326
|
this.workspaceNames.set(namespace, name2);
|
|
@@ -3710,12 +4389,75 @@ function renderMessages(messages) {
|
|
|
3710
4389
|
return details.join("\n");
|
|
3711
4390
|
}).join("\n\n");
|
|
3712
4391
|
}
|
|
3713
|
-
function
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
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;
|
|
3719
4461
|
}
|
|
3720
4462
|
function activatedEvents(memory, relevance) {
|
|
3721
4463
|
const allowed = new Map(relevance.map(({ event }) => [event.id, event]));
|
|
@@ -3759,7 +4501,7 @@ function activatedElements(memory, relevance) {
|
|
|
3759
4501
|
weight
|
|
3760
4502
|
]).map(({ item }) => item);
|
|
3761
4503
|
}
|
|
3762
|
-
function renderActivatedMemory(events,
|
|
4504
|
+
function renderActivatedMemory(events, graphNodes) {
|
|
3763
4505
|
const heading = [
|
|
3764
4506
|
"[Activated long-term memory]",
|
|
3765
4507
|
"Historical memory context.",
|
|
@@ -3769,7 +4511,7 @@ function renderActivatedMemory(events, elements) {
|
|
|
3769
4511
|
const lines = [...heading];
|
|
3770
4512
|
let tokens = estimateTokens(lines.join("\n"));
|
|
3771
4513
|
let eventCount = 0;
|
|
3772
|
-
let
|
|
4514
|
+
let nodeCount = 0;
|
|
3773
4515
|
for (const event of events) {
|
|
3774
4516
|
const rendered = JSON.stringify({
|
|
3775
4517
|
id: event.id,
|
|
@@ -3788,25 +4530,24 @@ Events:
|
|
|
3788
4530
|
tokens += cost;
|
|
3789
4531
|
eventCount += 1;
|
|
3790
4532
|
}
|
|
3791
|
-
for (const
|
|
4533
|
+
for (const node of graphNodes) {
|
|
3792
4534
|
const rendered = JSON.stringify({
|
|
3793
|
-
|
|
3794
|
-
name:
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
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 }))
|
|
3799
4540
|
});
|
|
3800
4541
|
const cost = estimateTokens(`
|
|
3801
|
-
|
|
4542
|
+
KnowledgeGraph:
|
|
3802
4543
|
- ${rendered}`);
|
|
3803
4544
|
if (tokens + cost > AUTO_MEMORY_TOKEN_BUDGET) break;
|
|
3804
|
-
if (
|
|
4545
|
+
if (nodeCount === 0) lines.push("KnowledgeGraph:");
|
|
3805
4546
|
lines.push(`- ${rendered}`);
|
|
3806
4547
|
tokens += cost;
|
|
3807
|
-
|
|
4548
|
+
nodeCount += 1;
|
|
3808
4549
|
}
|
|
3809
|
-
if (eventCount === 0 &&
|
|
4550
|
+
if (eventCount === 0 && nodeCount === 0) lines.push("(no activated memory)");
|
|
3810
4551
|
return lines.join("\n");
|
|
3811
4552
|
}
|
|
3812
4553
|
function estimateTokens(value) {
|
|
@@ -3866,9 +4607,26 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3866
4607
|
...args.participants ? { participants: args.participants } : {}
|
|
3867
4608
|
})
|
|
3868
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
|
+
}));
|
|
3869
4627
|
ctx.tools.register(defineTool({
|
|
3870
4628
|
name: "memory_search_elements",
|
|
3871
|
-
description: "
|
|
4629
|
+
description: "Deprecated compatibility search for legacy Element-card data. Prefer memory_search_graph.",
|
|
3872
4630
|
parameters: {
|
|
3873
4631
|
query: { type: "string", required: true },
|
|
3874
4632
|
limit: { type: "integer" },
|
|
@@ -3938,7 +4696,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3938
4696
|
missing: { type: "string", required: true },
|
|
3939
4697
|
next_strategy: {
|
|
3940
4698
|
type: "string",
|
|
3941
|
-
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"],
|
|
3942
4700
|
required: true
|
|
3943
4701
|
}
|
|
3944
4702
|
},
|
|
@@ -3962,7 +4720,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3962
4720
|
|
|
3963
4721
|
// src/web.ts
|
|
3964
4722
|
import { createRequire } from "node:module";
|
|
3965
|
-
var STRATAGATE_DSH_VERSION = "0.2.
|
|
4723
|
+
var STRATAGATE_DSH_VERSION = "0.2.22";
|
|
3966
4724
|
var LEGACY_THREAD_ID = "__legacy__";
|
|
3967
4725
|
var nodeRequire = createRequire(import.meta.url);
|
|
3968
4726
|
function installedPackageVersion(names) {
|
|
@@ -3985,8 +4743,8 @@ function numeric(value, fallback, minimum, maximum) {
|
|
|
3985
4743
|
const parsed = Number(value);
|
|
3986
4744
|
return Number.isFinite(parsed) ? Math.min(maximum, Math.max(minimum, Math.floor(parsed))) : fallback;
|
|
3987
4745
|
}
|
|
3988
|
-
function redact(
|
|
3989
|
-
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]");
|
|
3990
4748
|
}
|
|
3991
4749
|
function redactValue(value) {
|
|
3992
4750
|
if (typeof value === "string") return redact(value);
|
|
@@ -4033,6 +4791,7 @@ function eventSummary(event) {
|
|
|
4033
4791
|
id: event.id,
|
|
4034
4792
|
title: event.title,
|
|
4035
4793
|
summary: event.summary,
|
|
4794
|
+
narrative: event.narrative,
|
|
4036
4795
|
tags: event.tags,
|
|
4037
4796
|
sourceBlockId: event.sourceBlockId,
|
|
4038
4797
|
sourceMessageIds: event.sourceMessageIds,
|
|
@@ -4084,8 +4843,8 @@ async function overview(runtime) {
|
|
|
4084
4843
|
for (const namespace of namespaces) {
|
|
4085
4844
|
const snapshot = await runtime.adminSnapshot(namespace);
|
|
4086
4845
|
if (!snapshot) continue;
|
|
4087
|
-
const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.
|
|
4088
|
-
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;
|
|
4089
4848
|
const failedJobDetails = [
|
|
4090
4849
|
...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => ({
|
|
4091
4850
|
id: job.blockId,
|
|
@@ -4095,9 +4854,9 @@ async function overview(runtime) {
|
|
|
4095
4854
|
lastErrorFull: job.lastError,
|
|
4096
4855
|
updatedAt: job.updatedAt
|
|
4097
4856
|
})),
|
|
4098
|
-
...snapshot.
|
|
4857
|
+
...snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").map((job) => ({
|
|
4099
4858
|
id: job.id,
|
|
4100
|
-
kind: "
|
|
4859
|
+
kind: "graph-projection",
|
|
4101
4860
|
attempts: job.attempts,
|
|
4102
4861
|
lastError: job.lastError?.slice(0, 500) ?? null,
|
|
4103
4862
|
lastErrorFull: job.lastError,
|
|
@@ -4108,6 +4867,7 @@ async function overview(runtime) {
|
|
|
4108
4867
|
...snapshot.blocks.map(({ createdAt }) => createdAt),
|
|
4109
4868
|
...snapshot.events.map(({ updatedAt }) => updatedAt),
|
|
4110
4869
|
...snapshot.elements.map(({ updatedAt }) => updatedAt),
|
|
4870
|
+
...snapshot.graphNodes.map(({ updatedAt }) => updatedAt),
|
|
4111
4871
|
...snapshot.usageReceipts.map(({ createdAt }) => createdAt)
|
|
4112
4872
|
].sort();
|
|
4113
4873
|
rows.push({
|
|
@@ -4122,6 +4882,15 @@ async function overview(runtime) {
|
|
|
4122
4882
|
events: snapshot.events.length,
|
|
4123
4883
|
activeEvents: snapshot.events.filter(({ status }) => status === "active").length,
|
|
4124
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
|
+
})(),
|
|
4125
4894
|
usageReceipts: snapshot.usageReceipts.length,
|
|
4126
4895
|
memoryUseCount: snapshot.usageReceipts.filter((receipt) => receipt.eventIds.length > 0 || receipt.elementIds.length > 0).length,
|
|
4127
4896
|
failedJobs,
|
|
@@ -4276,11 +5045,38 @@ async function memories(runtime, url) {
|
|
|
4276
5045
|
const offset = numeric(url.searchParams.get("offset"), 0, 0, Number.MAX_SAFE_INTEGER);
|
|
4277
5046
|
const limit = numeric(url.searchParams.get("limit"), 100, 1, 200);
|
|
4278
5047
|
let values;
|
|
4279
|
-
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) => ({
|
|
4280
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 })),
|
|
4281
5054
|
relatedElements: snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.includes(event.id)).map(({ id, name: name2 }) => ({ id, name: name2 }))
|
|
4282
5055
|
}));
|
|
4283
|
-
else if (kind === "
|
|
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);
|
|
4284
5080
|
else if (kind === "blocks") {
|
|
4285
5081
|
const recovered = recoverSnapshotView(snapshot);
|
|
4286
5082
|
const conversations = conversationRows(snapshot, recovered);
|
|
@@ -4293,8 +5089,8 @@ async function memories(runtime, url) {
|
|
|
4293
5089
|
const blockMessageIds = new Set(block.messages.map(({ id }) => id));
|
|
4294
5090
|
const relatedEvents = snapshot.events.filter((event) => event.sourceBlockId === source.id && (!block.virtual || event.sourceMessageIds.some((id) => blockMessageIds.has(id))));
|
|
4295
5091
|
const eventIds = new Set(relatedEvents.map(({ id }) => id));
|
|
4296
|
-
const projections = snapshot.
|
|
4297
|
-
const
|
|
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 }));
|
|
4298
5094
|
const failedProjection = projections.find(({ status: status2 }) => status2 === "failed");
|
|
4299
5095
|
const pendingProjection = projections.some(({ status: status2 }) => status2 === "pending" || status2 === "running");
|
|
4300
5096
|
const needsExtraction = source.shouldExtract === true;
|
|
@@ -4331,13 +5127,13 @@ async function memories(runtime, url) {
|
|
|
4331
5127
|
updatedAt: extraction.updatedAt,
|
|
4332
5128
|
lastError: extraction.lastError
|
|
4333
5129
|
} : null,
|
|
4334
|
-
|
|
5130
|
+
graphProjection: projections.length ? {
|
|
4335
5131
|
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
4336
5132
|
jobs: projections.length,
|
|
4337
5133
|
lastError: failedProjection?.lastError ?? null
|
|
4338
5134
|
} : null,
|
|
4339
5135
|
relatedEvents: relatedEvents.map(eventSummary),
|
|
4340
|
-
|
|
5136
|
+
relatedNodes
|
|
4341
5137
|
};
|
|
4342
5138
|
});
|
|
4343
5139
|
const filtered2 = values.filter((value) => matchesQuery(value, query));
|
|
@@ -4368,6 +5164,7 @@ async function sources(runtime, url) {
|
|
|
4368
5164
|
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
4369
5165
|
const snapshot = await requiredSnapshot(runtime, namespace);
|
|
4370
5166
|
const eventId = url.searchParams.get("eventId");
|
|
5167
|
+
const nodeId = url.searchParams.get("nodeId");
|
|
4371
5168
|
const elementId = url.searchParams.get("elementId");
|
|
4372
5169
|
const blockId = url.searchParams.get("blockId");
|
|
4373
5170
|
let events = [];
|
|
@@ -4378,6 +5175,18 @@ async function sources(runtime, url) {
|
|
|
4378
5175
|
if (!event) throw new AdminHttpError(404, `Unknown event: ${eventId}`);
|
|
4379
5176
|
events = [event];
|
|
4380
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
|
+
};
|
|
4381
5190
|
} else if (elementId) {
|
|
4382
5191
|
const element = snapshot.elements.find(({ id }) => id === elementId);
|
|
4383
5192
|
if (!element) throw new AdminHttpError(404, `Unknown element: ${elementId}`);
|
|
@@ -4402,12 +5211,13 @@ async function sources(runtime, url) {
|
|
|
4402
5211
|
virtual: displayBlock?.virtual ?? false
|
|
4403
5212
|
};
|
|
4404
5213
|
} else {
|
|
4405
|
-
throw new AdminHttpError(400, "eventId, elementId, or blockId is required");
|
|
5214
|
+
throw new AdminHttpError(400, "eventId, nodeId, elementId, or blockId is required");
|
|
4406
5215
|
}
|
|
4407
5216
|
return {
|
|
4408
5217
|
namespace,
|
|
4409
5218
|
events: events.map(eventSummary),
|
|
4410
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 })),
|
|
4411
5221
|
messages: sourceMessages(snapshot, ids)
|
|
4412
5222
|
};
|
|
4413
5223
|
}
|
|
@@ -4488,7 +5298,7 @@ var MEMORY_PROTOCOL = `[StrataGate memory protocol]
|
|
|
4488
5298
|
StrataGate provides durable, evidence-gated memory through memory_* tools.
|
|
4489
5299
|
|
|
4490
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.
|
|
4491
|
-
- 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.
|
|
4492
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.
|
|
4493
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.
|
|
4494
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.
|
|
@@ -4502,6 +5312,8 @@ async function apply(ctx, config) {
|
|
|
4502
5312
|
const models = new DshModelBridge(ctx, resolved);
|
|
4503
5313
|
const runtime = new StrataGateRuntime(resolved, models, (error) => {
|
|
4504
5314
|
ctx.logger.error(`stratagate-memory ingestion failed: ${renderError(error)}`);
|
|
5315
|
+
}, async (session) => {
|
|
5316
|
+
await ctx.sessions.flush(session);
|
|
4505
5317
|
});
|
|
4506
5318
|
await runtime.syncConfiguredSettings();
|
|
4507
5319
|
ctx.systemPrompt.section({ name: "tool:stratagate-memory", order: 113, text: MEMORY_PROTOCOL });
|
|
@@ -4510,10 +5322,10 @@ async function apply(ctx, config) {
|
|
|
4510
5322
|
const session = context.agent?.session;
|
|
4511
5323
|
if (!session) return assembled;
|
|
4512
5324
|
try {
|
|
4513
|
-
const
|
|
5325
|
+
const text3 = await runtime.buildAutoContext(session);
|
|
4514
5326
|
return {
|
|
4515
5327
|
...assembled,
|
|
4516
|
-
contexts: [...assembled.contexts, { name: "stratagate:auto-memory", text:
|
|
5328
|
+
contexts: [...assembled.contexts, { name: "stratagate:auto-memory", text: text3 }]
|
|
4517
5329
|
};
|
|
4518
5330
|
} catch (error) {
|
|
4519
5331
|
ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
|
|
@@ -4522,7 +5334,7 @@ async function apply(ctx, config) {
|
|
|
4522
5334
|
});
|
|
4523
5335
|
ctx.on("agent/turn-stopping", ({ agent }) => {
|
|
4524
5336
|
if (!runtime.needsRecordUse(agent.session)) return;
|
|
4525
|
-
agent.steer(
|
|
5337
|
+
agent.steer(createUserMessage3({
|
|
4526
5338
|
content: [{
|
|
4527
5339
|
type: "text",
|
|
4528
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."
|