stratagate-dsh 0.3.4 → 0.3.6
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 +19 -0
- package/README.md +111 -64
- package/README.zh-CN.md +113 -66
- package/dist/client.js +3 -2
- package/dist/plugin.js +750 -464
- package/dist/plugin.js.map +1 -1
- package/docs/DSH.md +3 -1
- package/docs/DSH.zh-CN.md +5 -1
- package/docs/MEMORY_TOPICS.zh-CN.md +2 -2
- package/docs/assets/stratagate-long-term-memory-views-en.png +0 -0
- package/docs/assets/stratagate-long-term-memory-views-zh.png +0 -0
- package/docs/assets/stratagate-memory-workflow-en-v030.png +0 -0
- package/docs/assets/stratagate-memory-workflow-zh-v030.jpg +0 -0
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -169,14 +169,14 @@ function resultSummary(value) {
|
|
|
169
169
|
}
|
|
170
170
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
171
171
|
if (!value || typeof value !== "object") return "";
|
|
172
|
-
const
|
|
173
|
-
if (typeof
|
|
174
|
-
if (typeof
|
|
175
|
-
if (typeof
|
|
176
|
-
const counts = Object.entries(
|
|
172
|
+
const record3 = value;
|
|
173
|
+
if (typeof record3.error === "string") return `failed: ${record3.error.replace(/\s+/g, " ").slice(0, 140)}`;
|
|
174
|
+
if (typeof record3.summary === "string") return record3.summary.replace(/\s+/g, " ").slice(0, 160);
|
|
175
|
+
if (typeof record3.message === "string") return record3.message.replace(/\s+/g, " ").slice(0, 160);
|
|
176
|
+
const counts = Object.entries(record3).filter(([key2, item]) => !["arguments", "params", "input", "request"].includes(key2) && Array.isArray(item)).map(([key2, item]) => `${key2}: ${item.length}`);
|
|
177
177
|
if (counts.length > 0) return counts.join(", ").slice(0, 160);
|
|
178
|
-
if (
|
|
179
|
-
if (
|
|
178
|
+
if (record3.ok === true) return "completed";
|
|
179
|
+
if (record3.ok === false) return "not completed";
|
|
180
180
|
return "structured result returned";
|
|
181
181
|
}
|
|
182
182
|
function serializedValue(value) {
|
|
@@ -190,15 +190,15 @@ function serializedValue(value) {
|
|
|
190
190
|
function sourceFieldName(value) {
|
|
191
191
|
return DERIVATION_SOURCE_FIELD.test(value.replace(/[\s_-]+/gu, "").toLocaleLowerCase());
|
|
192
192
|
}
|
|
193
|
-
function omitLargeSourceFields(value,
|
|
193
|
+
function omitLargeSourceFields(value, key2 = "", seen = /* @__PURE__ */ new WeakSet()) {
|
|
194
194
|
if (typeof value === "string") {
|
|
195
|
-
if (!sourceFieldName(
|
|
196
|
-
return `[${
|
|
195
|
+
if (!sourceFieldName(key2) || value.length <= DERIVATION_SOURCE_FIELD_MIN_CHARS) return value;
|
|
196
|
+
return `[${key2 || "source"} omitted: ${value.length} chars; full value remains in L5]`;
|
|
197
197
|
}
|
|
198
198
|
if (!value || typeof value !== "object") return value;
|
|
199
199
|
if (seen.has(value)) return "[circular value omitted; full value remains in L5]";
|
|
200
200
|
seen.add(value);
|
|
201
|
-
if (Array.isArray(value)) return value.map((item) => omitLargeSourceFields(item,
|
|
201
|
+
if (Array.isArray(value)) return value.map((item) => omitLargeSourceFields(item, key2, seen));
|
|
202
202
|
return Object.fromEntries(Object.entries(value).map(([nestedKey, nestedValue]) => [nestedKey, omitLargeSourceFields(nestedValue, nestedKey, seen)]));
|
|
203
203
|
}
|
|
204
204
|
function compactDerivationValue(value, maxChars, label) {
|
|
@@ -261,14 +261,14 @@ function summarizeToolJson(value) {
|
|
|
261
261
|
item.forEach(visit);
|
|
262
262
|
return;
|
|
263
263
|
}
|
|
264
|
-
const
|
|
265
|
-
if (typeof
|
|
266
|
-
if (typeof
|
|
267
|
-
if (
|
|
268
|
-
const name2 =
|
|
264
|
+
const record3 = item;
|
|
265
|
+
if (typeof record3.name === "string" && ("arguments" in record3 || "parameters" in record3)) names.add(record3.name);
|
|
266
|
+
if (typeof record3.tool === "string") names.add(record3.tool);
|
|
267
|
+
if (record3.function && typeof record3.function === "object") {
|
|
268
|
+
const name2 = record3.function.name;
|
|
269
269
|
if (typeof name2 === "string") names.add(name2);
|
|
270
270
|
}
|
|
271
|
-
Object.values(
|
|
271
|
+
Object.values(record3).forEach(visit);
|
|
272
272
|
};
|
|
273
273
|
visit(parsed);
|
|
274
274
|
if (names.size === 0) return null;
|
|
@@ -358,9 +358,9 @@ function condenseTranscript(messages) {
|
|
|
358
358
|
];
|
|
359
359
|
const rendered = parts.map((part) => {
|
|
360
360
|
if (!part.pasteCandidate) return part.text;
|
|
361
|
-
const
|
|
362
|
-
if (!seen.has(
|
|
363
|
-
seen.add(
|
|
361
|
+
const key2 = normalizedParagraph(part.text);
|
|
362
|
+
if (!seen.has(key2)) {
|
|
363
|
+
seen.add(key2);
|
|
364
364
|
return part.text;
|
|
365
365
|
}
|
|
366
366
|
return REPEATED_PASTE_MARKER;
|
|
@@ -483,12 +483,12 @@ function renderCurrentState(facts) {
|
|
|
483
483
|
return facts.map((fact) => `${factText(fact)}${fact.status === "disputed" ? " [disputed]" : ""}`).join("\n");
|
|
484
484
|
}
|
|
485
485
|
var EXPOSED_EVENT_STATUSES = /* @__PURE__ */ new Set(["active", "superseded"]);
|
|
486
|
-
function exposedSources(
|
|
487
|
-
return unique((
|
|
486
|
+
function exposedSources(ids2, eventById) {
|
|
487
|
+
return unique((ids2 ?? []).filter((id) => EXPOSED_EVENT_STATUSES.has(eventById.get(id)?.status ?? "forgotten")));
|
|
488
488
|
}
|
|
489
|
-
function metadataEntries(
|
|
489
|
+
function metadataEntries(entries2, allowedValues, eventById) {
|
|
490
490
|
const allowed = new Set(allowedValues.map((value) => normalizeSearchText(value)));
|
|
491
|
-
return (
|
|
491
|
+
return (entries2 ?? []).flatMap((entry) => {
|
|
492
492
|
const value = typeof entry.value === "string" ? entry.value.trim() : "";
|
|
493
493
|
const sourceEventIds = exposedSources(entry.sourceEventIds, eventById);
|
|
494
494
|
return value && allowed.has(normalizeSearchText(value)) && sourceEventIds.length > 0 ? [{ value, sourceEventIds }] : [];
|
|
@@ -518,21 +518,21 @@ function effectiveMetadata(node, eventById) {
|
|
|
518
518
|
function effectiveGraphNodeView(node, edges, events) {
|
|
519
519
|
if (node.status !== "active" && node.status !== "disputed") return null;
|
|
520
520
|
const eventById = new Map(events.map((event) => [event.id, event]));
|
|
521
|
-
const sources2 = (
|
|
521
|
+
const sources2 = (ids2, statuses) => unique((ids2 ?? []).filter((id) => {
|
|
522
522
|
const event = eventById.get(id);
|
|
523
523
|
return event !== void 0 && statuses.has(event.status);
|
|
524
524
|
}));
|
|
525
525
|
const activeStatuses = /* @__PURE__ */ new Set(["active"]);
|
|
526
526
|
const visibleStatuses = /* @__PURE__ */ new Set(["active", "superseded"]);
|
|
527
|
-
const splitRecord = (
|
|
528
|
-
if (
|
|
529
|
-
const activeSources = sources2(
|
|
530
|
-
const visibleSources = sources2(
|
|
531
|
-
if ((
|
|
532
|
-
return { current: { ...
|
|
527
|
+
const splitRecord = (record3) => {
|
|
528
|
+
if (record3.status === "archived") return {};
|
|
529
|
+
const activeSources = sources2(record3.sourceEventIds, activeStatuses);
|
|
530
|
+
const visibleSources = sources2(record3.sourceEventIds, visibleStatuses);
|
|
531
|
+
if ((record3.status === "active" || record3.status === "disputed") && activeSources.length > 0) {
|
|
532
|
+
return { current: { ...record3, sourceEventIds: activeSources } };
|
|
533
533
|
}
|
|
534
|
-
if (visibleSources.length > 0 &&
|
|
535
|
-
return { historical: { ...
|
|
534
|
+
if (visibleSources.length > 0 && record3.status === "superseded") {
|
|
535
|
+
return { historical: { ...record3, sourceEventIds: visibleSources } };
|
|
536
536
|
}
|
|
537
537
|
return {};
|
|
538
538
|
};
|
|
@@ -568,14 +568,14 @@ function effectiveGraphNodeView(node, edges, events) {
|
|
|
568
568
|
};
|
|
569
569
|
}
|
|
570
570
|
function boundEffectiveGraphNodeView(view, eventIds, options = {}) {
|
|
571
|
-
const boundRecord = (
|
|
572
|
-
const sourceEventIds =
|
|
573
|
-
return sourceEventIds.length > 0 ? { ...
|
|
571
|
+
const boundRecord = (record3) => {
|
|
572
|
+
const sourceEventIds = record3.sourceEventIds.filter((id) => eventIds.has(id));
|
|
573
|
+
return sourceEventIds.length > 0 ? { ...record3, sourceEventIds } : null;
|
|
574
574
|
};
|
|
575
|
-
const currentFacts = view.currentFacts.flatMap((
|
|
576
|
-
const historicalFacts = view.historicalFacts.flatMap((
|
|
577
|
-
const currentEdges = view.currentEdges.flatMap((
|
|
578
|
-
const historicalEdges = view.historicalEdges.flatMap((
|
|
575
|
+
const currentFacts = view.currentFacts.flatMap((record3) => boundRecord(record3) ?? []);
|
|
576
|
+
const historicalFacts = view.historicalFacts.flatMap((record3) => boundRecord(record3) ?? []);
|
|
577
|
+
const currentEdges = view.currentEdges.flatMap((record3) => boundRecord(record3) ?? []);
|
|
578
|
+
const historicalEdges = view.historicalEdges.flatMap((record3) => boundRecord(record3) ?? []);
|
|
579
579
|
const metadata = view.node.metadataProvenance ? (() => {
|
|
580
580
|
const name2 = (view.node.metadataProvenance?.name ?? []).filter((id) => eventIds.has(id));
|
|
581
581
|
const aliases = (view.node.metadataProvenance?.aliases ?? []).flatMap((entry) => {
|
|
@@ -630,16 +630,16 @@ function strings(value, limit = 32) {
|
|
|
630
630
|
function confidence(value) {
|
|
631
631
|
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0.8;
|
|
632
632
|
}
|
|
633
|
-
function chronology(events,
|
|
634
|
-
return
|
|
633
|
+
function chronology(events, ids2, fallback) {
|
|
634
|
+
return ids2.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;
|
|
635
635
|
}
|
|
636
636
|
function entityMatchKind(node, proposal, eventById) {
|
|
637
637
|
if (node.type !== proposal.type) return null;
|
|
638
|
-
const
|
|
638
|
+
const key2 = (value) => normalizeSearchText(value).replace(/[\s_-]+/g, "");
|
|
639
639
|
const metadata = effectiveMetadata(node, eventById);
|
|
640
|
-
const proposalNames = new Set([proposal.name, ...proposal.aliases ?? []].map(
|
|
641
|
-
if (proposalNames.has(
|
|
642
|
-
if (metadata.aliases.some((value) => proposalNames.has(
|
|
640
|
+
const proposalNames = new Set([proposal.name, ...proposal.aliases ?? []].map(key2));
|
|
641
|
+
if (proposalNames.has(key2(metadata.name))) return "canonical-name";
|
|
642
|
+
if (metadata.aliases.some((value) => proposalNames.has(key2(value)))) return "alias";
|
|
643
643
|
return null;
|
|
644
644
|
}
|
|
645
645
|
function applyGraphProjection(options) {
|
|
@@ -652,9 +652,9 @@ function applyGraphProjection(options) {
|
|
|
652
652
|
const requested = strings(value, 64);
|
|
653
653
|
return requested.length > 0 && requested.every((id) => options.allowedEventIds.has(id) && EXPOSED_EVENT_STATUSES.has(eventById.get(id)?.status ?? "forgotten")) ? requested : [];
|
|
654
654
|
};
|
|
655
|
-
const activeSources = (
|
|
656
|
-
const metadataEntriesFromProjection = (
|
|
657
|
-
const byValue = new Map((
|
|
655
|
+
const activeSources = (ids2) => ids2.filter((id) => eventById.get(id)?.status === "active");
|
|
656
|
+
const metadataEntriesFromProjection = (entries2, fallbackValues) => {
|
|
657
|
+
const byValue = new Map((entries2 ?? []).map((entry) => [normalizeSearchText(entry.value), entry]));
|
|
658
658
|
return fallbackValues.flatMap((value) => {
|
|
659
659
|
const entry = byValue.get(normalizeSearchText(value));
|
|
660
660
|
const sourceEventIds = validSources(entry?.sourceEventIds);
|
|
@@ -748,26 +748,26 @@ function applyGraphProjection(options) {
|
|
|
748
748
|
...Array.isArray(proposal.facts) ? proposal.facts : []
|
|
749
749
|
];
|
|
750
750
|
for (const rawFact of facts) {
|
|
751
|
-
const
|
|
751
|
+
const key2 = text(rawFact.key, 160);
|
|
752
752
|
const value = Array.isArray(rawFact.value) ? strings(rawFact.value, 40) : text(rawFact.value, 1200);
|
|
753
|
-
if (!
|
|
753
|
+
if (!key2 || (Array.isArray(value) ? value.length === 0 : !value)) continue;
|
|
754
754
|
const factSources = "sourceEventIds" in rawFact ? validSources(rawFact.sourceEventIds) : sources2;
|
|
755
755
|
if (factSources.length === 0) {
|
|
756
|
-
warnings.push(`Dropped fact "${
|
|
756
|
+
warnings.push(`Dropped fact "${key2}" because it lacks valid exposable Event provenance.`);
|
|
757
757
|
continue;
|
|
758
758
|
}
|
|
759
759
|
if ((node.status === "active" || node.status === "disputed") && activeSources(factSources).length === 0) {
|
|
760
|
-
warnings.push(`Dropped current fact "${
|
|
760
|
+
warnings.push(`Dropped current fact "${key2}" because it has no active Event provenance.`);
|
|
761
761
|
continue;
|
|
762
762
|
}
|
|
763
|
-
for (const old of node.facts.filter((fact2) => fact2.status === "active" && fact2.key ===
|
|
763
|
+
for (const old of node.facts.filter((fact2) => fact2.status === "active" && fact2.key === key2)) {
|
|
764
764
|
old.status = "superseded";
|
|
765
765
|
if (!old.validTo) old.validTo = validFrom;
|
|
766
766
|
old.updatedAt = options.now;
|
|
767
767
|
}
|
|
768
768
|
const fact = {
|
|
769
769
|
id: options.idFactory("gfact"),
|
|
770
|
-
key,
|
|
770
|
+
key: key2,
|
|
771
771
|
value,
|
|
772
772
|
status: node.status,
|
|
773
773
|
validFrom,
|
|
@@ -1519,9 +1519,9 @@ function parseTemporal(value) {
|
|
|
1519
1519
|
const raw = record(value);
|
|
1520
1520
|
if (!raw) return void 0;
|
|
1521
1521
|
const temporal = {};
|
|
1522
|
-
for (const
|
|
1523
|
-
const parsed = optionalString(raw[
|
|
1524
|
-
if (parsed) temporal[
|
|
1522
|
+
for (const key2 of ["mentionedAt", "happenedStart", "happenedEnd", "originalText"]) {
|
|
1523
|
+
const parsed = optionalString(raw[key2], 200);
|
|
1524
|
+
if (parsed) temporal[key2] = parsed;
|
|
1525
1525
|
}
|
|
1526
1526
|
const precision = oneOf(raw.precision, ["instant", "day", "month", "year", "range", "unknown"]);
|
|
1527
1527
|
const basis = oneOf(raw.basis, ["explicit", "relative", "inferred", "unknown"]);
|
|
@@ -1909,8 +1909,8 @@ function normalizeSnapshot(value) {
|
|
|
1909
1909
|
throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
|
|
1910
1910
|
}
|
|
1911
1911
|
if (!Array.isArray(snapshot.externalMemoryImportJobs)) snapshot.externalMemoryImportJobs = [];
|
|
1912
|
-
for (const
|
|
1913
|
-
if (!Array.isArray(snapshot[
|
|
1912
|
+
for (const key2 of ["openTail", "blocks", "summaryJobs", "events", "agentEvents", "graphNodes", "graphEdges", "graphProjectionJobs", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts", "externalMemoryImportJobs"]) {
|
|
1913
|
+
if (!Array.isArray(snapshot[key2])) throw new TypeError(`Invalid StrataGate snapshot: ${key2} must be an array`);
|
|
1914
1914
|
}
|
|
1915
1915
|
if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
|
|
1916
1916
|
for (const job of snapshot.graphProjectionJobs) {
|
|
@@ -2000,7 +2000,7 @@ function renderElementState(facts, includeHistorical = false) {
|
|
|
2000
2000
|
lines.push(`${fact.key}: ${values.join(", ")}`);
|
|
2001
2001
|
}
|
|
2002
2002
|
}
|
|
2003
|
-
for (const [
|
|
2003
|
+
for (const [key2, values] of sets) lines.push(`${key2}: ${values.join(", ")}`);
|
|
2004
2004
|
return lines.join("\n");
|
|
2005
2005
|
}
|
|
2006
2006
|
function applyElementChanges(options) {
|
|
@@ -2008,7 +2008,7 @@ function applyElementChanges(options) {
|
|
|
2008
2008
|
for (const rawChange of options.changes) {
|
|
2009
2009
|
const name2 = compactText(rawChange.element?.name, 160);
|
|
2010
2010
|
const type = rawChange.element?.type;
|
|
2011
|
-
const
|
|
2011
|
+
const key2 = compactText(rawChange.key, 160);
|
|
2012
2012
|
const mode = rawChange.mode;
|
|
2013
2013
|
const operation = rawChange.operation;
|
|
2014
2014
|
const requestedSourceEventIds = stringList(rawChange.sourceEventIds, 24);
|
|
@@ -2016,7 +2016,7 @@ function applyElementChanges(options) {
|
|
|
2016
2016
|
const rawValue = rawChange.value;
|
|
2017
2017
|
const value = Array.isArray(rawValue) ? stringList(rawValue, 40) : compactText(rawValue, 1200);
|
|
2018
2018
|
const operationMatchesMode = mode === "state" && operation === "set_state" || mode === "set" && operation === "add_set_item" || mode === "relation" && operation === "set_relation";
|
|
2019
|
-
if (!name2 || !ELEMENT_TYPES.has(type) || !
|
|
2019
|
+
if (!name2 || !ELEMENT_TYPES.has(type) || !key2 || !FACT_MODES.has(mode) || !operationMatchesMode || sourceEventIds.length === 0 || sourceEventIds.length !== requestedSourceEventIds.length || (Array.isArray(value) ? value.length === 0 : !value)) continue;
|
|
2020
2020
|
const aliases = stringList(rawChange.element?.aliases, 20, 160).filter((alias) => normalizeSearchText(alias) !== normalizeSearchText(name2));
|
|
2021
2021
|
const knownNames = new Set([name2, ...aliases].map(normalizeSearchText));
|
|
2022
2022
|
let element = options.elements.find((candidate) => candidate.type === type && [candidate.name, ...candidate.aliases].some((candidateName) => knownNames.has(normalizeSearchText(candidateName))));
|
|
@@ -2049,18 +2049,18 @@ function applyElementChanges(options) {
|
|
|
2049
2049
|
const validFrom = compactText(rawChange.validFrom, 80) || sourceEvents.map(eventChronology).sort().at(-1) || options.now;
|
|
2050
2050
|
const validTo = compactText(rawChange.validTo, 80) || void 0;
|
|
2051
2051
|
if (mode !== "set") {
|
|
2052
|
-
for (const fact of element.facts.filter((candidate) => candidate.status === "active" && candidate.key ===
|
|
2052
|
+
for (const fact of element.facts.filter((candidate) => candidate.status === "active" && candidate.key === key2 && candidate.mode === mode)) {
|
|
2053
2053
|
fact.status = "superseded";
|
|
2054
2054
|
if (!fact.validTo) fact.validTo = validFrom;
|
|
2055
2055
|
fact.updatedAt = options.now;
|
|
2056
2056
|
}
|
|
2057
2057
|
}
|
|
2058
|
-
const existingSetValues = new Set(element.facts.filter((fact) => fact.status === "active" && fact.mode === "set" && fact.key ===
|
|
2058
|
+
const existingSetValues = new Set(element.facts.filter((fact) => fact.status === "active" && fact.mode === "set" && fact.key === key2).flatMap((fact) => Array.isArray(fact.value) ? fact.value : [fact.value]).map(normalizeSearchText));
|
|
2059
2059
|
const factValue = mode === "set" ? (Array.isArray(value) ? value : [value]).filter((item) => !existingSetValues.has(normalizeSearchText(item))) : value;
|
|
2060
2060
|
if (mode !== "set" || factValue.length > 0) {
|
|
2061
2061
|
const fact = {
|
|
2062
2062
|
id: options.idFactory("fact"),
|
|
2063
|
-
key,
|
|
2063
|
+
key: key2,
|
|
2064
2064
|
mode,
|
|
2065
2065
|
value: factValue,
|
|
2066
2066
|
...validFrom ? { validFrom } : {},
|
|
@@ -2550,11 +2550,11 @@ var SqliteStorage = class {
|
|
|
2550
2550
|
}
|
|
2551
2551
|
async load(namespace) {
|
|
2552
2552
|
this.assertOpen();
|
|
2553
|
-
const
|
|
2553
|
+
const key2 = nonEmptyNamespace(namespace);
|
|
2554
2554
|
const space = this.database.prepare(`
|
|
2555
2555
|
SELECT schema_version, revision, current_turn, block_turn_size, block_decay_lambda
|
|
2556
2556
|
FROM memory_spaces WHERE namespace = ?
|
|
2557
|
-
`).get(
|
|
2557
|
+
`).get(key2);
|
|
2558
2558
|
if (!space) return null;
|
|
2559
2559
|
if (space.schema_version !== STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
2560
2560
|
throw new Error(`Unsupported stored StrataGate schema: ${space.schema_version}`);
|
|
@@ -2562,7 +2562,7 @@ var SqliteStorage = class {
|
|
|
2562
2562
|
const messageRows = this.database.prepare(`
|
|
2563
2563
|
SELECT id, block_id, thread_id, position, role, content, created_at, tool_calls_json
|
|
2564
2564
|
FROM messages WHERE namespace = ? ORDER BY block_id, position
|
|
2565
|
-
`).all(
|
|
2565
|
+
`).all(key2);
|
|
2566
2566
|
const openTail = [];
|
|
2567
2567
|
const messagesByBlock = /* @__PURE__ */ new Map();
|
|
2568
2568
|
for (const row of messageRows) {
|
|
@@ -2583,7 +2583,7 @@ var SqliteStorage = class {
|
|
|
2583
2583
|
}
|
|
2584
2584
|
const blockRows = this.database.prepare(`
|
|
2585
2585
|
SELECT * FROM blocks WHERE namespace = ? ORDER BY sequence
|
|
2586
|
-
`).all(
|
|
2586
|
+
`).all(key2);
|
|
2587
2587
|
const blocks = blockRows.map((row) => ({
|
|
2588
2588
|
id: row.id,
|
|
2589
2589
|
...row.thread_id ? { threadId: row.thread_id } : {},
|
|
@@ -2611,7 +2611,7 @@ var SqliteStorage = class {
|
|
|
2611
2611
|
const summaryJobs = this.database.prepare(`
|
|
2612
2612
|
SELECT block_id, status, attempts, last_error, next_retry_at, updated_at
|
|
2613
2613
|
FROM block_summary_jobs WHERE namespace = ? ORDER BY block_id
|
|
2614
|
-
`).all(
|
|
2614
|
+
`).all(key2).map((row) => ({
|
|
2615
2615
|
blockId: row.block_id,
|
|
2616
2616
|
status: row.status,
|
|
2617
2617
|
attempts: row.attempts,
|
|
@@ -2622,34 +2622,34 @@ var SqliteStorage = class {
|
|
|
2622
2622
|
const sourceRows = this.database.prepare(`
|
|
2623
2623
|
SELECT event_id, message_id, position FROM event_sources
|
|
2624
2624
|
WHERE namespace = ? ORDER BY event_id, position
|
|
2625
|
-
`).all(
|
|
2625
|
+
`).all(key2);
|
|
2626
2626
|
const sourcesByEvent = /* @__PURE__ */ new Map();
|
|
2627
2627
|
for (const row of sourceRows) {
|
|
2628
|
-
const
|
|
2629
|
-
|
|
2630
|
-
sourcesByEvent.set(row.event_id,
|
|
2628
|
+
const ids2 = sourcesByEvent.get(row.event_id) ?? [];
|
|
2629
|
+
ids2.push(row.message_id);
|
|
2630
|
+
sourcesByEvent.set(row.event_id, ids2);
|
|
2631
2631
|
}
|
|
2632
2632
|
const eventRows = this.database.prepare(`
|
|
2633
2633
|
SELECT * FROM events WHERE namespace = ? ORDER BY position
|
|
2634
|
-
`).all(
|
|
2634
|
+
`).all(key2);
|
|
2635
2635
|
const events = mapEventRows(eventRows, sourcesByEvent, "events");
|
|
2636
2636
|
const agentSourceRows = this.database.prepare(`
|
|
2637
2637
|
SELECT event_id, message_id, position FROM agent_event_sources
|
|
2638
2638
|
WHERE namespace = ? ORDER BY event_id, position
|
|
2639
|
-
`).all(
|
|
2639
|
+
`).all(key2);
|
|
2640
2640
|
const agentSourcesByEvent = /* @__PURE__ */ new Map();
|
|
2641
2641
|
for (const row of agentSourceRows) {
|
|
2642
|
-
const
|
|
2643
|
-
|
|
2644
|
-
agentSourcesByEvent.set(row.event_id,
|
|
2642
|
+
const ids2 = agentSourcesByEvent.get(row.event_id) ?? [];
|
|
2643
|
+
ids2.push(row.message_id);
|
|
2644
|
+
agentSourcesByEvent.set(row.event_id, ids2);
|
|
2645
2645
|
}
|
|
2646
2646
|
const agentEventRows = this.database.prepare(`
|
|
2647
2647
|
SELECT * FROM agent_events WHERE namespace = ? ORDER BY position
|
|
2648
|
-
`).all(
|
|
2648
|
+
`).all(key2);
|
|
2649
2649
|
const agentEvents = mapEventRows(agentEventRows, agentSourcesByEvent, "agent_events");
|
|
2650
2650
|
const hasEventMetadata = this.database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'event_metadata'").get();
|
|
2651
2651
|
if (hasEventMetadata) {
|
|
2652
|
-
const metadataRows = this.database.prepare("SELECT event_id, catalog_hints_json, extractor_version FROM event_metadata WHERE namespace = ?").all(
|
|
2652
|
+
const metadataRows = this.database.prepare("SELECT event_id, catalog_hints_json, extractor_version FROM event_metadata WHERE namespace = ?").all(key2);
|
|
2653
2653
|
const byId = new Map([...events, ...agentEvents].map((event) => [event.id, event]));
|
|
2654
2654
|
for (const row of metadataRows) {
|
|
2655
2655
|
const event = byId.get(row.event_id);
|
|
@@ -2662,26 +2662,26 @@ var SqliteStorage = class {
|
|
|
2662
2662
|
const elementSourceRows = this.database.prepare(`
|
|
2663
2663
|
SELECT element_id, event_id, position FROM element_sources
|
|
2664
2664
|
WHERE namespace = ? ORDER BY element_id, position
|
|
2665
|
-
`).all(
|
|
2665
|
+
`).all(key2);
|
|
2666
2666
|
const sourcesByElement = /* @__PURE__ */ new Map();
|
|
2667
2667
|
for (const row of elementSourceRows) {
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2670
|
-
sourcesByElement.set(row.element_id,
|
|
2668
|
+
const ids2 = sourcesByElement.get(row.element_id) ?? [];
|
|
2669
|
+
ids2.push(row.event_id);
|
|
2670
|
+
sourcesByElement.set(row.element_id, ids2);
|
|
2671
2671
|
}
|
|
2672
2672
|
const elementFactSourceRows = this.database.prepare(`
|
|
2673
2673
|
SELECT fact_id, event_id, position FROM element_fact_sources
|
|
2674
2674
|
WHERE namespace = ? ORDER BY fact_id, position
|
|
2675
|
-
`).all(
|
|
2675
|
+
`).all(key2);
|
|
2676
2676
|
const sourcesByFact = /* @__PURE__ */ new Map();
|
|
2677
2677
|
for (const row of elementFactSourceRows) {
|
|
2678
|
-
const
|
|
2679
|
-
|
|
2680
|
-
sourcesByFact.set(row.fact_id,
|
|
2678
|
+
const ids2 = sourcesByFact.get(row.fact_id) ?? [];
|
|
2679
|
+
ids2.push(row.event_id);
|
|
2680
|
+
sourcesByFact.set(row.fact_id, ids2);
|
|
2681
2681
|
}
|
|
2682
2682
|
const elementFactRows = this.database.prepare(`
|
|
2683
2683
|
SELECT * FROM element_facts WHERE namespace = ? ORDER BY element_id, position
|
|
2684
|
-
`).all(
|
|
2684
|
+
`).all(key2);
|
|
2685
2685
|
const factsByElement = /* @__PURE__ */ new Map();
|
|
2686
2686
|
for (const row of elementFactRows) {
|
|
2687
2687
|
const facts = factsByElement.get(row.element_id) ?? [];
|
|
@@ -2702,7 +2702,7 @@ var SqliteStorage = class {
|
|
|
2702
2702
|
}
|
|
2703
2703
|
const elementRows = this.database.prepare(`
|
|
2704
2704
|
SELECT * FROM elements WHERE namespace = ? ORDER BY position
|
|
2705
|
-
`).all(
|
|
2705
|
+
`).all(key2);
|
|
2706
2706
|
const messagesByEvent = new Map(events.map((event) => [event.id, event.sourceMessageIds]));
|
|
2707
2707
|
const elements = elementRows.map((row) => {
|
|
2708
2708
|
const sourceEventIds = sourcesByElement.get(row.id) ?? [];
|
|
@@ -2730,7 +2730,7 @@ var SqliteStorage = class {
|
|
|
2730
2730
|
const extractionJobs = this.database.prepare(`
|
|
2731
2731
|
SELECT block_id, status, attempts, last_error, next_retry_at, updated_at
|
|
2732
2732
|
FROM extraction_jobs WHERE namespace = ? ORDER BY block_id
|
|
2733
|
-
`).all(
|
|
2733
|
+
`).all(key2).map((row) => ({
|
|
2734
2734
|
blockId: row.block_id,
|
|
2735
2735
|
status: row.status,
|
|
2736
2736
|
attempts: row.attempts,
|
|
@@ -2741,7 +2741,7 @@ var SqliteStorage = class {
|
|
|
2741
2741
|
const elementProjectionJobs = this.database.prepare(`
|
|
2742
2742
|
SELECT id, source_event_ids_json, status, attempts, element_ids_json, reason, last_error, created_at, updated_at
|
|
2743
2743
|
FROM element_projection_jobs WHERE namespace = ? ORDER BY created_at, id
|
|
2744
|
-
`).all(
|
|
2744
|
+
`).all(key2).map((row) => ({
|
|
2745
2745
|
id: row.id,
|
|
2746
2746
|
sourceEventIds: parseJson(row.source_event_ids_json, "element_projection_jobs.source_event_ids_json"),
|
|
2747
2747
|
status: row.status,
|
|
@@ -2755,7 +2755,7 @@ var SqliteStorage = class {
|
|
|
2755
2755
|
const successfulModelResponses = this.database.prepare(`
|
|
2756
2756
|
SELECT id, kind, response, created_at
|
|
2757
2757
|
FROM model_response_history WHERE namespace = ? ORDER BY created_at, id
|
|
2758
|
-
`).all(
|
|
2758
|
+
`).all(key2).map((row) => ({
|
|
2759
2759
|
id: row.id,
|
|
2760
2760
|
kind: row.kind,
|
|
2761
2761
|
response: row.response,
|
|
@@ -2764,7 +2764,7 @@ var SqliteStorage = class {
|
|
|
2764
2764
|
const usageReceipts = this.database.prepare(`
|
|
2765
2765
|
SELECT receipt_id, event_ids_json, element_ids_json, audit_json, created_at
|
|
2766
2766
|
FROM usage_receipts WHERE namespace = ? ORDER BY created_at, receipt_id
|
|
2767
|
-
`).all(
|
|
2767
|
+
`).all(key2).map((row) => {
|
|
2768
2768
|
const audit2 = parseJson(row.audit_json, "usage_receipts.audit_json");
|
|
2769
2769
|
return {
|
|
2770
2770
|
id: row.receipt_id,
|
|
@@ -2777,22 +2777,22 @@ var SqliteStorage = class {
|
|
|
2777
2777
|
const ingestionReceipts = this.database.prepare(`
|
|
2778
2778
|
SELECT receipt_id, created_at
|
|
2779
2779
|
FROM ingestion_receipts WHERE namespace = ? ORDER BY created_at, receipt_id
|
|
2780
|
-
`).all(
|
|
2780
|
+
`).all(key2).map((row) => ({
|
|
2781
2781
|
id: row.receipt_id,
|
|
2782
2782
|
createdAt: row.created_at
|
|
2783
2783
|
}));
|
|
2784
2784
|
const externalMemoryImportJobs = this.database.prepare(`
|
|
2785
2785
|
SELECT id, payload_json FROM external_memory_import_jobs
|
|
2786
2786
|
WHERE namespace = ? ORDER BY created_at, id
|
|
2787
|
-
`).all(
|
|
2787
|
+
`).all(key2).map((row) => parseJson(row.payload_json, "external_memory_import_jobs.payload_json"));
|
|
2788
2788
|
const graphState = this.database.prepare(`
|
|
2789
2789
|
SELECT nodes_json, edges_json, jobs_json FROM graph_state WHERE namespace = ?
|
|
2790
|
-
`).get(
|
|
2790
|
+
`).get(key2);
|
|
2791
2791
|
const graphNodes = graphState ? parseJson(graphState.nodes_json, "graph_state.nodes_json") : [];
|
|
2792
2792
|
const graphEdges = graphState ? parseJson(graphState.edges_json, "graph_state.edges_json") : [];
|
|
2793
2793
|
const graphProjectionJobs = graphState ? parseJson(graphState.jobs_json, "graph_state.jobs_json") : [];
|
|
2794
2794
|
const hasTopics = this.database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'memory_topic_state'").get();
|
|
2795
|
-
const topicState = hasTopics ? this.database.prepare("SELECT state_json FROM memory_topic_state WHERE namespace = ?").get(
|
|
2795
|
+
const topicState = hasTopics ? this.database.prepare("SELECT state_json FROM memory_topic_state WHERE namespace = ?").get(key2) : void 0;
|
|
2796
2796
|
const snapshot = {
|
|
2797
2797
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
2798
2798
|
currentTurn: space.current_turn,
|
|
@@ -2823,8 +2823,8 @@ var SqliteStorage = class {
|
|
|
2823
2823
|
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
|
|
2824
2824
|
throw new TypeError("expectedRevision must be a non-negative integer");
|
|
2825
2825
|
}
|
|
2826
|
-
const
|
|
2827
|
-
return this.immediateTransaction(() => this.persistSnapshot(
|
|
2826
|
+
const key2 = nonEmptyNamespace(namespace);
|
|
2827
|
+
return this.immediateTransaction(() => this.persistSnapshot(key2, snapshot, expectedRevision, rawMessageIndexDelta));
|
|
2828
2828
|
}
|
|
2829
2829
|
async close() {
|
|
2830
2830
|
if (this.closed) return;
|
|
@@ -2945,7 +2945,7 @@ var SqliteStorage = class {
|
|
|
2945
2945
|
searchRawMessageIds(namespace, tokens, limit, threadId, includeUnthreaded = false) {
|
|
2946
2946
|
this.assertOpen();
|
|
2947
2947
|
if (!this.rawSearchFtsAvailable || tokens.length === 0) return null;
|
|
2948
|
-
const
|
|
2948
|
+
const key2 = nonEmptyNamespace(namespace);
|
|
2949
2949
|
const boundedLimit = Math.max(1, Math.min(5e3, Math.floor(limit)));
|
|
2950
2950
|
const match = tokens.map(encodeRawSearchToken).filter(Boolean).map((token) => `"${token}"`).join(" OR ");
|
|
2951
2951
|
if (!match) return [];
|
|
@@ -2960,7 +2960,7 @@ var SqliteStorage = class {
|
|
|
2960
2960
|
${threadId === void 0 ? "" : includeUnthreaded ? "AND (m.thread_id = ? OR m.thread_id IS NULL)" : "AND m.thread_id = ?"}
|
|
2961
2961
|
ORDER BY rank
|
|
2962
2962
|
LIMIT ?
|
|
2963
|
-
`).all(...threadId === void 0 ? [match,
|
|
2963
|
+
`).all(...threadId === void 0 ? [match, key2, boundedLimit] : [match, key2, threadId, boundedLimit]);
|
|
2964
2964
|
return rows.map(({ message_id }) => message_id);
|
|
2965
2965
|
} catch {
|
|
2966
2966
|
this.rawSearchFtsAvailable = false;
|
|
@@ -3761,6 +3761,7 @@ var TOPIC_SAFE_REASONS = /* @__PURE__ */ new Set([
|
|
|
3761
3761
|
"Topic projection must contain at most 8 overview paragraphs per chapter per batch.",
|
|
3762
3762
|
"Topic projection must return 1-12 topics.",
|
|
3763
3763
|
"Topic projection omitted batch events.",
|
|
3764
|
+
"Topic retry must replace invalid overview without removing entries.",
|
|
3764
3765
|
"Topic projection source version changed; stale result rejected.",
|
|
3765
3766
|
"Topic sections omitted batch events.",
|
|
3766
3767
|
"Truncated topic sources permit navigation scope only, not factual overview.",
|
|
@@ -3776,6 +3777,10 @@ var TOPIC_SAFE_REASONS = /* @__PURE__ */ new Set([
|
|
|
3776
3777
|
"overview must contain 0-8 source-grounded entries",
|
|
3777
3778
|
"overview.sourceEventIds must contain unique, supplied Event ids (at most 12)",
|
|
3778
3779
|
"overview.text must be nonempty and at most 600 characters",
|
|
3780
|
+
"overview.text must be a string",
|
|
3781
|
+
"overview.text is empty",
|
|
3782
|
+
"overview.text contains only whitespace",
|
|
3783
|
+
"overview.text exceeds the character limit",
|
|
3779
3784
|
"overview.title must be nonempty and at most 80 characters",
|
|
3780
3785
|
"section backfill must update only its existing chapter",
|
|
3781
3786
|
"section.sourceEventIds must contain unique, supplied Event ids",
|
|
@@ -3811,7 +3816,7 @@ function safeTopicDiagnostics(value) {
|
|
|
3811
3816
|
category: input.category,
|
|
3812
3817
|
reason: typeof input.reason === "string" && TOPIC_SAFE_REASONS.has(input.reason) ? input.reason : "topic worker failed"
|
|
3813
3818
|
};
|
|
3814
|
-
for (const
|
|
3819
|
+
for (const key2 of [
|
|
3815
3820
|
"eventCount",
|
|
3816
3821
|
"candidateTopicCount",
|
|
3817
3822
|
"estimatedInputTokens",
|
|
@@ -3819,10 +3824,16 @@ function safeTopicDiagnostics(value) {
|
|
|
3819
3824
|
"maxOutputTokens",
|
|
3820
3825
|
"modelCalls",
|
|
3821
3826
|
"attempt",
|
|
3822
|
-
"splitDepth"
|
|
3827
|
+
"splitDepth",
|
|
3828
|
+
"actualCharacters",
|
|
3829
|
+
"minCharacters",
|
|
3830
|
+
"maxCharacters"
|
|
3823
3831
|
]) {
|
|
3824
|
-
const number = input[
|
|
3825
|
-
if (typeof number === "number" && Number.isSafeInteger(number) && number >= 0) result[
|
|
3832
|
+
const number = input[key2];
|
|
3833
|
+
if (typeof number === "number" && Number.isSafeInteger(number) && number >= 0) result[key2] = number;
|
|
3834
|
+
}
|
|
3835
|
+
if (typeof input.fieldPath === "string" && /^topics\[\d{1,5}\]\.overview\[\d{1,5}\]\.text$/.test(input.fieldPath)) {
|
|
3836
|
+
result.fieldPath = input.fieldPath;
|
|
3826
3837
|
}
|
|
3827
3838
|
if (["stop", "max-tokens", "error", "aborted", "tool-calls", "unknown"].includes(String(input.finishReason))) {
|
|
3828
3839
|
result.finishReason = input.finishReason;
|
|
@@ -3842,16 +3853,146 @@ var TopicProjectionError = class extends Error {
|
|
|
3842
3853
|
this.diagnostics = diagnostics;
|
|
3843
3854
|
}
|
|
3844
3855
|
};
|
|
3856
|
+
function validateTopicOverviewText(value, fieldPath, category = "validation-failed") {
|
|
3857
|
+
const count = typeof value === "string" ? Array.from(value).length : void 0;
|
|
3858
|
+
const metadata = {
|
|
3859
|
+
fieldPath,
|
|
3860
|
+
minCharacters: 1,
|
|
3861
|
+
maxCharacters: 600,
|
|
3862
|
+
...count === void 0 ? {} : { actualCharacters: count }
|
|
3863
|
+
};
|
|
3864
|
+
const reason = typeof value !== "string" ? "overview.text must be a string" : value.length === 0 ? "overview.text is empty" : !value.trim() ? "overview.text contains only whitespace" : count > 600 ? "overview.text exceeds the character limit" : null;
|
|
3865
|
+
if (reason) throw new TopicProjectionError(category, reason, metadata);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
// packages/core/src/topic-overview-repair.ts
|
|
3869
|
+
var kinds = ["history", "decision", "change", "open-question", "scope"];
|
|
3870
|
+
var record2 = (value) => value && typeof value === "object" ? value : {};
|
|
3871
|
+
var ids = (value) => Array.isArray(value) ? [...new Set(value.filter((id) => typeof id === "string"))].sort() : [];
|
|
3872
|
+
var key = (row) => JSON.stringify([row.kind ?? null, row.sourceEventIds]);
|
|
3873
|
+
var validText = (value) => typeof value === "string" && !!value.trim() && Array.from(value).length <= 600;
|
|
3874
|
+
function uniqueTopicOverviewEntries(parts) {
|
|
3875
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3876
|
+
return parts.filter((part) => {
|
|
3877
|
+
const row = record2(part);
|
|
3878
|
+
const identity = JSON.stringify([row.kind, row.title ?? null, row.text, ids(row.sourceEventIds)]);
|
|
3879
|
+
if (seen.has(identity)) return false;
|
|
3880
|
+
seen.add(identity);
|
|
3881
|
+
return true;
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
function entries(value) {
|
|
3885
|
+
const topics = record2(value).topics;
|
|
3886
|
+
return Array.isArray(topics) ? topics.flatMap((topic) => {
|
|
3887
|
+
const overview2 = record2(topic).overview;
|
|
3888
|
+
return Array.isArray(overview2) ? uniqueTopicOverviewEntries(overview2).map(record2) : [];
|
|
3889
|
+
}) : [];
|
|
3890
|
+
}
|
|
3891
|
+
var repairsByError = /* @__PURE__ */ new WeakMap();
|
|
3892
|
+
function carryTopicOverviewRepairs(error, requirements) {
|
|
3893
|
+
if (requirements.length) repairsByError.set(error, structuredClone([...requirements]));
|
|
3894
|
+
return error;
|
|
3895
|
+
}
|
|
3896
|
+
function topicOverviewRepairsFromError(error) {
|
|
3897
|
+
return error instanceof TopicProjectionError ? structuredClone(repairsByError.get(error) ?? []) : [];
|
|
3898
|
+
}
|
|
3899
|
+
function topicOverviewRepairsForHistoricalFailure(diagnostics) {
|
|
3900
|
+
return diagnostics && ["schema-invalid", "validation-failed"].includes(diagnostics.category) && [
|
|
3901
|
+
"overview.text must be a string",
|
|
3902
|
+
"overview.text is empty",
|
|
3903
|
+
"overview.text contains only whitespace",
|
|
3904
|
+
"overview.text exceeds the character limit",
|
|
3905
|
+
"overview.text must be nonempty and at most 600 characters"
|
|
3906
|
+
].includes(diagnostics.reason) ? [{ sourceEventIds: [], minimumEntries: 1 }] : [];
|
|
3907
|
+
}
|
|
3908
|
+
var TopicOverviewRepairGuard = class {
|
|
3909
|
+
required = [];
|
|
3910
|
+
constructor(requirements = []) {
|
|
3911
|
+
this.merge(requirements);
|
|
3912
|
+
}
|
|
3913
|
+
get requirements() {
|
|
3914
|
+
return structuredClone(this.required);
|
|
3915
|
+
}
|
|
3916
|
+
merge(requirements) {
|
|
3917
|
+
const merged = new Map(this.required.map((row) => [key(row), row]));
|
|
3918
|
+
for (const requirement of requirements) {
|
|
3919
|
+
const row = {
|
|
3920
|
+
...kinds.includes(requirement.kind ?? "") ? { kind: requirement.kind } : {},
|
|
3921
|
+
sourceEventIds: ids(requirement.sourceEventIds),
|
|
3922
|
+
minimumEntries: requirement.minimumEntries
|
|
3923
|
+
};
|
|
3924
|
+
if (!Number.isSafeInteger(row.minimumEntries) || row.minimumEntries < 1) continue;
|
|
3925
|
+
const previous = merged.get(key(row));
|
|
3926
|
+
if (!previous || previous.minimumEntries < row.minimumEntries) merged.set(key(row), row);
|
|
3927
|
+
}
|
|
3928
|
+
this.required = [...merged.values()];
|
|
3929
|
+
}
|
|
3930
|
+
observe(value, context) {
|
|
3931
|
+
const parts = entries(value);
|
|
3932
|
+
if (!parts.some((part) => !validText(part.text))) return;
|
|
3933
|
+
const rawTopics = record2(value).topics;
|
|
3934
|
+
const rows = rawTopics.flatMap((topic) => {
|
|
3935
|
+
const item = record2(topic), overview2 = item.overview;
|
|
3936
|
+
const candidate = context.existingTopics.find(({ id }) => id === item.topicId);
|
|
3937
|
+
const allowed = /* @__PURE__ */ new Set([...context.events.map(({ id }) => id), ...candidate?.sourceEventIds ?? []]);
|
|
3938
|
+
return Array.isArray(overview2) ? uniqueTopicOverviewEntries(overview2).map((entry) => {
|
|
3939
|
+
const part = record2(entry);
|
|
3940
|
+
const row = {
|
|
3941
|
+
...kinds.includes(String(part.kind)) ? { kind: part.kind } : {},
|
|
3942
|
+
sourceEventIds: ids(part.sourceEventIds).filter((id) => allowed.has(id)),
|
|
3943
|
+
minimumEntries: 1
|
|
3944
|
+
};
|
|
3945
|
+
return { row, invalid: !validText(part.text) };
|
|
3946
|
+
}) : [];
|
|
3947
|
+
});
|
|
3948
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3949
|
+
for (const { row, invalid } of rows) {
|
|
3950
|
+
if (!invalid) continue;
|
|
3951
|
+
const count = rows.filter(({ row: original }) => (row.kind === void 0 || row.kind === original.kind) && (!row.sourceEventIds.length || JSON.stringify(row.sourceEventIds) === JSON.stringify(original.sourceEventIds))).length;
|
|
3952
|
+
counts.set(key(row), { ...row, minimumEntries: count });
|
|
3953
|
+
}
|
|
3954
|
+
if (rows.some(({ row, invalid }) => invalid && (row.kind === void 0 || !row.sourceEventIds.length))) {
|
|
3955
|
+
const total = { sourceEventIds: [], minimumEntries: parts.length };
|
|
3956
|
+
counts.set(key(total), total);
|
|
3957
|
+
}
|
|
3958
|
+
this.merge([...counts.values()]);
|
|
3959
|
+
}
|
|
3960
|
+
assert(value) {
|
|
3961
|
+
if (!this.required.length) return;
|
|
3962
|
+
const actual = uniqueTopicOverviewEntries(entries(value)).filter((part) => validText(part.text)).map((part) => ({
|
|
3963
|
+
kind: part.kind,
|
|
3964
|
+
sourceEventIds: ids(part.sourceEventIds)
|
|
3965
|
+
}));
|
|
3966
|
+
for (const row of this.required) {
|
|
3967
|
+
const count = actual.filter((part) => (row.kind === void 0 || row.kind === part.kind) && (!row.sourceEventIds.length || JSON.stringify(row.sourceEventIds) === JSON.stringify(part.sourceEventIds))).length;
|
|
3968
|
+
if (count < row.minimumEntries) {
|
|
3969
|
+
throw new TopicProjectionError("validation-failed", "Topic retry must replace invalid overview without removing entries.");
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
};
|
|
3974
|
+
function partitionTopicOverviewRepairs(requirements, parentIds, childIds) {
|
|
3975
|
+
if (!requirements?.length) return void 0;
|
|
3976
|
+
const parent = new Set(parentIds), child = new Set(childIds);
|
|
3977
|
+
const rows = requirements.flatMap((row) => {
|
|
3978
|
+
const batchIds = row.sourceEventIds.filter((id) => parent.has(id));
|
|
3979
|
+
if (batchIds.length && !batchIds.some((id) => child.has(id))) return [];
|
|
3980
|
+
return [{ ...row, sourceEventIds: row.sourceEventIds.filter((id) => !parent.has(id) || child.has(id)) }];
|
|
3981
|
+
});
|
|
3982
|
+
const guard = new TopicOverviewRepairGuard(rows);
|
|
3983
|
+
return guard.requirements.length ? guard.requirements : void 0;
|
|
3984
|
+
}
|
|
3845
3985
|
|
|
3846
3986
|
// packages/core/src/topics.ts
|
|
3847
3987
|
var MEMORY_TOPIC_PROJECTOR_VERSION = 3;
|
|
3848
3988
|
var TOPIC_BATCH_LIMIT = 12;
|
|
3849
3989
|
var TOPIC_CANDIDATE_LIMIT = 12;
|
|
3850
3990
|
var TOPIC_MAX_ATTEMPTS = 3;
|
|
3991
|
+
var TOPIC_RECOVERY_VERSION = 1;
|
|
3851
3992
|
var TOPIC_LEASE_MS = 5 * 6e4;
|
|
3852
3993
|
function canonical(value) {
|
|
3853
3994
|
if (Array.isArray(value)) return value.map(canonical);
|
|
3854
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([
|
|
3995
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, item]) => [key2, canonical(item)]));
|
|
3855
3996
|
return value;
|
|
3856
3997
|
}
|
|
3857
3998
|
function digest(value) {
|
|
@@ -3866,7 +4007,7 @@ function topicTemporal(event) {
|
|
|
3866
4007
|
return temporal;
|
|
3867
4008
|
}
|
|
3868
4009
|
var visible = (event) => event.status === "active" || event.status === "superseded";
|
|
3869
|
-
var unique2 = (
|
|
4010
|
+
var unique2 = (ids2) => [...new Set(ids2)];
|
|
3870
4011
|
var bounded = (value, size) => [...value].slice(0, size).join("");
|
|
3871
4012
|
var SECTION_TITLES = {
|
|
3872
4013
|
history: "\u53D1\u5C55\u8109\u7EDC",
|
|
@@ -3888,10 +4029,10 @@ function memoryTopicMembershipSections(topic) {
|
|
|
3888
4029
|
const sections = /* @__PURE__ */ new Map();
|
|
3889
4030
|
for (const part of topic.overview) {
|
|
3890
4031
|
const title = memoryTopicSectionTitle(part);
|
|
3891
|
-
const
|
|
3892
|
-
const section = sections.get(
|
|
4032
|
+
const key2 = normalizedLabel(title);
|
|
4033
|
+
const section = sections.get(key2) ?? { title, sourceEventIds: [] };
|
|
3893
4034
|
section.sourceEventIds = unique2([...section.sourceEventIds, ...part.sourceEventIds]);
|
|
3894
|
-
sections.set(
|
|
4035
|
+
sections.set(key2, section);
|
|
3895
4036
|
}
|
|
3896
4037
|
return [...sections.values()];
|
|
3897
4038
|
}
|
|
@@ -3910,22 +4051,26 @@ function sourceVersions(sources2) {
|
|
|
3910
4051
|
function jobInputsCurrent(job, versions) {
|
|
3911
4052
|
return job.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION && Object.entries({ ...job.sourceVersions, ...job.dependencyVersions }).every(([id, version]) => versions.get(id) === version);
|
|
3912
4053
|
}
|
|
4054
|
+
function memoryTopicUnfinishedJobEventIds(job, state, versions) {
|
|
4055
|
+
if (job.superseded || job.status === "completed" || !jobInputsCurrent(job, versions)) return [];
|
|
4056
|
+
return job.sourceEventIds.filter((id) => versions.has(id) && job.sourceVersions[id] === versions.get(id) && (job.sectionBackfillTopicId ? state.sectionBackfill?.[job.sectionBackfillTopicId]?.[id] === versions.get(id) : state.projectedVersions[id] !== versions.get(id)));
|
|
4057
|
+
}
|
|
3913
4058
|
function topicView(topic, events, versions) {
|
|
3914
|
-
const
|
|
3915
|
-
if (
|
|
3916
|
-
const valid = !topic.invalidated && topic.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION &&
|
|
4059
|
+
const ids2 = topic.sourceEventIds.filter((id) => events.has(id));
|
|
4060
|
+
if (ids2.length === 0) return null;
|
|
4061
|
+
const valid = !topic.invalidated && topic.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION && ids2.length === topic.sourceEventIds.length && Object.entries(topic.dependencyVersions ?? topic.sourceVersions).every(([id, version]) => versions.get(id) === version);
|
|
3917
4062
|
const overview2 = valid ? structuredClone(topic.overview) : [];
|
|
3918
4063
|
const summarized = new Set(overview2.flatMap((part) => part.sourceEventIds));
|
|
3919
4064
|
return {
|
|
3920
4065
|
id: topic.id,
|
|
3921
4066
|
title: valid ? topic.title : "\u5F85\u66F4\u65B0\u8BB0\u5FC6\u4E3B\u9898",
|
|
3922
4067
|
description: valid ? topic.description : "\u6982\u89C8\u7B49\u5F85\u91CD\u65B0\u6574\u7406\uFF0C\u8BF7\u67E5\u770B\u5173\u8054\u4E8B\u4EF6\u3002",
|
|
3923
|
-
sourceEventIds:
|
|
4068
|
+
sourceEventIds: ids2,
|
|
3924
4069
|
overview: overview2,
|
|
3925
4070
|
sections: valid ? memoryTopicMembershipSections(topic) : [],
|
|
3926
4071
|
createdAt: topic.createdAt,
|
|
3927
4072
|
updatedAt: topic.updatedAt,
|
|
3928
|
-
coverage: { totalEvents:
|
|
4073
|
+
coverage: { totalEvents: ids2.length, summarizedEvents: summarized.size, omittedEvents: ids2.length - summarized.size },
|
|
3929
4074
|
...!valid ? { isFallback: true } : {}
|
|
3930
4075
|
};
|
|
3931
4076
|
}
|
|
@@ -3933,9 +4078,9 @@ function compactEvent(event) {
|
|
|
3933
4078
|
const full = { ...structuredClone(event), temporal: structuredClone(topicTemporal(event)), quotes: [], sourceMessageIds: [] };
|
|
3934
4079
|
if (JSON.stringify(full).length <= 1e4) return full;
|
|
3935
4080
|
const temporal = structuredClone(topicTemporal(event));
|
|
3936
|
-
for (const [
|
|
3937
|
-
if (typeof value === "string") temporal[
|
|
3938
|
-
if (Array.isArray(value)) temporal[
|
|
4081
|
+
for (const [key2, value] of Object.entries(temporal)) {
|
|
4082
|
+
if (typeof value === "string") temporal[key2] = bounded(value, key2 === "originalText" ? 200 : 120);
|
|
4083
|
+
if (Array.isArray(value)) temporal[key2] = value.slice(0, 12).map((item) => bounded(String(item), 120));
|
|
3939
4084
|
}
|
|
3940
4085
|
return {
|
|
3941
4086
|
...structuredClone(event),
|
|
@@ -3990,7 +4135,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3990
4135
|
return this.state.bootstrap ? structuredClone(this.state.bootstrap) : null;
|
|
3991
4136
|
}
|
|
3992
4137
|
needsInitialization() {
|
|
3993
|
-
return this.state.bootstrap?.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || this.state.sectionMembershipVersion !== 1;
|
|
4138
|
+
return this.state.bootstrap?.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || this.state.sectionMembershipVersion !== 1 || (this.state.recoveryVersion ?? 0) < TOPIC_RECOVERY_VERSION;
|
|
3994
4139
|
}
|
|
3995
4140
|
initializeBootstrap(events, now) {
|
|
3996
4141
|
this.synchronize(events, now);
|
|
@@ -3999,12 +4144,13 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3999
4144
|
const missing = {};
|
|
4000
4145
|
for (const topic of this.state.topics.filter((topic2) => !topic2.invalidated)) {
|
|
4001
4146
|
const assigned = new Set(memoryTopicMembershipSections(topic).flatMap((section) => section.sourceEventIds));
|
|
4002
|
-
const
|
|
4003
|
-
if (
|
|
4147
|
+
const ids2 = topic.sourceEventIds.filter((id) => !assigned.has(id) && versions.has(id) && this.state.projectedVersions[id] === versions.get(id));
|
|
4148
|
+
if (ids2.length) missing[topic.id] = Object.fromEntries(ids2.map((id) => [id, versions.get(id)]));
|
|
4004
4149
|
}
|
|
4005
4150
|
this.state.sectionBackfill = missing;
|
|
4006
4151
|
this.state.sectionMembershipVersion = 1;
|
|
4007
4152
|
}
|
|
4153
|
+
this.recoverFailures(events, now);
|
|
4008
4154
|
if (this.state.bootstrap?.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION) return;
|
|
4009
4155
|
this.state.bootstrap = {
|
|
4010
4156
|
projectorVersion: MEMORY_TOPIC_PROJECTOR_VERSION,
|
|
@@ -4016,6 +4162,24 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4016
4162
|
};
|
|
4017
4163
|
this.synchronize(events, now);
|
|
4018
4164
|
}
|
|
4165
|
+
recoverFailures(events, now) {
|
|
4166
|
+
if ((this.state.recoveryVersion ?? 0) >= TOPIC_RECOVERY_VERSION) return;
|
|
4167
|
+
this.state.recoveryVersion = TOPIC_RECOVERY_VERSION;
|
|
4168
|
+
const versions = sourceVersions(new Map(events.filter(visible).map((event) => [event.id, event])));
|
|
4169
|
+
for (const previous of [...this.state.jobs]) {
|
|
4170
|
+
if (previous.superseded || previous.status !== "failed" || previous.attempts < TOPIC_MAX_ATTEMPTS || previous.nextRetryAt !== null || !jobInputsCurrent(previous, versions)) continue;
|
|
4171
|
+
const work = memoryTopicUnfinishedJobEventIds(previous, this.state, versions);
|
|
4172
|
+
if (!work.length) continue;
|
|
4173
|
+
const ids2 = work.filter((id) => !this.state.jobs.some((job) => job.id !== previous.id && !job.superseded && jobInputsCurrent(job, versions) && job.sectionBackfillTopicId === previous.sectionBackfillTopicId && job.sourceVersions[id] === versions.get(id) && (job.status === "pending" || job.status === "running" || job.status === "failed" && job.attempts < TOPIC_MAX_ATTEMPTS && job.nextRetryAt !== null)));
|
|
4174
|
+
if (!ids2.length) {
|
|
4175
|
+
previous.superseded = true;
|
|
4176
|
+
previous.updatedAt = now;
|
|
4177
|
+
continue;
|
|
4178
|
+
}
|
|
4179
|
+
this.requeue(previous, ids2, versions, now);
|
|
4180
|
+
}
|
|
4181
|
+
this.synchronize(events, now);
|
|
4182
|
+
}
|
|
4019
4183
|
synchronize(events, now) {
|
|
4020
4184
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
4021
4185
|
const versions = sourceVersions(sources2);
|
|
@@ -4074,13 +4238,24 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4074
4238
|
for (const [id, version] of Object.entries(pending)) if (!topic || !topic.sourceEventIds.includes(id) || versions.get(id) !== version || assigned.has(id)) delete pending[id];
|
|
4075
4239
|
if (!Object.keys(pending).length) delete this.state.sectionBackfill[topicId];
|
|
4076
4240
|
}
|
|
4241
|
+
for (const job of this.state.jobs) if (job.status === "failed" && !job.superseded && jobInputsCurrent(job, versions) && !memoryTopicUnfinishedJobEventIds(job, this.state, versions).length) {
|
|
4242
|
+
job.superseded = true;
|
|
4243
|
+
job.context = null;
|
|
4244
|
+
job.nextRetryAt = null;
|
|
4245
|
+
job.leaseUntil = null;
|
|
4246
|
+
job.updatedAt = now;
|
|
4247
|
+
}
|
|
4077
4248
|
const bootstrap = this.state.bootstrap;
|
|
4078
4249
|
if (bootstrap && bootstrap.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION) {
|
|
4079
4250
|
const outstanding = Object.entries(bootstrap.sourceVersions).filter(([id, version]) => versions.get(id) === version && this.state.projectedVersions[id] !== version);
|
|
4080
|
-
bootstrap.failedEvents = outstanding.filter(([id, version]) => this.state.jobs.some((job) => !job.superseded && job.status === "failed" && job.attempts >= TOPIC_MAX_ATTEMPTS && job.sourceVersions[id] === version && jobInputsCurrent(job, versions))).length;
|
|
4081
|
-
if (outstanding.length ===
|
|
4251
|
+
bootstrap.failedEvents = outstanding.filter(([id, version]) => this.state.jobs.some((job) => !job.superseded && job.sectionBackfillTopicId === void 0 && job.status === "failed" && job.attempts >= TOPIC_MAX_ATTEMPTS && job.sourceVersions[id] === version && jobInputsCurrent(job, versions))).length;
|
|
4252
|
+
if (outstanding.length === 0) {
|
|
4253
|
+
if (bootstrap.status !== "completed") bootstrap.completedAt = now;
|
|
4082
4254
|
bootstrap.status = "completed";
|
|
4083
|
-
|
|
4255
|
+
} else {
|
|
4256
|
+
bootstrap.completedAt = null;
|
|
4257
|
+
if (outstanding.length === bootstrap.failedEvents) bootstrap.status = "failed";
|
|
4258
|
+
else if (bootstrap.status === "completed" || bootstrap.status === "failed") bootstrap.status = "running";
|
|
4084
4259
|
}
|
|
4085
4260
|
}
|
|
4086
4261
|
}
|
|
@@ -4123,8 +4298,8 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4123
4298
|
const bootstrap = this.state.bootstrap;
|
|
4124
4299
|
return sectionBackfillTopicId !== void 0 || this.state.rebuildVersions?.[id] === version || !bootstrap || bootstrap.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || bootstrap.status !== "completed" && bootstrap.sourceVersions[id] === version;
|
|
4125
4300
|
}
|
|
4126
|
-
matchesMode(
|
|
4127
|
-
return mode === "all" ||
|
|
4301
|
+
matchesMode(ids2, versions, mode, sectionBackfillTopicId) {
|
|
4302
|
+
return mode === "all" || ids2.some((id) => this.isBootstrap(id, versions.get(id), sectionBackfillTopicId) === (mode === "bootstrap"));
|
|
4128
4303
|
}
|
|
4129
4304
|
hasPending(events, now, mode = "all") {
|
|
4130
4305
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
@@ -4254,6 +4429,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4254
4429
|
}
|
|
4255
4430
|
job.context = {
|
|
4256
4431
|
jobId: job.id,
|
|
4432
|
+
...job.overviewRepairRequirements?.length ? { overviewRepairRequirements: structuredClone(job.overviewRepairRequirements) } : {},
|
|
4257
4433
|
events: batch.map(compactEvent),
|
|
4258
4434
|
existingTopics,
|
|
4259
4435
|
truncatedEventIds,
|
|
@@ -4279,6 +4455,17 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4279
4455
|
return structuredClone(job.context);
|
|
4280
4456
|
}
|
|
4281
4457
|
complete(id, result, events, now) {
|
|
4458
|
+
const job = this.requireJob(id);
|
|
4459
|
+
const repairs = new TopicOverviewRepairGuard(job.overviewRepairRequirements);
|
|
4460
|
+
if (job.context) repairs.observe(result, job.context);
|
|
4461
|
+
try {
|
|
4462
|
+
return this.completeValidated(id, result, events, now, repairs);
|
|
4463
|
+
} catch (error) {
|
|
4464
|
+
if (error instanceof TopicProjectionError) throw carryTopicOverviewRepairs(error, repairs.requirements);
|
|
4465
|
+
throw error;
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
completeValidated(id, result, events, now, repairs) {
|
|
4282
4469
|
const job = this.requireJob(id);
|
|
4283
4470
|
if (job.status === "completed") return { topicIds: [...job.topicIds] };
|
|
4284
4471
|
if (job.status !== "running" || !job.context) throw new Error(`Topic projection ${id} is not running`);
|
|
@@ -4299,13 +4486,13 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4299
4486
|
const touched = /* @__PURE__ */ new Set();
|
|
4300
4487
|
const labels = /* @__PURE__ */ new Set();
|
|
4301
4488
|
const proposals = [];
|
|
4302
|
-
const checkIds = (
|
|
4303
|
-
if (!Array.isArray(
|
|
4304
|
-
return
|
|
4489
|
+
const checkIds = (ids2, label) => {
|
|
4490
|
+
if (!Array.isArray(ids2) || ids2.length === 0 || unique2(ids2).length !== ids2.length || ids2.some((eventId) => typeof eventId !== "string" || !allowed.has(eventId))) throw new TopicProjectionError("validation-failed", `Invalid topic ${label} source events.`);
|
|
4491
|
+
return ids2;
|
|
4305
4492
|
};
|
|
4306
|
-
for (const proposal of result.topics) {
|
|
4307
|
-
const
|
|
4308
|
-
const matching = this.state.topics.filter((topic) => candidates.has(topic.id) && topic.sourceEventIds.some((eventId) =>
|
|
4493
|
+
for (const [topicIndex, proposal] of result.topics.entries()) {
|
|
4494
|
+
const ids2 = checkIds(proposal.sourceEventIds, "membership");
|
|
4495
|
+
const matching = this.state.topics.filter((topic) => candidates.has(topic.id) && topic.sourceEventIds.some((eventId) => ids2.includes(eventId)));
|
|
4309
4496
|
const inferred = matching.length === 1 && result.topics.filter((other) => Array.isArray(other.sourceEventIds) && matching[0].sourceEventIds.some((eventId) => other.sourceEventIds.includes(eventId))).length === 1 ? matching[0] : void 0;
|
|
4310
4497
|
const label = normalizedLabel;
|
|
4311
4498
|
const sameLabel = typeof proposal.title === "string" ? this.state.topics.filter((topic) => !topic.invalidated && label(topic.title) === label(proposal.title)) : [];
|
|
@@ -4317,25 +4504,26 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4317
4504
|
throw new TopicProjectionError("validation-failed", "Invalid duplicate chapter label conflicts with an existing topic.");
|
|
4318
4505
|
}
|
|
4319
4506
|
if (typeof proposal.title === "string") {
|
|
4320
|
-
const
|
|
4321
|
-
if (labels.has(
|
|
4322
|
-
labels.add(
|
|
4507
|
+
const key2 = label(proposal.title);
|
|
4508
|
+
if (labels.has(key2)) throw new TopicProjectionError("validation-failed", "Invalid duplicate chapter label in one projection.");
|
|
4509
|
+
labels.add(key2);
|
|
4323
4510
|
}
|
|
4324
4511
|
if (proposal.topicId && (!existing || !candidates.has(proposal.topicId))) throw new TopicProjectionError("validation-failed", "Unknown or unexposed topic id.");
|
|
4325
4512
|
const topicId = existing?.id ?? `topic_${randomUUID()}`;
|
|
4326
4513
|
if (touched.has(topicId)) throw new TopicProjectionError("validation-failed", "A topic may be updated only once per projection.");
|
|
4327
4514
|
touched.add(topicId);
|
|
4328
4515
|
if (typeof proposal.title !== "string" || !proposal.title.trim() || [...proposal.title].length > 120 || typeof proposal.description !== "string" || !proposal.description.trim() || [...proposal.description].length > 400) throw new TopicProjectionError("validation-failed", "Invalid topic title or description.");
|
|
4329
|
-
for (const eventId of
|
|
4516
|
+
for (const eventId of ids2) if (batchIds.has(eventId)) covered.add(eventId);
|
|
4330
4517
|
if (!Array.isArray(proposal.overview) || proposal.overview.length > 8) throw new TopicProjectionError("validation-failed", "Topic projection must contain at most 8 overview paragraphs per chapter per batch.");
|
|
4331
|
-
for (const part of proposal.overview) {
|
|
4518
|
+
for (const [overviewIndex, part] of proposal.overview.entries()) {
|
|
4332
4519
|
if (part.title !== void 0 && (typeof part.title !== "string" || !part.title.trim() || [...part.title].length > 80)) throw new TopicProjectionError("validation-failed", "Invalid topic overview title.");
|
|
4333
|
-
if (!["history", "decision", "change", "open-question", "scope"].includes(part.kind)
|
|
4520
|
+
if (!["history", "decision", "change", "open-question", "scope"].includes(part.kind)) throw new TopicProjectionError("validation-failed", "Invalid topic overview section.");
|
|
4521
|
+
validateTopicOverviewText(part.text, `topics[${topicIndex}].overview[${overviewIndex}].text`);
|
|
4334
4522
|
checkIds(part.sourceEventIds, "overview");
|
|
4335
4523
|
if (part.kind !== "scope" && part.sourceEventIds.some((eventId) => job.context.truncatedEventIds?.includes(eventId))) {
|
|
4336
4524
|
throw new TopicProjectionError("validation-failed", "Truncated topic sources permit navigation scope only, not factual overview.");
|
|
4337
4525
|
}
|
|
4338
|
-
if (part.sourceEventIds.length > 12 || part.sourceEventIds.some((eventId) => !
|
|
4526
|
+
if (part.sourceEventIds.length > 12 || part.sourceEventIds.some((eventId) => !ids2.includes(eventId))) throw new TopicProjectionError("validation-failed", "Overview references must belong to returned topic membership.");
|
|
4339
4527
|
if (part.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !candidates.get(topicId)?.overview.some((old) => digest(old) === digest(part))) throw new TopicProjectionError("validation-failed", "Old overview evidence must be preserved verbatim; new sections need batch events.");
|
|
4340
4528
|
}
|
|
4341
4529
|
if (proposal.sections !== void 0 && !Array.isArray(proposal.sections)) throw new TopicProjectionError("validation-failed", "Invalid topic sections.");
|
|
@@ -4345,11 +4533,11 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4345
4533
|
if (!section || typeof section.title !== "string" || !section.title.trim() || [...section.title].length > 80 || sectionLabels.has(label(section.title))) throw new TopicProjectionError("validation-failed", "Invalid or duplicate topic section title.");
|
|
4346
4534
|
sectionLabels.add(label(section.title));
|
|
4347
4535
|
checkIds(section.sourceEventIds, "section");
|
|
4348
|
-
if (section.sourceEventIds.some((eventId) => !
|
|
4536
|
+
if (section.sourceEventIds.some((eventId) => !ids2.includes(eventId))) throw new TopicProjectionError("validation-failed", "Section references must belong to returned topic membership.");
|
|
4349
4537
|
if (section.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !memoryTopicMembershipSections(existing ?? { overview: [] }).some((old) => label(old.title) === label(section.title) && section.sourceEventIds.filter((eventId) => !batchIds.has(eventId)).every((eventId) => old.sourceEventIds.includes(eventId)))) throw new TopicProjectionError("validation-failed", "Old section membership must be preserved; reassignment needs batch events.");
|
|
4350
4538
|
}
|
|
4351
|
-
if (proposal.sections !== void 0 &&
|
|
4352
|
-
const membership = unique2([...(existing?.sourceEventIds ?? []).filter((eventId) => sources2.has(eventId)), ...
|
|
4539
|
+
if (proposal.sections !== void 0 && ids2.some((eventId) => batchIds.has(eventId) && !assignments.some((section) => section.sourceEventIds.includes(eventId)))) throw new TopicProjectionError("validation-failed", "Topic sections omitted batch events.");
|
|
4540
|
+
const membership = unique2([...(existing?.sourceEventIds ?? []).filter((eventId) => sources2.has(eventId)), ...ids2]);
|
|
4353
4541
|
const assignedIds = new Set(proposal.sections === void 0 ? [] : assignments.flatMap((section) => section.sourceEventIds).filter((eventId) => batchIds.has(eventId)));
|
|
4354
4542
|
const sections = existing && !existing.invalidated ? memoryTopicMembershipSections(existing) : [];
|
|
4355
4543
|
const memberIds = new Set(membership);
|
|
@@ -4367,7 +4555,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4367
4555
|
throw new TopicProjectionError("validation-failed", "New topic overview must match a final topic section.");
|
|
4368
4556
|
}
|
|
4369
4557
|
}
|
|
4370
|
-
const overview2 =
|
|
4558
|
+
const overview2 = uniqueTopicOverviewEntries(proposal.overview.filter((part) => memoryTopicOverviewMatchesSection(part, titles))).map((part) => structuredClone(part));
|
|
4371
4559
|
for (const part of inherited) if (memoryTopicOverviewMatchesSection(part, titles) && !overview2.some((current) => digest(current) === digest(part))) overview2.push(structuredClone(part));
|
|
4372
4560
|
proposals.push({
|
|
4373
4561
|
id: topicId,
|
|
@@ -4385,6 +4573,8 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4385
4573
|
});
|
|
4386
4574
|
}
|
|
4387
4575
|
if (job.sourceEventIds.some((eventId) => !covered.has(eventId))) throw new TopicProjectionError("validation-failed", "every supplied batch Event must be assigned to a topic");
|
|
4576
|
+
repairs.assert(result);
|
|
4577
|
+
repairs.assert({ topics: proposals });
|
|
4388
4578
|
for (const topic of proposals) {
|
|
4389
4579
|
const index = this.state.topics.findIndex((current) => current.id === topic.id);
|
|
4390
4580
|
if (index < 0) this.state.topics.push(topic);
|
|
@@ -4400,6 +4590,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4400
4590
|
if (this.state.rebuildVersions?.[eventId] === version) delete this.state.rebuildVersions[eventId];
|
|
4401
4591
|
}
|
|
4402
4592
|
this.state.topics = this.state.topics.filter((topic) => !topic.invalidated || topic.sourceEventIds.some((eventId) => sources2.has(eventId) && this.state.projectedVersions[eventId] !== versions.get(eventId)));
|
|
4593
|
+
job.overviewRepairRequirements = void 0;
|
|
4403
4594
|
job.status = "completed";
|
|
4404
4595
|
job.topicIds = proposals.map((topic) => topic.id);
|
|
4405
4596
|
job.context = null;
|
|
@@ -4415,6 +4606,10 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4415
4606
|
const job = this.state.jobs.find((candidate) => candidate.id === id);
|
|
4416
4607
|
if (!job || job.status !== "running") return;
|
|
4417
4608
|
const diagnostic = error instanceof TopicProjectionError ? safeTopicDiagnostics(error.diagnostics) : void 0;
|
|
4609
|
+
const repairs = new TopicOverviewRepairGuard(job.overviewRepairRequirements);
|
|
4610
|
+
const observed = topicOverviewRepairsFromError(error);
|
|
4611
|
+
repairs.merge(observed.length ? observed : topicOverviewRepairsForHistoricalFailure(diagnostic));
|
|
4612
|
+
job.overviewRepairRequirements = repairs.requirements.length ? repairs.requirements : void 0;
|
|
4418
4613
|
const message = error instanceof Error ? error.message : String(error);
|
|
4419
4614
|
const category = diagnostic?.category ?? (/timeout|timed out/i.test(message) ? "timeout" : /source.*changed|stale|not running/i.test(message) ? "source-changed" : "worker-failed");
|
|
4420
4615
|
job.diagnostics = safeTopicDiagnostics({
|
|
@@ -4434,12 +4629,13 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4434
4629
|
job.updatedAt = now;
|
|
4435
4630
|
if (category === "max-tokens" && diagnostic?.finishReason === "max-tokens" && job.sourceEventIds.length > 1 && (job.splitDepth ?? 0) < 4) {
|
|
4436
4631
|
const middle = Math.ceil(job.sourceEventIds.length / 2);
|
|
4437
|
-
for (const
|
|
4632
|
+
for (const ids2 of [job.sourceEventIds.slice(0, middle), job.sourceEventIds.slice(middle)]) {
|
|
4438
4633
|
this.state.jobs.push({
|
|
4439
4634
|
...structuredClone(job),
|
|
4440
4635
|
id: `tproj_${randomUUID()}`,
|
|
4441
|
-
sourceEventIds:
|
|
4442
|
-
sourceVersions: Object.fromEntries(
|
|
4636
|
+
sourceEventIds: ids2,
|
|
4637
|
+
sourceVersions: Object.fromEntries(ids2.map((id2) => [id2, job.sourceVersions[id2]])),
|
|
4638
|
+
overviewRepairRequirements: partitionTopicOverviewRepairs(job.overviewRepairRequirements, job.sourceEventIds, ids2),
|
|
4443
4639
|
dependencyVersions: {},
|
|
4444
4640
|
candidateVersions: {},
|
|
4445
4641
|
status: "pending",
|
|
@@ -4463,15 +4659,12 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4463
4659
|
if (!job) throw new Error(`Unknown topic projection: ${id}`);
|
|
4464
4660
|
return job;
|
|
4465
4661
|
}
|
|
4466
|
-
|
|
4467
|
-
const previous = this.requireJob(id);
|
|
4468
|
-
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
4469
|
-
const versions = sourceVersions(sources2);
|
|
4470
|
-
if (previous.superseded || previous.status !== "failed" || previous.attempts < TOPIC_MAX_ATTEMPTS || previous.nextRetryAt !== null || !jobInputsCurrent(previous, versions) || previous.sourceEventIds.some((eventId) => this.state.projectedVersions[eventId] === versions.get(eventId) && (!previous.sectionBackfillTopicId || this.state.sectionBackfill?.[previous.sectionBackfillTopicId]?.[eventId] !== versions.get(eventId)))) {
|
|
4471
|
-
throw new Error("Topic retry conflict: failure is no longer current or retryable.");
|
|
4472
|
-
}
|
|
4662
|
+
requeue(previous, ids2, versions, now) {
|
|
4473
4663
|
const job = {
|
|
4474
4664
|
...structuredClone(previous),
|
|
4665
|
+
sourceEventIds: [...ids2],
|
|
4666
|
+
overviewRepairRequirements: partitionTopicOverviewRepairs(previous.overviewRepairRequirements ?? topicOverviewRepairsForHistoricalFailure(safeTopicDiagnostics(previous.diagnostics)), previous.sourceEventIds, ids2),
|
|
4667
|
+
sourceVersions: Object.fromEntries(ids2.map((id) => [id, previous.sourceVersions[id]])),
|
|
4475
4668
|
id: `tproj_${randomUUID()}`,
|
|
4476
4669
|
status: "pending",
|
|
4477
4670
|
attempts: 0,
|
|
@@ -4496,6 +4689,17 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4496
4689
|
bootstrap.status = "pending";
|
|
4497
4690
|
bootstrap.completedAt = null;
|
|
4498
4691
|
}
|
|
4692
|
+
return job;
|
|
4693
|
+
}
|
|
4694
|
+
retry(id, events, now) {
|
|
4695
|
+
const previous = this.requireJob(id);
|
|
4696
|
+
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
4697
|
+
const versions = sourceVersions(sources2);
|
|
4698
|
+
const ids2 = memoryTopicUnfinishedJobEventIds(previous, this.state, versions);
|
|
4699
|
+
if (previous.superseded || previous.status !== "failed" || previous.attempts < TOPIC_MAX_ATTEMPTS || previous.nextRetryAt !== null || !jobInputsCurrent(previous, versions) || !ids2.length) {
|
|
4700
|
+
throw new Error("Topic retry conflict: failure is no longer current or retryable.");
|
|
4701
|
+
}
|
|
4702
|
+
const job = this.requeue(previous, ids2, versions, now);
|
|
4499
4703
|
this.synchronize(events, now);
|
|
4500
4704
|
return { jobId: job.id, status: "pending" };
|
|
4501
4705
|
}
|
|
@@ -4579,8 +4783,8 @@ function restrictExtractionRelations(temporal, allowedIds) {
|
|
|
4579
4783
|
}
|
|
4580
4784
|
for (const field of ["beforeEventIds", "afterEventIds", "supersedesEventIds", "conflictsWithEventIds", "relatedEventIds"]) {
|
|
4581
4785
|
const values = temporal[field];
|
|
4582
|
-
const
|
|
4583
|
-
if (
|
|
4786
|
+
const ids2 = Array.isArray(values) ? [...new Set(values.filter((id) => typeof id === "string" && allowedIds.has(id)))] : [];
|
|
4787
|
+
if (ids2.length > 0) result[field] = ids2;
|
|
4584
4788
|
else delete result[field];
|
|
4585
4789
|
}
|
|
4586
4790
|
return result;
|
|
@@ -4889,13 +5093,13 @@ var StrataGate = class _StrataGate {
|
|
|
4889
5093
|
listSummaryJobs() {
|
|
4890
5094
|
return [...this.summaryJobs.values()];
|
|
4891
5095
|
}
|
|
4892
|
-
runManualRetry(
|
|
4893
|
-
const existing = this.manualRetryRuns.get(
|
|
5096
|
+
runManualRetry(key2, operation) {
|
|
5097
|
+
const existing = this.manualRetryRuns.get(key2);
|
|
4894
5098
|
if (existing) return existing;
|
|
4895
5099
|
const run = operation().finally(() => {
|
|
4896
|
-
if (this.manualRetryRuns.get(
|
|
5100
|
+
if (this.manualRetryRuns.get(key2) === run) this.manualRetryRuns.delete(key2);
|
|
4897
5101
|
});
|
|
4898
|
-
this.manualRetryRuns.set(
|
|
5102
|
+
this.manualRetryRuns.set(key2, run);
|
|
4899
5103
|
return run;
|
|
4900
5104
|
}
|
|
4901
5105
|
/** Give one failed Block Summary job a fresh, user-requested retry budget. */
|
|
@@ -5451,9 +5655,9 @@ var StrataGate = class _StrataGate {
|
|
|
5451
5655
|
decisions.push(audit2);
|
|
5452
5656
|
}
|
|
5453
5657
|
if (addedEvents.length > 0) {
|
|
5454
|
-
const
|
|
5455
|
-
this.queueElementProjection(
|
|
5456
|
-
this.queueGraphProjection(
|
|
5658
|
+
const ids2 = addedEvents.map(({ id }) => id);
|
|
5659
|
+
this.queueElementProjection(ids2);
|
|
5660
|
+
this.queueGraphProjection(ids2, 2e3);
|
|
5457
5661
|
}
|
|
5458
5662
|
return {
|
|
5459
5663
|
sourceBlockId: source.id,
|
|
@@ -6425,8 +6629,8 @@ var StrataGate = class _StrataGate {
|
|
|
6425
6629
|
const matchDetails = (node) => {
|
|
6426
6630
|
const view = viewById.get(node.id);
|
|
6427
6631
|
const queryParts = normalizeSearchText(query).split(/[^\p{Letter}\p{Number}_]+/gu).filter(Boolean);
|
|
6428
|
-
const explicitFactKeys = dedupe([...view.currentFacts, ...view.historicalFacts].map(({ key }) =>
|
|
6429
|
-
const knownFactKeyTokens = new Set(explicitFactKeys.flatMap((
|
|
6632
|
+
const explicitFactKeys = dedupe([...view.currentFacts, ...view.historicalFacts].map(({ key: key2 }) => key2)).filter((key2) => queryParts.includes(normalizeSearchText(key2)));
|
|
6633
|
+
const knownFactKeyTokens = new Set(explicitFactKeys.flatMap((key2) => [...tokenSet(key2)]));
|
|
6430
6634
|
const queryKeyTokens = queryTokens.filter((token) => knownFactKeyTokens.has(token));
|
|
6431
6635
|
const valueQueryTokens = queryTokens.filter((token) => !queryKeyTokens.includes(token));
|
|
6432
6636
|
const meaningfulTokens = queryTokens;
|
|
@@ -6528,13 +6732,13 @@ var StrataGate = class _StrataGate {
|
|
|
6528
6732
|
...details.currentEdges.flatMap(({ sourceEventIds }) => sourceEventIds),
|
|
6529
6733
|
...details.historicalEdges.flatMap(({ sourceEventIds }) => sourceEventIds)
|
|
6530
6734
|
]);
|
|
6531
|
-
const historicalKeys = new Set(details.historicalFacts.map(({ key }) =>
|
|
6735
|
+
const historicalKeys = new Set(details.historicalFacts.map(({ key: key2 }) => key2));
|
|
6532
6736
|
const historicalRelations = new Set(details.historicalEdges.map(({ relation }) => relation));
|
|
6533
6737
|
const currentContextEventIds = dedupe([
|
|
6534
|
-
...details.view.currentFacts.filter(({ key }) => historicalKeys.has(
|
|
6738
|
+
...details.view.currentFacts.filter(({ key: key2 }) => historicalKeys.has(key2)).flatMap(({ sourceEventIds }) => sourceEventIds),
|
|
6535
6739
|
...details.view.currentEdges.filter(({ relation }) => historicalRelations.has(relation)).flatMap(({ sourceEventIds }) => sourceEventIds)
|
|
6536
6740
|
]);
|
|
6537
|
-
const currentContextFacts = details.view.currentFacts.filter(({ key }) => historicalKeys.has(
|
|
6741
|
+
const currentContextFacts = details.view.currentFacts.filter(({ key: key2 }) => historicalKeys.has(key2));
|
|
6538
6742
|
const currentContextEdges = details.view.currentEdges.filter(({ relation }) => historicalRelations.has(relation));
|
|
6539
6743
|
const nameEventIds = node.metadataProvenance?.name ?? [];
|
|
6540
6744
|
const metadataCandidates = dedupe([...details.matchedMetadataEventIds, ...nameEventIds]);
|
|
@@ -6561,9 +6765,9 @@ var StrataGate = class _StrataGate {
|
|
|
6561
6765
|
const boundedView = boundEffectiveGraphNodeView(details.view, evidenceIds, {
|
|
6562
6766
|
preserveLegacyMetadata: legacyMetadataNotExpanded
|
|
6563
6767
|
});
|
|
6564
|
-
const bounded2 = (
|
|
6565
|
-
const sourceEventIds =
|
|
6566
|
-
return sourceEventIds.length > 0 ? { ...
|
|
6768
|
+
const bounded2 = (record3) => {
|
|
6769
|
+
const sourceEventIds = record3.sourceEventIds.filter((id) => evidenceIds.has(id));
|
|
6770
|
+
return sourceEventIds.length > 0 ? { ...record3, sourceEventIds } : null;
|
|
6567
6771
|
};
|
|
6568
6772
|
const matchType = details.currentHit && details.historicalHit ? "both" : details.historicalHit ? "historical" : "current";
|
|
6569
6773
|
return {
|
|
@@ -6572,10 +6776,10 @@ var StrataGate = class _StrataGate {
|
|
|
6572
6776
|
matchedFields: details.matchedFields,
|
|
6573
6777
|
matchReason: `Meaningful ${matchType} match in ${details.matchedFields.join(", ")}; relation text is ranking-only.`,
|
|
6574
6778
|
matchType,
|
|
6575
|
-
currentFacts: dedupe([...details.currentFacts, ...currentContextFacts]).flatMap((
|
|
6576
|
-
historicalFacts: details.historicalFacts.flatMap((
|
|
6577
|
-
currentEdges: dedupe([...details.currentEdges, ...currentContextEdges]).flatMap((
|
|
6578
|
-
historicalEdges: details.historicalEdges.flatMap((
|
|
6779
|
+
currentFacts: dedupe([...details.currentFacts, ...currentContextFacts]).flatMap((record3) => bounded2(record3) ?? []),
|
|
6780
|
+
historicalFacts: details.historicalFacts.flatMap((record3) => bounded2(record3) ?? []),
|
|
6781
|
+
currentEdges: dedupe([...details.currentEdges, ...currentContextEdges]).flatMap((record3) => bounded2(record3) ?? []),
|
|
6782
|
+
historicalEdges: details.historicalEdges.flatMap((record3) => bounded2(record3) ?? []),
|
|
6579
6783
|
provenanceEventIds,
|
|
6580
6784
|
...legacyMetadataNotExpanded ? { metadataEvidenceStatus: "not_expanded" } : {},
|
|
6581
6785
|
timeline: graphTimeline(provenanceEventIds, this.listAllEvents())
|
|
@@ -6591,12 +6795,12 @@ var StrataGate = class _StrataGate {
|
|
|
6591
6795
|
if (!this.graphProjector) return null;
|
|
6592
6796
|
const completed = new Set([...this.graphProjectionJobs.values()].filter((job2) => job2.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job2.status === "completed").flatMap((job2) => job2.sourceEventIds));
|
|
6593
6797
|
const queued = new Set([...this.graphProjectionJobs.values()].filter((job2) => job2.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job2.status !== "completed").flatMap((job2) => job2.sourceEventIds));
|
|
6594
|
-
const
|
|
6595
|
-
if (
|
|
6798
|
+
const ids2 = [...new Set(sourceEventIds.filter((id) => this.findEvent(id) && !completed.has(id) && !queued.has(id)))];
|
|
6799
|
+
if (ids2.length === 0) return null;
|
|
6596
6800
|
const now = toUtc8Iso(this.now());
|
|
6597
6801
|
const job = {
|
|
6598
6802
|
id: this.graphIdFactory("gproj"),
|
|
6599
|
-
sourceEventIds:
|
|
6803
|
+
sourceEventIds: ids2,
|
|
6600
6804
|
projectorVersion: KNOWLEDGE_GRAPH_PROJECTOR_VERSION,
|
|
6601
6805
|
status: "pending",
|
|
6602
6806
|
attempts: 0,
|
|
@@ -6639,12 +6843,12 @@ var StrataGate = class _StrataGate {
|
|
|
6639
6843
|
}
|
|
6640
6844
|
queueElementProjection(sourceEventIds) {
|
|
6641
6845
|
if (this.disableElementProjection) return null;
|
|
6642
|
-
const
|
|
6643
|
-
if (
|
|
6846
|
+
const ids2 = [...new Set(sourceEventIds.filter((id) => Boolean(this.findEvent(id))))];
|
|
6847
|
+
if (ids2.length === 0) return null;
|
|
6644
6848
|
const now = toUtc8Iso(this.now());
|
|
6645
6849
|
const job = {
|
|
6646
6850
|
id: this.elementIdFactory("proj"),
|
|
6647
|
-
sourceEventIds:
|
|
6851
|
+
sourceEventIds: ids2,
|
|
6648
6852
|
status: "pending",
|
|
6649
6853
|
attempts: 0,
|
|
6650
6854
|
elementIds: [],
|
|
@@ -6667,8 +6871,8 @@ var StrataGate = class _StrataGate {
|
|
|
6667
6871
|
this.pendingRawUpserts.set(message.id, structuredClone(message));
|
|
6668
6872
|
}
|
|
6669
6873
|
}
|
|
6670
|
-
markRawMessagesDeleted(
|
|
6671
|
-
for (const id of
|
|
6874
|
+
markRawMessagesDeleted(ids2) {
|
|
6875
|
+
for (const id of ids2) {
|
|
6672
6876
|
this.pendingRawUpserts.delete(id);
|
|
6673
6877
|
this.pendingRawDeletes.add(id);
|
|
6674
6878
|
this.rawMessageLookup.delete(id);
|
|
@@ -6962,9 +7166,9 @@ var StrataGate = class _StrataGate {
|
|
|
6962
7166
|
...event.temporal ? { temporal: restrictExtractionRelations(event.temporal, allowedIds) } : {}
|
|
6963
7167
|
})) : [];
|
|
6964
7168
|
if (extracted.length > 0) {
|
|
6965
|
-
const
|
|
6966
|
-
this.queueElementProjection(
|
|
6967
|
-
this.queueGraphProjection(
|
|
7169
|
+
const ids2 = extracted.map(({ id }) => id);
|
|
7170
|
+
this.queueElementProjection(ids2);
|
|
7171
|
+
this.queueGraphProjection(ids2, 1e3);
|
|
6968
7172
|
}
|
|
6969
7173
|
const job = this.extractionJobs.get(target.id);
|
|
6970
7174
|
if (!job) throw new Error(`Missing extraction job for block: ${target.id}`);
|
|
@@ -7381,7 +7585,7 @@ function isObject(value) {
|
|
|
7381
7585
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7382
7586
|
}
|
|
7383
7587
|
function hasRequiredKeys(value, requiredKeys) {
|
|
7384
|
-
return requiredKeys.every((
|
|
7588
|
+
return requiredKeys.every((key2) => Object.prototype.hasOwnProperty.call(value, key2));
|
|
7385
7589
|
}
|
|
7386
7590
|
function balancedValueEnd(value, start) {
|
|
7387
7591
|
const opening = value[start];
|
|
@@ -7633,13 +7837,13 @@ function normalizeGraphNameProvenance(value) {
|
|
|
7633
7837
|
for (const candidate of result.nodes) {
|
|
7634
7838
|
const node = object(candidate);
|
|
7635
7839
|
const metadata = object(node.metadataProvenance);
|
|
7636
|
-
const
|
|
7637
|
-
if (typeof node.name !== "string" || !Array.isArray(
|
|
7638
|
-
if (!
|
|
7840
|
+
const entries2 = metadata.name;
|
|
7841
|
+
if (typeof node.name !== "string" || !Array.isArray(entries2) || entries2.length === 0) continue;
|
|
7842
|
+
if (!entries2.every((candidateEntry) => {
|
|
7639
7843
|
const entry = object(candidateEntry);
|
|
7640
|
-
return entry.value === node.name && Object.keys(entry).every((
|
|
7844
|
+
return entry.value === node.name && Object.keys(entry).every((key2) => key2 === "value" || key2 === "sourceEventIds") && Array.isArray(entry.sourceEventIds) && entry.sourceEventIds.length > 0 && entry.sourceEventIds.every((id) => typeof id === "string");
|
|
7641
7845
|
})) continue;
|
|
7642
|
-
metadata.name = [...new Set(
|
|
7846
|
+
metadata.name = [...new Set(entries2.flatMap((entry) => entry.sourceEventIds))];
|
|
7643
7847
|
}
|
|
7644
7848
|
return value;
|
|
7645
7849
|
}
|
|
@@ -7731,7 +7935,8 @@ Topic \u662F\u957F\u671F\u7684\u5927\u7AE0\u8282\uFF0C\u6309\u9879\u76EE\u3001\u
|
|
|
7731
7935
|
\u5BFC\u822A\u5C42\u7EA7\u662F\u7AE0 \u2192 \u8282 \u2192 Event\u3002Event \u4FDD\u6301\u5177\u4F53\u3001\u72EC\u7ACB\uFF1B\u8282\u5FC5\u987B\u80FD\u6301\u7EED\u5BB9\u7EB3\u540C\u7C7B\u4E8B\u4EF6\u3002\u5355\u4E2A bug\u3001\u7248\u672C\u53F7\u3001\u67D0\u5929\u8FDB\u5C55\u3001\u4E00\u6B21\u5B89\u88C5\u6216\u4E00\u4E2A\u4FEE\u590D\u65B9\u6848\u4E0D\u80FD\u5355\u72EC\u6210\u8282\uFF0C\u4E5F\u4E0D\u8981\u7167\u6284 Event \u6807\u9898\u4F5C\u4E3A\u8282\u6807\u9898\u3002\u4F8B\u5982\u201C0.2.4 \u63D0\u53D6\u6839\u56E0\u66B4\u9732\u8FC7\u7A0B\u201D\u201C0.2.6 \u63D0\u53D6\u4ECD 0 \u4EA7\u51FA\u201D\u201C0.2.7 \u4FEE\u590D\u65B9\u6848\u201D\u201C\u5C01\u5757\u5F02\u5E38\u5F85\u6392\u67E5\u201D\u90FD\u8FDB\u5165\u201C\u7F3A\u9677\u6392\u67E5\u4E0E\u4FEE\u590D\u201D\uFF1B\u201C\u4ECA\u65E5\u7248\u672C\u8FED\u4EE3 0.2.1\u21920.2.7\u201D\u201C0.2.8 \u5B89\u88C5\u4E0E\u9A8C\u8BC1\u201D\u8FDB\u5165\u201C\u7248\u672C\u53D1\u5E03\u4E0E\u5B89\u88C5\u201D\u3002\u5177\u4F53\u7248\u672C\u3001\u65F6\u95F4\u3001\u6839\u56E0\u3001\u5904\u7F6E\u8FC7\u7A0B\u548C\u72B6\u6001\u653E\u5728\u8282\u5185\u6982\u8981\u53CA\u539F Event \u4E2D\u3002\u201C\u8BA1\u5212\u4FEE\u590D\u201D\u201C\u4FEE\u590D\u4E2D\u201D\u201C\u5DF2\u4FEE\u590D\u201D\u5C5E\u4E8E\u540C\u4E00\u7C7B\u522B\u7684\u4E0D\u540C\u72B6\u6001\uFF0C\u4E0D\u53E6\u8D77\u8282\u3002\u540C\u4E00\u7C7B\u522B\u53EF\u4EE5\u6709\u591A\u4E2A\u6709\u6765\u6E90\u7684\u6BB5\u843D\uFF0C\u4E0D\u8981\u4E3A\u6BB5\u843D\u53E6\u8D77\u6807\u9898\u3002
|
|
7732
7936
|
\u5148\u627E\u672C\u7AE0\u5DF2\u6709\u7C7B\u522B\uFF1B\u5B83\u80FD\u5BB9\u7EB3\u65F6\u5FC5\u987B\u590D\u7528\u3002\u65B0\u7AE0\u5148\u5F52\u7EB3\u5C11\u91CF\u957F\u671F\u7C7B\u522B\uFF0C\u4E0D\u628A\u6BCF\u4E2A Event \u5404\u5EFA\u4E00\u8282\uFF1B\u540E\u7EED\u6279\u6B21\u6CBF\u7528\u8FD9\u4E9B\u7C7B\u522B\uFF0C\u6BCF\u6279\u901A\u5E38\u53EA\u65B0\u589E 0-2 \u4E2A\u8282\u3002\u7C7B\u522B\u6570\u91CF\u7531\u771F\u5B9E\u5185\u5BB9\u51B3\u5B9A\uFF0C\u4E0D\u8BBE\u786C\u8282\u6570\u4E0A\u9650\uFF0C\u4E5F\u4E0D\u8981\u628A\u65E0\u5173\u7C7B\u522B\u786C\u585E\u8FDB\u201C\u5176\u4ED6\u201D\u3002\u4EE5\u4E0A\u662F\u9879\u76EE\u7AE0\u7684\u4F8B\u5B50\uFF0C\u4E0D\u662F\u5168\u5C40\u56FA\u5B9A\u5206\u7C7B\u8868\uFF1B\u6C42\u804C\u7AE0\u53EF\u7528\u201C\u6295\u9012\u4E0E\u9762\u8BD5\u201D\u201C\u804C\u4E1A\u9009\u62E9\u201D\u201C\u5165\u804C\u4E0E\u9002\u5E94\u201D\uFF0C\u751F\u6D3B\u7AE0\u4F7F\u7528\u5176\u81EA\u8EAB\u9002\u5408\u7684\u957F\u671F\u7C7B\u522B\u3002\u7ADE\u54C1\u5206\u6790\u3001\u5B9E\u9A8C\u8BC4\u6D4B\u3001\u5B89\u5168\u4E0E\u9690\u79C1\u7B49\u5185\u5BB9\u9700\u8981\u65F6\u5404\u5F52\u5165\u76F8\u5E94\u7C7B\u522B\uFF0C\u4E0D\u7EDF\u4E00\u585E\u8FDB\u4FEE bug\u3002
|
|
7733
7937
|
|
|
7734
|
-
title \u662F\u53EF\u8BC6\u522B\u7684\u4E3B\u9898\u540D\u79F0\uFF1Bdescription \u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8FD9\u91CC\u5B58\u6709\u54EA\u4E9B\u8D44\u6599\uFF0C\u4F8B\u5982\u201C\u5305\u542B\u90E8\u7F72\u9009\u578B\u3001\u8FC1\u79FB\u7ECF\u8FC7\u548C\u9057\u7559\u95EE\u9898\u201D\uFF0C\u4E0D\u8981\u628A\u5386\u53F2\u7ED3\u8BBA\u5199\u6210\u5F53\u524D\u4E8B\u5B9E\
|
|
7938
|
+
title \u662F\u53EF\u8BC6\u522B\u7684\u4E3B\u9898\u540D\u79F0\uFF1Bdescription \u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8FD9\u91CC\u5B58\u6709\u54EA\u4E9B\u8D44\u6599\uFF0C\u4F8B\u5982\u201C\u5305\u542B\u90E8\u7F72\u9009\u578B\u3001\u8FC1\u79FB\u7ECF\u8FC7\u548C\u9057\u7559\u95EE\u9898\u201D\uFF0C\u4E0D\u8981\u628A\u5386\u53F2\u7ED3\u8BBA\u5199\u6210\u5F53\u524D\u4E8B\u5B9E\u3002overviewRepairRequirements \u5982\u5B58\u5728\uFF0C\u8868\u793A\u6B64\u524D\u4E0D\u5408\u683C\u6982\u8981\u5FC5\u987B\u63D0\u4F9B\u5408\u6CD5\u66FF\u4EE3\uFF1A\u4FDD\u7559\u5BF9\u5E94 kind\u3001\u6765\u6E90\u7F16\u53F7\u548C\u81F3\u5C11 minimumEntries \u6761\uFF1B\u4E0D\u5F97\u5220\u9664\u6BB5\u843D\u3001\u6E05\u7A7A overview\u3001\u6362\u6389\u6765\u6E90\u6216\u964D\u4F4E\u4E8B\u5B9E\u6027\u8D28\u6765\u7ED5\u8FC7\u6821\u9A8C\u3002\u9996\u6B21\u6CA1\u6709\u4FEE\u590D\u7EA6\u675F\u65F6\uFF0C\u6B63\u5E38\u7684\u7A7A overview \u4ECD\u5408\u6CD5\u3002
|
|
7939
|
+
overview \u7684 kind \u4EC5\u5141\u8BB8 scope\uFF08\u80CC\u666F\u6216\u8986\u76D6\u8303\u56F4\uFF09\u3001history\uFF08\u5386\u53F2\u8FDB\u5C55\uFF09\u3001decision\uFF08\u5F53\u65F6\u7684\u51B3\u5B9A\uFF09\u3001change\uFF08\u6709\u4F9D\u636E\u7684\u53D8\u5316\uFF09\u3001open-question\uFF08\u5F85\u786E\u8BA4\u4E8B\u9879\uFF09\u3002\u4FDD\u7559\u660E\u786E\u65F6\u70B9\u3001\u8FC7\u53BB\u5F0F\u3001\u8BA1\u5212\u3001\u53D6\u6D88\u3001\u4E89\u8BAE\u548C\u4E0D\u786E\u5B9A\u6027\uFF1B\u6CA1\u6709\u65B0\u7684\u652F\u6301\u4E0D\u80FD\u628A\u65E7\u4E8B\u5B9E\u5347\u683C\u4E3A\u5F53\u524D\u7ED3\u8BBA\uFF0C\u4E0D\u80FD\u628A\u8BA1\u5212\u5199\u6210\u5DF2\u5B8C\u6210\u3001\u5EFA\u8BAE\u5199\u6210\u5DF2\u51B3\u5B9A\u3001\u63A8\u6D4B\u5199\u6210\u539F\u56E0\u3002\u51B3\u5B9A\u66FE\u7ECF\u6210\u7ACB\u4E0D\u7B49\u4E8E\u73B0\u5728\u4ECD\u6709\u6548\u3002\u76F8\u5173\u4E8B\u4EF6\u77DB\u76FE\u65F6\u4FDD\u7559\u77DB\u76FE\u548C\u5F85\u786E\u8BA4\u72B6\u6001\u3002
|
|
7735
7940
|
|
|
7736
7941
|
sections \u72EC\u7ACB\u4FDD\u5B58\u4E8B\u4EF6\u7684\u5C0F\u8282\u5F52\u5C5E\uFF0C\u4E0E overview \u7684\u6765\u6E90\u5F15\u7528\u5206\u5F00\u3002\u6BCF\u4E2A\u5F52\u5165\u672C\u7AE0\u7684\u672C\u6279\u4E8B\u4EF6\u5FC5\u987B\u81F3\u5C11\u5206\u914D\u5230\u4E00\u4E2A sections \u5C0F\u8282\uFF0C\u5373\u4F7F\u603B\u89C8\u6CA1\u6709\u5F15\u7528\u5B83\u3002sections \u53EA\u5199\u672C\u6279\u4E8B\u4EF6\u7F16\u53F7\uFF1B\u65E7\u5F52\u5C5E\u7531\u5B58\u50A8\u5C42\u7EE7\u627F\u3002\u4F18\u5148\u9010\u5B57\u590D\u7528 existingTopics.sectionTitles \u4E2D\u9002\u5408\u7684\u6807\u9898\uFF1B\u5C0F\u8282\u6CBF\u7528\u4E0A\u8FF0\u957F\u671F\u7C7B\u522B\u89C4\u5219\u3002\u65B0 overview \u7684\u5C55\u793A\u6807\u9898\u5FC5\u987B\u5339\u914D\u672C\u8F6E sections.title \u6216\u91CD\u5206\u7C7B\u540E\u4ECD\u5B58\u5728\u7684\u5DF2\u6709\u5C0F\u8282\u6807\u9898\uFF0C\u4E0D\u80FD\u9760\u603B\u89C8\u5355\u72EC\u521B\u5EFA\u5C0F\u8282\uFF1B\u7701\u7565 title \u65F6\uFF0C\u5176 kind \u7684\u9ED8\u8BA4\u5C55\u793A\u6807\u9898\u4E5F\u5FC5\u987B\u6709\u5BF9\u5E94\u7684\u6700\u7EC8\u5C0F\u8282\u3002\u82E5\u91CD\u5206\u7C7B\u79FB\u8D70\u65E7\u5C0F\u8282\u7684\u5168\u90E8\u4E8B\u4EF6\uFF0C\u5B58\u50A8\u5C42\u4E22\u5F03\u8BE5\u8282\u7684\u65E7\u603B\u89C8\uFF0C\u5373\u4F7F\u672C\u8F6E\u539F\u6837\u56DE\u4F20\u4E5F\u4E0D\u4F1A\u4FDD\u7559\u3002overview.title \u53EA\u51B3\u5B9A\u6982\u62EC\u6587\u5B57\u663E\u793A\u5728\u54EA\u4E2A\u5C0F\u8282\uFF0C\u4E0D\u51B3\u5B9A\u4E8B\u4EF6\u5F52\u5C5E\uFF1B\u65E0\u9700\u4E3A\u6BCF\u6761\u4E8B\u4EF6\u5199\u603B\u89C8\uFF0C\u4E5F\u4E0D\u8981\u4E3A\u4E86\u8986\u76D6\u76EE\u5F55\u800C\u865A\u589E\u6765\u6E90\u5F15\u7528\u3002
|
|
7737
7942
|
\u82E5\u8F93\u5165\u542B sectionBackfillTopicId\uFF0C\u8FD9\u662F\u5347\u7EA7\u540E\u7684\u65E7\u4E8B\u4EF6\u8865\u5F52\u7C7B\uFF1A\u53EA\u8FD4\u56DE\u8BE5 topicId \u7684\u4E00\u4E2A\u7AE0\u8282\uFF0C\u4FDD\u7559\u539F\u7AE0\u540D\u548C\u63CF\u8FF0\uFF0C\u4E3A\u5168\u90E8\u672C\u6279\u4E8B\u4EF6\u5199\u5165\u9002\u5F53\u7684\u5C0F\u8282\uFF1B\u4E0D\u5F97\u6362\u7AE0\u6216\u65B0\u5EFA\u7AE0\u3002\u5DF2\u6709\u5C0F\u8282\u6807\u9898\u53EF\u80FD\u53EA\u5C55\u793A\u524D 120 \u4E2A\uFF0C\u4F18\u5148\u590D\u7528\u9002\u5408\u7684\u5DF2\u6709\u6807\u9898\u3002\u6B64\u4EFB\u52A1\u65E0\u9700\u91CD\u5199\u603B\u89C8\uFF0Coverview \u53EF\u4EE5\u4E3A\u7A7A\uFF0C\u5B58\u50A8\u5C42\u4FDD\u7559\u6709\u6548\u65E7\u603B\u89C8\u3002
|
|
@@ -7901,6 +8106,7 @@ function topicProjectionPayload(context, outputTokenBudget) {
|
|
|
7901
8106
|
...context.sectionBackfillTopicId ? { sectionBackfillTopicId: context.sectionBackfillTopicId } : {},
|
|
7902
8107
|
evidenceCompleteness: "bounded-navigation-cards; retrieve Event and original-source evidence before relying on factual content",
|
|
7903
8108
|
outputTokenBudget,
|
|
8109
|
+
...context.overviewRepairRequirements?.length ? { overviewRepairRequirements: context.overviewRepairRequirements } : {},
|
|
7904
8110
|
events,
|
|
7905
8111
|
truncatedEventIds: context.truncatedEventIds ?? [],
|
|
7906
8112
|
existingTopics: existingTopics.map((topic) => ({
|
|
@@ -7928,7 +8134,7 @@ function parseTopicProjection(value, context) {
|
|
|
7928
8134
|
throw new TopicProjectionError("validation-failed", message);
|
|
7929
8135
|
};
|
|
7930
8136
|
const raw = object(value);
|
|
7931
|
-
if (Object.keys(raw).some((
|
|
8137
|
+
if (Object.keys(raw).some((key2) => key2 !== "topics") || !Array.isArray(raw.topics) || raw.topics.length > 12) {
|
|
7932
8138
|
fail("expected only topics, with at most 12 entries");
|
|
7933
8139
|
}
|
|
7934
8140
|
const eventIds = new Set(context.events.map(({ id }) => id));
|
|
@@ -7948,13 +8154,13 @@ function parseTopicProjection(value, context) {
|
|
|
7948
8154
|
return value2;
|
|
7949
8155
|
};
|
|
7950
8156
|
const rawTopics = raw.topics;
|
|
7951
|
-
const topics = rawTopics.map((candidate) => {
|
|
8157
|
+
const topics = rawTopics.map((candidate, topicIndex) => {
|
|
7952
8158
|
const item = object(candidate);
|
|
7953
8159
|
const title = boundedText(item.title, "topic.title", 120);
|
|
7954
8160
|
const label = title.normalize("NFKC").replace(/\s+/g, " ").toLowerCase();
|
|
7955
8161
|
if (usedLabels.has(label)) fail("duplicate chapter label; combine its batch assignments");
|
|
7956
8162
|
usedLabels.add(label);
|
|
7957
|
-
if (Object.keys(item).some((
|
|
8163
|
+
if (Object.keys(item).some((key2) => !["topicId", "title", "description", "sourceEventIds", "overview", "sections"].includes(key2))) fail("unexpected topic field");
|
|
7958
8164
|
const topicId = item.topicId === void 0 ? void 0 : boundedText(item.topicId, "topicId", 200);
|
|
7959
8165
|
if (context.sectionBackfillTopicId && (rawTopics.length !== 1 || topicId !== context.sectionBackfillTopicId)) {
|
|
7960
8166
|
fail("section backfill must update only its existing chapter");
|
|
@@ -7970,11 +8176,11 @@ function parseTopicProjection(value, context) {
|
|
|
7970
8176
|
const sectionLabels = /* @__PURE__ */ new Set();
|
|
7971
8177
|
const sections = item.sections.map((candidateSection) => {
|
|
7972
8178
|
const section = object(candidateSection);
|
|
7973
|
-
if (Object.keys(section).some((
|
|
8179
|
+
if (Object.keys(section).some((key3) => !["title", "sourceEventIds"].includes(key3))) fail("unexpected section field");
|
|
7974
8180
|
const title2 = boundedText(section.title, "section.title", 80);
|
|
7975
|
-
const
|
|
7976
|
-
if (sectionLabels.has(
|
|
7977
|
-
sectionLabels.add(
|
|
8181
|
+
const key2 = title2.normalize("NFKC").replace(/\s+/g, " ").toLowerCase();
|
|
8182
|
+
if (sectionLabels.has(key2)) fail("duplicate section title");
|
|
8183
|
+
sectionLabels.add(key2);
|
|
7978
8184
|
return { title: title2, sourceEventIds: sourceIds(section.sourceEventIds, "section.sourceEventIds", new Set(sources2.filter((id) => eventIds.has(id)))) };
|
|
7979
8185
|
});
|
|
7980
8186
|
const assigned = new Set(sections.flatMap((section) => section.sourceEventIds));
|
|
@@ -7984,12 +8190,13 @@ function parseTopicProjection(value, context) {
|
|
|
7984
8190
|
...sections.map((section) => section.title),
|
|
7985
8191
|
...existing?.sectionTitles ?? memoryTopicMembershipSections(existing ?? { overview: [] }).map((section) => section.title)
|
|
7986
8192
|
];
|
|
7987
|
-
const overview2 = item.overview.map((candidateEntry) => {
|
|
8193
|
+
const overview2 = item.overview.map((candidateEntry, overviewIndex) => {
|
|
7988
8194
|
const entry = object(candidateEntry);
|
|
7989
|
-
if (Object.keys(entry).some((
|
|
8195
|
+
if (Object.keys(entry).some((key2) => !["kind", "title", "text", "sourceEventIds"].includes(key2))) fail("unexpected overview field");
|
|
7990
8196
|
const sectionTitle = entry.title === void 0 ? void 0 : boundedText(entry.title, "overview.title", 80);
|
|
7991
8197
|
if (!TOPIC_OVERVIEW_KINDS.includes(entry.kind)) fail("unknown overview kind");
|
|
7992
|
-
|
|
8198
|
+
validateTopicOverviewText(entry.text, `topics[${topicIndex}].overview[${overviewIndex}].text`);
|
|
8199
|
+
const paragraph = entry.text;
|
|
7993
8200
|
const paragraphSources = sourceIds(entry.sourceEventIds, "overview.sourceEventIds", new Set(sources2), 12);
|
|
7994
8201
|
if (entry.kind !== "scope" && paragraphSources.some((id) => truncatedIds.has(id))) fail("truncated Event evidence may only support scope entries");
|
|
7995
8202
|
const preserved = existing?.overview.some((old) => old.kind === entry.kind && old.title === sectionTitle && old.text === paragraph && old.sourceEventIds.length === paragraphSources.length && old.sourceEventIds.every((id) => paragraphSources.includes(id)));
|
|
@@ -8131,10 +8338,10 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8131
8338
|
}).filter((event) => event !== null);
|
|
8132
8339
|
const unique3 = /* @__PURE__ */ new Map();
|
|
8133
8340
|
for (const event of candidates) {
|
|
8134
|
-
const
|
|
8135
|
-
const prior = unique3.get(
|
|
8341
|
+
const key2 = exactKeys.get(event);
|
|
8342
|
+
const prior = unique3.get(key2);
|
|
8136
8343
|
if (!prior) {
|
|
8137
|
-
unique3.set(
|
|
8344
|
+
unique3.set(key2, event);
|
|
8138
8345
|
continue;
|
|
8139
8346
|
}
|
|
8140
8347
|
prior.sourceMessageIds = [.../* @__PURE__ */ new Set([...prior.sourceMessageIds, ...event.sourceMessageIds])];
|
|
@@ -8259,6 +8466,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8259
8466
|
};
|
|
8260
8467
|
topicProjector = async (context) => {
|
|
8261
8468
|
const { payload, shownContext } = topicProjectionPayload(context, TOPIC_OUTPUT_TOKEN_BUDGET);
|
|
8469
|
+
const repairs = new TopicOverviewRepairGuard(context.overviewRepairRequirements);
|
|
8262
8470
|
try {
|
|
8263
8471
|
const raw = await this.callStructured(
|
|
8264
8472
|
"topicProjector",
|
|
@@ -8266,16 +8474,18 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8266
8474
|
payload,
|
|
8267
8475
|
(value) => {
|
|
8268
8476
|
parseTopicProjection(value, shownContext);
|
|
8477
|
+
repairs.assert(value);
|
|
8269
8478
|
},
|
|
8270
|
-
{ eventCount: shownContext.events.length, candidateTopicCount: shownContext.existingTopics.length }
|
|
8479
|
+
{ eventCount: shownContext.events.length, candidateTopicCount: shownContext.existingTopics.length },
|
|
8480
|
+
{ guard: repairs, context: shownContext }
|
|
8271
8481
|
);
|
|
8272
8482
|
return parseTopicProjection(raw, shownContext);
|
|
8273
8483
|
} catch (error) {
|
|
8274
|
-
if (error instanceof TopicProjectionError) throw error;
|
|
8484
|
+
if (error instanceof TopicProjectionError) throw carryTopicOverviewRepairs(error, repairs.requirements);
|
|
8275
8485
|
if (error instanceof Error && /^StrataGate structured model task timed out after \d+ms$/.test(error.message)) {
|
|
8276
|
-
throw new TopicProjectionError("timeout", "structured model task timed out");
|
|
8486
|
+
throw carryTopicOverviewRepairs(new TopicProjectionError("timeout", "structured model task timed out"), repairs.requirements);
|
|
8277
8487
|
}
|
|
8278
|
-
throw new TopicProjectionError("provider-failed", "provider or route error");
|
|
8488
|
+
throw carryTopicOverviewRepairs(new TopicProjectionError("provider-failed", "provider or route error"), repairs.requirements);
|
|
8279
8489
|
}
|
|
8280
8490
|
};
|
|
8281
8491
|
externalMemoryDecider = async (context) => {
|
|
@@ -8327,7 +8537,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8327
8537
|
}
|
|
8328
8538
|
return proposed;
|
|
8329
8539
|
}
|
|
8330
|
-
async callStructured(kind, system, payload, validate, topicMetadata) {
|
|
8540
|
+
async callStructured(kind, system, payload, validate, topicMetadata, topicRepair) {
|
|
8331
8541
|
const execution = this.sessions.getStore();
|
|
8332
8542
|
if (!execution) throw new Error("StrataGate model callback ran without an execution context");
|
|
8333
8543
|
const baseRoute = this.resolveRoute(execution.session);
|
|
@@ -8345,11 +8555,13 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8345
8555
|
let attemptsUsed = 0;
|
|
8346
8556
|
let noAdapterRetried = false;
|
|
8347
8557
|
let graphRetryFeedback = "";
|
|
8558
|
+
let topicRetryFeedback = "";
|
|
8348
8559
|
let offFallback = false;
|
|
8349
8560
|
for (let attempt = 1; attempt <= JSON_RESPONSE_ATTEMPTS; attempt += 1) {
|
|
8350
8561
|
attemptsUsed = attempt;
|
|
8562
|
+
const requestPayload = topicRepair?.guard.requirements.length ? { ...payload, overviewRepairRequirements: topicRepair.guard.requirements } : payload;
|
|
8351
8563
|
const message = createUserMessage({
|
|
8352
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8564
|
+
content: [{ type: "text", text: JSON.stringify(requestPayload) }],
|
|
8353
8565
|
source: dshMessageSource()
|
|
8354
8566
|
});
|
|
8355
8567
|
const assembler = new BlockAssembler();
|
|
@@ -8359,7 +8571,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8359
8571
|
messages: [message],
|
|
8360
8572
|
system: attempt === 1 ? system : `${system}
|
|
8361
8573
|
|
|
8362
|
-
${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
8574
|
+
${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}${topicRetryFeedback}`,
|
|
8363
8575
|
tools: [{
|
|
8364
8576
|
name: STRUCTURED_TOOLS[kind].name,
|
|
8365
8577
|
description: STRUCTURED_TOOLS[kind].description,
|
|
@@ -8383,7 +8595,7 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8383
8595
|
}
|
|
8384
8596
|
if (window !== void 0) request.maxTokens = Math.min(TOPIC_OUTPUT_TOKEN_BUDGET, Math.floor(window - inputTokens - 1024));
|
|
8385
8597
|
request.messages = [{ ...message, content: [{ type: "text", text: JSON.stringify({
|
|
8386
|
-
...
|
|
8598
|
+
...requestPayload,
|
|
8387
8599
|
outputTokenBudget: request.maxTokens
|
|
8388
8600
|
}) }] }];
|
|
8389
8601
|
inputTokens = estimateInput();
|
|
@@ -8474,10 +8686,20 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8474
8686
|
);
|
|
8475
8687
|
}
|
|
8476
8688
|
}
|
|
8689
|
+
topicRepair?.guard.observe(parsed, topicRepair.context);
|
|
8477
8690
|
if (kind === "graphProjector") parsed = normalizeGraphNameProvenance(parsed);
|
|
8478
8691
|
const violations = validateArgs(STRUCTURED_TOOLS[kind].parameters, parsed);
|
|
8479
8692
|
if (violations.length > 0) {
|
|
8480
|
-
if (isTopic)
|
|
8693
|
+
if (isTopic) {
|
|
8694
|
+
const topics = object(parsed).topics;
|
|
8695
|
+
if (Array.isArray(topics)) topics.forEach((topic, topicIndex) => {
|
|
8696
|
+
const overview2 = object(topic).overview;
|
|
8697
|
+
if (Array.isArray(overview2)) overview2.forEach((part, overviewIndex) => {
|
|
8698
|
+
validateTopicOverviewText(object(part).text, `topics[${topicIndex}].overview[${overviewIndex}].text`, "schema-invalid");
|
|
8699
|
+
});
|
|
8700
|
+
});
|
|
8701
|
+
throw topicError("schema-invalid", "structured topic schema mismatch");
|
|
8702
|
+
}
|
|
8481
8703
|
if (kind === "graphProjector") {
|
|
8482
8704
|
const paths = violations.flatMap((violation) => violation.match(/nodes\[\d+\]\.metadataProvenance\.name(?:\[\d+\])?/g) ?? []);
|
|
8483
8705
|
graphRetryFeedback = paths.length > 0 ? `
|
|
@@ -8500,7 +8722,7 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8500
8722
|
try {
|
|
8501
8723
|
validate?.(parsed);
|
|
8502
8724
|
} catch (error) {
|
|
8503
|
-
if (isTopic) throw
|
|
8725
|
+
if (isTopic) throw error instanceof TopicProjectionError ? new TopicProjectionError(error.diagnostics.category, error.diagnostics.reason, { ...topicDiagnostics, ...error.diagnostics }) : topicError("validation-failed", "topic semantic validation failed");
|
|
8504
8726
|
throw new ModelJsonResponseError(
|
|
8505
8727
|
`StrataGate ${expectedTool} arguments were invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
8506
8728
|
{ cause: error, response: responseForError }
|
|
@@ -8518,10 +8740,17 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8518
8740
|
return parsed;
|
|
8519
8741
|
} catch (error) {
|
|
8520
8742
|
if (!(error instanceof ModelJsonResponseError) && !(isTopic && error instanceof TopicProjectionError)) throw error;
|
|
8743
|
+
let retryError = error;
|
|
8744
|
+
if (isTopic && error instanceof TopicProjectionError) {
|
|
8745
|
+
const safe = new TopicProjectionError(error.diagnostics.category, error.diagnostics.reason, { ...topicDiagnostics, ...error.diagnostics });
|
|
8746
|
+
topicRetryFeedback = `
|
|
8747
|
+
Correct the previous Topic output using these validation errors: ${JSON.stringify(safe.diagnostics)}. Return the complete result; preserve evidence, references and Event assignments. Do not truncate, delete or fabricate content to bypass validation.`;
|
|
8748
|
+
retryError = safe;
|
|
8749
|
+
}
|
|
8521
8750
|
lastError = finish.kind === "max-tokens" ? new ModelJsonResponseError(
|
|
8522
8751
|
`StrataGate ${STRUCTURED_TOOLS[kind].name} call was truncated before valid arguments`,
|
|
8523
|
-
{ cause:
|
|
8524
|
-
) :
|
|
8752
|
+
{ cause: retryError, response: responseForError }
|
|
8753
|
+
) : retryError;
|
|
8525
8754
|
if ((kind === "graphProjector" || kind === "topicProjector") && finish.kind === "max-tokens") break;
|
|
8526
8755
|
if (attempt < JSON_RESPONSE_ATTEMPTS) {
|
|
8527
8756
|
this.ctx.logger.warn(`stratagate-memory model returned an invalid structured tool call; retrying (${attempt}/${JSON_RESPONSE_ATTEMPTS})`);
|
|
@@ -8536,18 +8765,18 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8536
8765
|
);
|
|
8537
8766
|
}
|
|
8538
8767
|
async shouldUseOff(route) {
|
|
8539
|
-
const
|
|
8540
|
-
const cached = this.offCapabilities.get(
|
|
8768
|
+
const key2 = `${route.provider}\0${route.model}`;
|
|
8769
|
+
const cached = this.offCapabilities.get(key2);
|
|
8541
8770
|
if (cached) {
|
|
8542
8771
|
if (cached === "unsupported" && this.currentStructuredReasoningEffort() === "force-off") {
|
|
8543
|
-
this.warnOffFallbackOnce(
|
|
8772
|
+
this.warnOffFallbackOnce(key2, `${route.provider}/${route.model} does not support reasoningEffort=off; using the model default`);
|
|
8544
8773
|
}
|
|
8545
8774
|
return cached === "supported";
|
|
8546
8775
|
}
|
|
8547
8776
|
if (typeof this.ctx.llm.resolveModelInfo !== "function") {
|
|
8548
|
-
this.offCapabilities.set(
|
|
8777
|
+
this.offCapabilities.set(key2, "unsupported");
|
|
8549
8778
|
if (this.currentStructuredReasoningEffort() === "force-off") {
|
|
8550
|
-
this.warnOffFallbackOnce(
|
|
8779
|
+
this.warnOffFallbackOnce(key2, `${route.provider}/${route.model} capabilities are unavailable; using the model default`);
|
|
8551
8780
|
}
|
|
8552
8781
|
return false;
|
|
8553
8782
|
}
|
|
@@ -8565,18 +8794,18 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8565
8794
|
timer.unref?.();
|
|
8566
8795
|
})
|
|
8567
8796
|
]);
|
|
8568
|
-
if (Number.isFinite(info.context?.contextWindow) && info.context.contextWindow > 0) this.contextWindows.set(
|
|
8797
|
+
if (Number.isFinite(info.context?.contextWindow) && info.context.contextWindow > 0) this.contextWindows.set(key2, info.context.contextWindow);
|
|
8569
8798
|
if (!info.reasoning) return this.currentStructuredReasoningEffort() === "force-off";
|
|
8570
8799
|
const supported = info.reasoning.efforts.some(({ id }) => String(id) === "off");
|
|
8571
|
-
this.offCapabilities.set(
|
|
8800
|
+
this.offCapabilities.set(key2, supported ? "supported" : "unsupported");
|
|
8572
8801
|
if (!supported && this.currentStructuredReasoningEffort() === "force-off") {
|
|
8573
|
-
this.warnOffFallbackOnce(
|
|
8802
|
+
this.warnOffFallbackOnce(key2, `${route.provider}/${route.model} does not support reasoningEffort=off; using the model default`);
|
|
8574
8803
|
}
|
|
8575
8804
|
return supported;
|
|
8576
8805
|
} catch {
|
|
8577
|
-
this.offCapabilities.set(
|
|
8806
|
+
this.offCapabilities.set(key2, "unsupported");
|
|
8578
8807
|
if (this.currentStructuredReasoningEffort() === "force-off") {
|
|
8579
|
-
this.warnOffFallbackOnce(
|
|
8808
|
+
this.warnOffFallbackOnce(key2, `${route.provider}/${route.model} capability lookup failed; using the model default`);
|
|
8580
8809
|
}
|
|
8581
8810
|
return false;
|
|
8582
8811
|
} finally {
|
|
@@ -8752,15 +8981,15 @@ var DshMetadataStore = class {
|
|
|
8752
8981
|
setLastFeedbackPromptAt(value) {
|
|
8753
8982
|
this.setSettingValue("lastFeedbackPromptAt", value);
|
|
8754
8983
|
}
|
|
8755
|
-
setSetting(
|
|
8756
|
-
this.setSettingValue(
|
|
8984
|
+
setSetting(key2, value) {
|
|
8985
|
+
this.setSettingValue(key2, String(value));
|
|
8757
8986
|
}
|
|
8758
|
-
setSettingValue(
|
|
8987
|
+
setSettingValue(key2, value) {
|
|
8759
8988
|
this.database.prepare(`
|
|
8760
8989
|
INSERT INTO stratagate_dsh_settings (key, value, updated_at)
|
|
8761
8990
|
VALUES (?, ?, ?)
|
|
8762
8991
|
ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
8763
|
-
`).run(
|
|
8992
|
+
`).run(key2, value, (/* @__PURE__ */ new Date()).toISOString());
|
|
8764
8993
|
}
|
|
8765
8994
|
workspaceName(namespace) {
|
|
8766
8995
|
const row = this.database.prepare("SELECT display_name FROM stratagate_dsh_workspaces WHERE namespace = ?").get(namespace);
|
|
@@ -8803,6 +9032,7 @@ import { dirname, resolve } from "node:path";
|
|
|
8803
9032
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
8804
9033
|
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
8805
9034
|
import { openNativePath } from "@deepseek-ai/dsh-native-command";
|
|
9035
|
+
import { validateArgs as validateArgs2 } from "@deepseek-ai/dsh-tools";
|
|
8806
9036
|
|
|
8807
9037
|
// src/fold.ts
|
|
8808
9038
|
function renderBlocks(blocks) {
|
|
@@ -8936,10 +9166,10 @@ function memoryTopicSections(topic) {
|
|
|
8936
9166
|
});
|
|
8937
9167
|
const sections = /* @__PURE__ */ new Map();
|
|
8938
9168
|
for (const membership of memoryTopicMembershipSections(topic)) {
|
|
8939
|
-
const
|
|
8940
|
-
const section = sections.get(
|
|
9169
|
+
const key2 = memoryTopicSectionKey(membership.title);
|
|
9170
|
+
const section = sections.get(key2) ?? { key: key2, title: membership.title, paragraphs: [], sourceEventIds: [] };
|
|
8941
9171
|
section.sourceEventIds.push(...membership.sourceEventIds.filter((id) => memberPositions.has(id)));
|
|
8942
|
-
sections.set(
|
|
9172
|
+
sections.set(key2, section);
|
|
8943
9173
|
}
|
|
8944
9174
|
for (const part of topic.overview) {
|
|
8945
9175
|
const title = memoryTopicSectionTitle(part);
|
|
@@ -9008,7 +9238,7 @@ function sortMemoryTopics(topics) {
|
|
|
9008
9238
|
}
|
|
9009
9239
|
function topicNavigation(topics, events) {
|
|
9010
9240
|
const byId = new Map(events.map((event) => [event.id, event]));
|
|
9011
|
-
const
|
|
9241
|
+
const entries2 = sortMemoryTopics(topics).map((topic) => ({
|
|
9012
9242
|
id: topic.id,
|
|
9013
9243
|
title: topic.title,
|
|
9014
9244
|
description: topic.description,
|
|
@@ -9019,24 +9249,24 @@ function topicNavigation(topics, events) {
|
|
|
9019
9249
|
}));
|
|
9020
9250
|
const categories2 = CATEGORIES.map((category) => ({
|
|
9021
9251
|
...category,
|
|
9022
|
-
count:
|
|
9252
|
+
count: entries2.filter((entry) => entry.category === category.id).length
|
|
9023
9253
|
})).filter(({ count }) => count > 0);
|
|
9024
|
-
return { entries, categories: categories2 };
|
|
9254
|
+
return { entries: entries2, categories: categories2 };
|
|
9025
9255
|
}
|
|
9026
9256
|
function topicPage(topics, events, options = {}) {
|
|
9027
9257
|
const navigation = topicNavigation(topics, events);
|
|
9028
9258
|
if (options.category && !CATEGORIES.some(({ id }) => id === options.category)) throw new TypeError("Unknown topic category");
|
|
9029
|
-
let
|
|
9259
|
+
let entries2 = options.category ? navigation.entries.filter(({ category }) => category === options.category) : navigation.entries;
|
|
9030
9260
|
const query = options.query?.trim().slice(0, 500);
|
|
9031
|
-
if (query)
|
|
9261
|
+
if (query) entries2 = bm25Rank(entries2, query, (entry) => weightedSearchTokens([[entry.title, 4], [entry.description, 2]])).map(({ item }) => item);
|
|
9032
9262
|
const offset = Number.isFinite(options.offset) ? Math.max(0, Math.floor(options.offset)) : 0;
|
|
9033
9263
|
const limit = Number.isFinite(options.limit) ? Math.max(1, Math.min(20, Math.floor(options.limit))) : 12;
|
|
9034
|
-
const page =
|
|
9264
|
+
const page = entries2.slice(offset, offset + limit);
|
|
9035
9265
|
return {
|
|
9036
9266
|
navigationOnly: true,
|
|
9037
|
-
total:
|
|
9267
|
+
total: entries2.length,
|
|
9038
9268
|
offset,
|
|
9039
|
-
nextOffset: offset + page.length <
|
|
9269
|
+
nextOffset: offset + page.length < entries2.length ? offset + page.length : null,
|
|
9040
9270
|
categories: navigation.categories,
|
|
9041
9271
|
topics: page
|
|
9042
9272
|
};
|
|
@@ -9045,27 +9275,58 @@ function lineText(value, maximum) {
|
|
|
9045
9275
|
return value.replace(/\s+/g, " ").trim().slice(0, maximum);
|
|
9046
9276
|
}
|
|
9047
9277
|
function renderMemoryDirectory(topics, events) {
|
|
9048
|
-
const { entries, categories: categories2 } = topicNavigation(topics, events);
|
|
9049
|
-
if (
|
|
9050
|
-
const header = "[StrataGate \u8BB0\u5FC6\u76EE\u5F55]\n\u4EC5\u4F9B\u5BFC\u822A\uFF0C\
|
|
9051
|
-
const
|
|
9052
|
-
const
|
|
9053
|
-
|
|
9278
|
+
const { entries: entries2, categories: categories2 } = topicNavigation(topics, events);
|
|
9279
|
+
if (entries2.length === 0) return "";
|
|
9280
|
+
const header = "[StrataGate \u8BB0\u5FC6\u76EE\u5F55]\n\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u4E0D\u662F\u4E8B\u5B9E\u8BC1\u636E\u3002\u9700\u8981\u4E3B\u9898\u8109\u7EDC\u65F6\u7528 memory_expand_topic\uFF1B\u9700\u8981\u4E8B\u5B9E\u8BC1\u636E\u53EF\u76F4\u63A5 memory_search_events(topic_id)\uFF0C\u4E5F\u53EF\u76F4\u63A5\u641C\u7D22\u4E8B\u4EF6/\u56FE\u8C31\u3002";
|
|
9281
|
+
const byId = new Map(topics.map((topic) => [topic.id, topic]));
|
|
9282
|
+
const chapters = entries2.map((entry) => ({
|
|
9283
|
+
...entry,
|
|
9284
|
+
line: `- ${entry.id}\uFF1A${lineText(entry.title, 64)}${entry.description ? `\uFF1B${lineText(entry.description, 48)}` : ""}`,
|
|
9285
|
+
// Reuse the book's stable section order, including summary-free and legacy sections.
|
|
9286
|
+
sections: memoryTopicSections(byId.get(entry.id)).map(({ title }) => ` - ${lineText(title, 64)}`)
|
|
9287
|
+
}));
|
|
9288
|
+
const render = (prefix, selected2) => `${prefix}
|
|
9289
|
+
${selected2.flatMap(({ line, sections }) => [line, ...sections]).join("\n")}`.trim();
|
|
9290
|
+
const complete = render(header, chapters);
|
|
9054
9291
|
if (estimateTokens(complete) <= MEMORY_DIRECTORY_TOKEN_BUDGET) return complete;
|
|
9055
9292
|
const branches = categories2.map(({ id, label, count }) => `${id}\uFF08${label} ${count}\uFF09`).join("\uFF1B");
|
|
9056
9293
|
const navigation = `${header}
|
|
9057
|
-
\u5171 ${
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
const
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9294
|
+
\u5171 ${entries2.length} \u9879\uFF1B\u5206\u7C7B\uFF1A${branches}\u3002\u76EE\u5F55\u5DF2\u88C1\u526A\uFF1B\u5B8C\u6574\u76EE\u5F55\u7528 memory_list_topics(category, offset) \u5206\u9875\uFF1Bquery \u53EF\u67E5\u4E3B\u9898\uFF0C\u5C0F\u8282\u7528 memory_expand_topic(id)\u3002`;
|
|
9295
|
+
let selected = chapters.map((chapter) => ({ ...chapter, sections: [] }));
|
|
9296
|
+
if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9297
|
+
selected = [];
|
|
9298
|
+
const admit = (chapter) => {
|
|
9299
|
+
for (const line of [chapter.line, `- ${chapter.id}\uFF1A${lineText(chapter.title, 64)}`]) {
|
|
9300
|
+
const candidate = { ...chapter, line, sections: [] };
|
|
9301
|
+
if (estimateTokens(render(navigation, [...selected, candidate])) <= MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9302
|
+
selected.push(candidate);
|
|
9303
|
+
return;
|
|
9304
|
+
}
|
|
9305
|
+
}
|
|
9306
|
+
};
|
|
9307
|
+
for (const category of categories2) admit(chapters.find((chapter) => chapter.category === category.id));
|
|
9308
|
+
for (const chapter of chapters) if (!selected.some(({ id }) => id === chapter.id)) admit(chapter);
|
|
9309
|
+
}
|
|
9310
|
+
const order = new Map(entries2.map(({ id }, index) => [id, index]));
|
|
9066
9311
|
selected.sort((a, b) => order.get(a.id) - order.get(b.id));
|
|
9067
|
-
|
|
9068
|
-
|
|
9312
|
+
const sectionLines = new Map(chapters.map(({ id, sections }) => [id, sections]));
|
|
9313
|
+
const exhausted = /* @__PURE__ */ new Set();
|
|
9314
|
+
for (let index = 0; exhausted.size < selected.length; index++) {
|
|
9315
|
+
for (const chapter of selected) {
|
|
9316
|
+
if (exhausted.has(chapter.id)) continue;
|
|
9317
|
+
const line = sectionLines.get(chapter.id)[index];
|
|
9318
|
+
if (line === void 0) {
|
|
9319
|
+
exhausted.add(chapter.id);
|
|
9320
|
+
continue;
|
|
9321
|
+
}
|
|
9322
|
+
chapter.sections.push(line);
|
|
9323
|
+
if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9324
|
+
chapter.sections.pop();
|
|
9325
|
+
exhausted.add(chapter.id);
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
}
|
|
9329
|
+
return render(navigation, selected);
|
|
9069
9330
|
}
|
|
9070
9331
|
function boundedTopic(topic, envelope = {}) {
|
|
9071
9332
|
const { sections: _sections, ...summaryTopic } = topic;
|
|
@@ -9128,13 +9389,13 @@ var FEEDBACK_MARKDOWN_HEADINGS = {
|
|
|
9128
9389
|
};
|
|
9129
9390
|
function patchFeedbackMarkdown(markdown, input) {
|
|
9130
9391
|
let result = markdown.trim();
|
|
9131
|
-
for (const [
|
|
9132
|
-
if (!Object.prototype.hasOwnProperty.call(input,
|
|
9133
|
-
const raw = input[
|
|
9392
|
+
for (const [key2, heading] of Object.entries(FEEDBACK_MARKDOWN_HEADINGS)) {
|
|
9393
|
+
if (!Object.prototype.hasOwnProperty.call(input, key2)) continue;
|
|
9394
|
+
const raw = input[key2];
|
|
9134
9395
|
const text3 = Array.isArray(raw) ? raw.map((item, index) => {
|
|
9135
9396
|
const value = feedbackText(item, 2e3);
|
|
9136
9397
|
return value ? `${index + 1}. ${value}` : "";
|
|
9137
|
-
}).filter(Boolean).join("\n") : feedbackText(raw,
|
|
9398
|
+
}).filter(Boolean).join("\n") : feedbackText(raw, key2 === "description" ? 2e4 : key2 === "expected" || key2 === "actual" ? 1e4 : 2e4);
|
|
9138
9399
|
const marker = `## ${heading}`;
|
|
9139
9400
|
const lines = result.split("\n");
|
|
9140
9401
|
let fence = null;
|
|
@@ -9176,9 +9437,9 @@ ${text3}`;
|
|
|
9176
9437
|
return result.trim();
|
|
9177
9438
|
}
|
|
9178
9439
|
function normalizeFeedbackDraft(input, previous) {
|
|
9179
|
-
const has = (
|
|
9440
|
+
const has = (key2) => Object.prototype.hasOwnProperty.call(input, key2);
|
|
9180
9441
|
const replacesStructuredBody = has("bodyMarkdown");
|
|
9181
|
-
const updatesStructuredBody = ["description", "reproduction", "expected", "actual", "errorContext"].some((
|
|
9442
|
+
const updatesStructuredBody = ["description", "reproduction", "expected", "actual", "errorContext"].some((key2) => has(key2));
|
|
9182
9443
|
const reproduction = replacesStructuredBody ? [] : has("reproduction") ? (Array.isArray(input.reproduction) ? input.reproduction : []).map((value) => feedbackText(value, 2e3)).filter(Boolean).slice(0, 20) : previous?.reproduction ?? [];
|
|
9183
9444
|
const bodyMarkdown = has("bodyMarkdown") ? feedbackText(input.bodyMarkdown, 5e4) : updatesStructuredBody && previous?.bodyMarkdown ? patchFeedbackMarkdown(previous.bodyMarkdown, input) : previous?.bodyMarkdown;
|
|
9184
9445
|
return {
|
|
@@ -9361,7 +9622,7 @@ var StrataGateRuntime = class {
|
|
|
9361
9622
|
...this.derivationRuns.keys(),
|
|
9362
9623
|
...this.migrationTimers.keys(),
|
|
9363
9624
|
...this.migrationRuns.keys()
|
|
9364
|
-
].some((
|
|
9625
|
+
].some((key2) => key2.startsWith(`${namespace}\0`) || key2 === namespace);
|
|
9365
9626
|
if (hasActiveSessionWork) return;
|
|
9366
9627
|
if (!this.models.isReady()) return;
|
|
9367
9628
|
const resumed = await this.models.runDetached(
|
|
@@ -9460,24 +9721,24 @@ var StrataGateRuntime = class {
|
|
|
9460
9721
|
if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
|
|
9461
9722
|
const turn = this.folder.accept(session, event);
|
|
9462
9723
|
if (!turn) return;
|
|
9463
|
-
const
|
|
9464
|
-
this.sessionsById.set(
|
|
9465
|
-
const queued = this.pendingTurns.get(
|
|
9724
|
+
const key2 = String(session.id);
|
|
9725
|
+
this.sessionsById.set(key2, session);
|
|
9726
|
+
const queued = this.pendingTurns.get(key2) ?? [];
|
|
9466
9727
|
queued.push(turn);
|
|
9467
|
-
this.pendingTurns.set(
|
|
9728
|
+
this.pendingTurns.set(key2, queued);
|
|
9468
9729
|
this.scheduleDrain(session);
|
|
9469
9730
|
}
|
|
9470
9731
|
/** Serialize drains per session while allowing unrelated sessions to settle independently. */
|
|
9471
9732
|
enqueueDrain(session) {
|
|
9472
|
-
const
|
|
9473
|
-
const prior = this.drainTails.get(
|
|
9733
|
+
const key2 = String(session.id);
|
|
9734
|
+
const prior = this.drainTails.get(key2) ?? Promise.resolve();
|
|
9474
9735
|
const next = prior.catch(() => {
|
|
9475
9736
|
}).then(() => this.drain(session));
|
|
9476
|
-
this.drainTails.set(
|
|
9737
|
+
this.drainTails.set(key2, next);
|
|
9477
9738
|
void next.then(() => {
|
|
9478
|
-
if ((this.pendingTurns.get(
|
|
9739
|
+
if ((this.pendingTurns.get(key2)?.length ?? 0) === 0) this.drainErrors.delete(key2);
|
|
9479
9740
|
}, (error) => {
|
|
9480
|
-
this.drainErrors.set(
|
|
9741
|
+
this.drainErrors.set(key2, error);
|
|
9481
9742
|
this.notePluginError(session, error);
|
|
9482
9743
|
this.onIngestError(error);
|
|
9483
9744
|
});
|
|
@@ -9485,46 +9746,46 @@ var StrataGateRuntime = class {
|
|
|
9485
9746
|
}
|
|
9486
9747
|
/** Batch short bursts and retry failed drains with bounded exponential backoff. */
|
|
9487
9748
|
scheduleDrain(session, overrideDelayMs) {
|
|
9488
|
-
const
|
|
9749
|
+
const key2 = String(session.id);
|
|
9489
9750
|
if (this.closed) return;
|
|
9490
|
-
const queued = this.pendingTurns.get(
|
|
9751
|
+
const queued = this.pendingTurns.get(key2)?.length ?? 0;
|
|
9491
9752
|
if (queued === 0) return;
|
|
9492
|
-
const delay = overrideDelayMs ?? (queued >= DRAIN_THRESHOLD ? DRAIN_EAGER_MS : Math.min(this.drainBackoffMs.get(
|
|
9493
|
-
const pending = this.drainTimers.get(
|
|
9753
|
+
const delay = overrideDelayMs ?? (queued >= DRAIN_THRESHOLD ? DRAIN_EAGER_MS : Math.min(this.drainBackoffMs.get(key2) ?? DRAIN_BASE_BACKOFF_MS, DRAIN_MAX_BACKOFF_MS));
|
|
9754
|
+
const pending = this.drainTimers.get(key2);
|
|
9494
9755
|
if (pending) {
|
|
9495
9756
|
if (delay >= DRAIN_BASE_BACKOFF_MS) return;
|
|
9496
9757
|
clearTimeout(pending);
|
|
9497
|
-
this.drainTimers.delete(
|
|
9758
|
+
this.drainTimers.delete(key2);
|
|
9498
9759
|
}
|
|
9499
9760
|
const timer = setTimeout(() => {
|
|
9500
|
-
this.drainTimers.delete(
|
|
9761
|
+
this.drainTimers.delete(key2);
|
|
9501
9762
|
if (this.closed) return;
|
|
9502
9763
|
void this.enqueueDrain(session).then(() => {
|
|
9503
|
-
this.drainBackoffMs.delete(
|
|
9504
|
-
if ((this.pendingTurns.get(
|
|
9764
|
+
this.drainBackoffMs.delete(key2);
|
|
9765
|
+
if ((this.pendingTurns.get(key2)?.length ?? 0) > 0) {
|
|
9505
9766
|
this.scheduleDrain(session, DRAIN_BASE_BACKOFF_MS);
|
|
9506
9767
|
}
|
|
9507
9768
|
}, () => {
|
|
9508
|
-
const backoff = this.drainBackoffMs.get(
|
|
9509
|
-
this.drainBackoffMs.set(
|
|
9510
|
-
const pendingRetry = this.drainTimers.get(
|
|
9769
|
+
const backoff = this.drainBackoffMs.get(key2) ?? DRAIN_BASE_BACKOFF_MS;
|
|
9770
|
+
this.drainBackoffMs.set(key2, Math.min(backoff * 2, DRAIN_MAX_BACKOFF_MS));
|
|
9771
|
+
const pendingRetry = this.drainTimers.get(key2);
|
|
9511
9772
|
if (pendingRetry) clearTimeout(pendingRetry);
|
|
9512
|
-
this.drainTimers.delete(
|
|
9773
|
+
this.drainTimers.delete(key2);
|
|
9513
9774
|
this.scheduleDrain(session);
|
|
9514
9775
|
});
|
|
9515
9776
|
}, delay);
|
|
9516
9777
|
timer.unref?.();
|
|
9517
|
-
this.drainTimers.set(
|
|
9778
|
+
this.drainTimers.set(key2, timer);
|
|
9518
9779
|
}
|
|
9519
9780
|
/**
|
|
9520
9781
|
* Persist one detached batch. On failure, the failed and unvisited turns are
|
|
9521
9782
|
* restored ahead of turns that arrived meanwhile, preserving original order.
|
|
9522
9783
|
*/
|
|
9523
9784
|
async drain(session) {
|
|
9524
|
-
const
|
|
9525
|
-
const turns = this.pendingTurns.get(
|
|
9785
|
+
const key2 = String(session.id);
|
|
9786
|
+
const turns = this.pendingTurns.get(key2);
|
|
9526
9787
|
if (!turns || turns.length === 0) return;
|
|
9527
|
-
this.pendingTurns.delete(
|
|
9788
|
+
this.pendingTurns.delete(key2);
|
|
9528
9789
|
let completed = 0;
|
|
9529
9790
|
let memory;
|
|
9530
9791
|
try {
|
|
@@ -9537,7 +9798,7 @@ var StrataGateRuntime = class {
|
|
|
9537
9798
|
} catch (error) {
|
|
9538
9799
|
const remaining = turns.slice(completed);
|
|
9539
9800
|
if (remaining.length > 0) {
|
|
9540
|
-
this.pendingTurns.set(
|
|
9801
|
+
this.pendingTurns.set(key2, [...remaining, ...this.pendingTurns.get(key2) ?? []]);
|
|
9541
9802
|
}
|
|
9542
9803
|
throw error;
|
|
9543
9804
|
} finally {
|
|
@@ -9611,9 +9872,21 @@ var StrataGateRuntime = class {
|
|
|
9611
9872
|
}
|
|
9612
9873
|
async buildMemoryDirectory(session) {
|
|
9613
9874
|
await this.flush();
|
|
9614
|
-
const
|
|
9615
|
-
|
|
9616
|
-
|
|
9875
|
+
const namespace = this.namespaceFor(session);
|
|
9876
|
+
const active = this.spaces.get(namespace);
|
|
9877
|
+
if (active) {
|
|
9878
|
+
const memory = await active;
|
|
9879
|
+
await this.refreshExternalImportMemory(namespace, memory);
|
|
9880
|
+
return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
|
|
9881
|
+
}
|
|
9882
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return "";
|
|
9883
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
9884
|
+
try {
|
|
9885
|
+
const memory = await StrataGate.openWithStorage({ storage, namespace });
|
|
9886
|
+
return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
|
|
9887
|
+
} finally {
|
|
9888
|
+
await storage.close();
|
|
9889
|
+
}
|
|
9617
9890
|
}
|
|
9618
9891
|
visibleTopics(memory) {
|
|
9619
9892
|
if (this.agentMemoryRetrievalWeight > 0) return memory.listMemoryTopics();
|
|
@@ -9760,7 +10033,7 @@ var StrataGateRuntime = class {
|
|
|
9760
10033
|
};
|
|
9761
10034
|
}
|
|
9762
10035
|
async recordUse(session, receiptId, evidenceRefs, batchId) {
|
|
9763
|
-
const
|
|
10036
|
+
const key2 = String(session.id);
|
|
9764
10037
|
const batch = this.requireBatch(session, batchId, "memory_record_use");
|
|
9765
10038
|
if (batch.status !== "unresolved") {
|
|
9766
10039
|
throw new Error(this.batchError(
|
|
@@ -9839,9 +10112,9 @@ var StrataGateRuntime = class {
|
|
|
9839
10112
|
eventIds: [...eventIds],
|
|
9840
10113
|
elementIds: [...elementIds]
|
|
9841
10114
|
}, {
|
|
9842
|
-
receiptId: `dsh:${
|
|
10115
|
+
receiptId: `dsh:${key2}:tool:${receiptId}`,
|
|
9843
10116
|
audit: {
|
|
9844
|
-
sessionId:
|
|
10117
|
+
sessionId: key2,
|
|
9845
10118
|
...turn === void 0 ? {} : { turn },
|
|
9846
10119
|
batchId: batch.id,
|
|
9847
10120
|
evidenceRefs: selectedRefs,
|
|
@@ -9890,6 +10163,7 @@ var StrataGateRuntime = class {
|
|
|
9890
10163
|
async buildAutoContext(session) {
|
|
9891
10164
|
await this.flush();
|
|
9892
10165
|
const memory = await this.space(session);
|
|
10166
|
+
await this.refreshExternalImportMemory(this.namespaceFor(session), memory);
|
|
9893
10167
|
const threadId = String(session.id);
|
|
9894
10168
|
const blockContexts = memory.getBlockContext(threadId);
|
|
9895
10169
|
if (this.syncDecayedBlockSurface(session, memory, blockContexts)) {
|
|
@@ -10004,9 +10278,9 @@ var StrataGateRuntime = class {
|
|
|
10004
10278
|
const contexts = new Map(memory.getBlockContext(threadId).map((context) => [context.id, context]));
|
|
10005
10279
|
const existing = currentBlockSurfaceMessages(session);
|
|
10006
10280
|
let changed = false;
|
|
10007
|
-
for (const [
|
|
10281
|
+
for (const [key2, { blockId }] of pending) {
|
|
10008
10282
|
if (existing.has(blockId)) {
|
|
10009
|
-
this.pendingSurfaceSync.delete(
|
|
10283
|
+
this.pendingSurfaceSync.delete(key2);
|
|
10010
10284
|
continue;
|
|
10011
10285
|
}
|
|
10012
10286
|
const block = memory.listBlocks().find((candidate) => candidate.id === blockId && candidate.threadId === threadId);
|
|
@@ -10014,7 +10288,7 @@ var StrataGateRuntime = class {
|
|
|
10014
10288
|
if (!block || block.processingStatus !== "ready" || !context) continue;
|
|
10015
10289
|
try {
|
|
10016
10290
|
changed = this.replaceSealedSurface(session, block, context, dshTurnAtBlockEnd(session, block)) || changed;
|
|
10017
|
-
this.pendingSurfaceSync.delete(
|
|
10291
|
+
this.pendingSurfaceSync.delete(key2);
|
|
10018
10292
|
} catch (error) {
|
|
10019
10293
|
this.onIngestError(error);
|
|
10020
10294
|
}
|
|
@@ -10024,11 +10298,11 @@ var StrataGateRuntime = class {
|
|
|
10024
10298
|
// Keep the ingestion error for callers that explicitly require a flushed run.
|
|
10025
10299
|
async settleIngestion() {
|
|
10026
10300
|
do {
|
|
10027
|
-
for (const [
|
|
10028
|
-
if ((this.pendingTurns.get(
|
|
10029
|
-
const timer = this.drainTimers.get(
|
|
10301
|
+
for (const [key2, session] of this.sessionsById) {
|
|
10302
|
+
if ((this.pendingTurns.get(key2)?.length ?? 0) === 0) continue;
|
|
10303
|
+
const timer = this.drainTimers.get(key2);
|
|
10030
10304
|
if (timer) clearTimeout(timer);
|
|
10031
|
-
this.drainTimers.delete(
|
|
10305
|
+
this.drainTimers.delete(key2);
|
|
10032
10306
|
this.enqueueDrain(session);
|
|
10033
10307
|
}
|
|
10034
10308
|
await Promise.allSettled(this.drainTails.values());
|
|
@@ -10091,16 +10365,16 @@ var StrataGateRuntime = class {
|
|
|
10091
10365
|
};
|
|
10092
10366
|
}
|
|
10093
10367
|
adminFeedbackDraft(namespace) {
|
|
10094
|
-
const
|
|
10095
|
-
if (!
|
|
10096
|
-
return { namespace:
|
|
10368
|
+
const key2 = namespace.trim();
|
|
10369
|
+
if (!key2) throw new TypeError("StrataGate feedback namespace must not be empty");
|
|
10370
|
+
return { namespace: key2, draft: this.loadFeedbackDraft(key2) };
|
|
10097
10371
|
}
|
|
10098
10372
|
adminSaveFeedbackDraft(namespace, input) {
|
|
10099
|
-
const
|
|
10100
|
-
if (!
|
|
10101
|
-
const draft = normalizeFeedbackDraft(input, this.loadFeedbackDraft(
|
|
10102
|
-
this.saveFeedbackDraft(
|
|
10103
|
-
return { namespace:
|
|
10373
|
+
const key2 = namespace.trim();
|
|
10374
|
+
if (!key2) throw new TypeError("StrataGate feedback namespace must not be empty");
|
|
10375
|
+
const draft = normalizeFeedbackDraft(input, this.loadFeedbackDraft(key2));
|
|
10376
|
+
this.saveFeedbackDraft(key2, draft);
|
|
10377
|
+
return { namespace: key2, draft };
|
|
10104
10378
|
}
|
|
10105
10379
|
/**
|
|
10106
10380
|
* Record one agent-authored fact through the core long-term Event pipeline.
|
|
@@ -10268,7 +10542,7 @@ var StrataGateRuntime = class {
|
|
|
10268
10542
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
|
|
10269
10543
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
10270
10544
|
try {
|
|
10271
|
-
const
|
|
10545
|
+
const entries2 = [];
|
|
10272
10546
|
for (const { namespace, revision } of storage.listNamespaceRevisions()) {
|
|
10273
10547
|
const opening = this.spaces.get(namespace);
|
|
10274
10548
|
if (opening) {
|
|
@@ -10278,25 +10552,25 @@ var StrataGateRuntime = class {
|
|
|
10278
10552
|
const cached2 = this.adminSnapshotCache.get(namespace);
|
|
10279
10553
|
const entry2 = cached2?.revision === currentRevision ? cached2 : { namespace, revision: currentRevision, snapshot: memory.exportSnapshot() };
|
|
10280
10554
|
this.adminSnapshotCache.set(namespace, entry2);
|
|
10281
|
-
|
|
10555
|
+
entries2.push(entry2);
|
|
10282
10556
|
continue;
|
|
10283
10557
|
}
|
|
10284
10558
|
const cached = this.adminSnapshotCache.get(namespace);
|
|
10285
10559
|
if (cached?.revision === revision) {
|
|
10286
|
-
|
|
10560
|
+
entries2.push(cached);
|
|
10287
10561
|
continue;
|
|
10288
10562
|
}
|
|
10289
10563
|
const loaded = await storage.load(namespace);
|
|
10290
10564
|
if (!loaded) continue;
|
|
10291
10565
|
const entry = { namespace, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
10292
10566
|
this.adminSnapshotCache.set(namespace, entry);
|
|
10293
|
-
|
|
10567
|
+
entries2.push(entry);
|
|
10294
10568
|
}
|
|
10295
|
-
const activeNamespaces = new Set(
|
|
10569
|
+
const activeNamespaces = new Set(entries2.map(({ namespace }) => namespace));
|
|
10296
10570
|
for (const namespace of this.adminSnapshotCache.keys()) {
|
|
10297
10571
|
if (!activeNamespaces.has(namespace)) this.adminSnapshotCache.delete(namespace);
|
|
10298
10572
|
}
|
|
10299
|
-
return
|
|
10573
|
+
return entries2;
|
|
10300
10574
|
} finally {
|
|
10301
10575
|
await storage.close();
|
|
10302
10576
|
}
|
|
@@ -10327,30 +10601,30 @@ var StrataGateRuntime = class {
|
|
|
10327
10601
|
}
|
|
10328
10602
|
}
|
|
10329
10603
|
async adminSnapshot(namespace) {
|
|
10330
|
-
const
|
|
10331
|
-
if (!
|
|
10604
|
+
const key2 = namespace.trim();
|
|
10605
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10332
10606
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
10333
|
-
const opening = this.spaces.get(
|
|
10607
|
+
const opening = this.spaces.get(key2);
|
|
10334
10608
|
if (opening) {
|
|
10335
10609
|
const memory = await opening;
|
|
10336
|
-
await this.refreshExternalImportMemory(
|
|
10610
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10337
10611
|
const revision = memory.storageRevision;
|
|
10338
|
-
const cached = this.adminSnapshotCache.get(
|
|
10612
|
+
const cached = this.adminSnapshotCache.get(key2);
|
|
10339
10613
|
if (cached?.revision === revision) return cached.snapshot;
|
|
10340
|
-
const entry = { namespace:
|
|
10341
|
-
this.adminSnapshotCache.set(
|
|
10614
|
+
const entry = { namespace: key2, revision, snapshot: memory.exportSnapshot() };
|
|
10615
|
+
this.adminSnapshotCache.set(key2, entry);
|
|
10342
10616
|
return entry.snapshot;
|
|
10343
10617
|
}
|
|
10344
10618
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
10345
10619
|
try {
|
|
10346
|
-
const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 ===
|
|
10620
|
+
const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 === key2);
|
|
10347
10621
|
if (!head) return null;
|
|
10348
|
-
const cached = this.adminSnapshotCache.get(
|
|
10622
|
+
const cached = this.adminSnapshotCache.get(key2);
|
|
10349
10623
|
if (cached?.revision === head.revision) return cached.snapshot;
|
|
10350
|
-
const loaded = await storage.load(
|
|
10624
|
+
const loaded = await storage.load(key2);
|
|
10351
10625
|
if (!loaded) return null;
|
|
10352
|
-
const entry = { namespace:
|
|
10353
|
-
this.adminSnapshotCache.set(
|
|
10626
|
+
const entry = { namespace: key2, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
10627
|
+
this.adminSnapshotCache.set(key2, entry);
|
|
10354
10628
|
return entry.snapshot;
|
|
10355
10629
|
} finally {
|
|
10356
10630
|
await storage.close();
|
|
@@ -10479,44 +10753,44 @@ var StrataGateRuntime = class {
|
|
|
10479
10753
|
}
|
|
10480
10754
|
/** Create a durable analysis job and return before model-backed work begins. */
|
|
10481
10755
|
async adminPreviewExternalMemory(namespace, text3) {
|
|
10482
|
-
const
|
|
10483
|
-
if (!
|
|
10756
|
+
const key2 = namespace.trim();
|
|
10757
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10484
10758
|
if (!text3.trim()) throw new TypeError("External memory text must not be empty");
|
|
10485
10759
|
await this.flush();
|
|
10486
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10760
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10487
10761
|
try {
|
|
10488
|
-
await this.refreshExternalImportMemory(
|
|
10762
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10489
10763
|
const job = await memory.createExternalMemoryImportJob(text3);
|
|
10490
|
-
this.scheduleExternalMemoryImport(
|
|
10764
|
+
this.scheduleExternalMemoryImport(key2, job.id);
|
|
10491
10765
|
return this.externalImportView(job);
|
|
10492
10766
|
} finally {
|
|
10493
10767
|
if (owned) await memory.close();
|
|
10494
10768
|
}
|
|
10495
10769
|
}
|
|
10496
10770
|
async adminExternalMemoryStatus(namespace, jobId) {
|
|
10497
|
-
const
|
|
10498
|
-
if (!
|
|
10499
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10771
|
+
const key2 = namespace.trim();
|
|
10772
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10773
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10500
10774
|
try {
|
|
10501
|
-
await this.refreshExternalImportMemory(
|
|
10775
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10502
10776
|
const jobs = memory.listExternalMemoryImportJobs();
|
|
10503
10777
|
const job = jobId ? jobs.find(({ id }) => id === jobId) : [...jobs].sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
|
|
10504
10778
|
if (!job) return { job: null };
|
|
10505
|
-
if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(
|
|
10779
|
+
if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(key2, job.id);
|
|
10506
10780
|
return this.externalImportView(job);
|
|
10507
10781
|
} finally {
|
|
10508
10782
|
if (owned) await memory.close();
|
|
10509
10783
|
}
|
|
10510
10784
|
}
|
|
10511
10785
|
async adminRetryExternalMemory(namespace, jobId) {
|
|
10512
|
-
const
|
|
10786
|
+
const key2 = namespace.trim();
|
|
10513
10787
|
let lastConflict;
|
|
10514
10788
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
10515
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10789
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10516
10790
|
try {
|
|
10517
|
-
await this.refreshExternalImportMemory(
|
|
10791
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10518
10792
|
const job = await memory.retryExternalMemoryImportJob(jobId);
|
|
10519
|
-
this.scheduleExternalMemoryImport(
|
|
10793
|
+
this.scheduleExternalMemoryImport(key2, job.id);
|
|
10520
10794
|
return this.externalImportView(job);
|
|
10521
10795
|
} catch (error) {
|
|
10522
10796
|
if (!(error instanceof StorageConflictError)) throw error;
|
|
@@ -10529,10 +10803,10 @@ var StrataGateRuntime = class {
|
|
|
10529
10803
|
throw lastConflict;
|
|
10530
10804
|
}
|
|
10531
10805
|
async adminCommitExternalMemory(namespace, jobId, choices) {
|
|
10532
|
-
const
|
|
10533
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10806
|
+
const key2 = namespace.trim();
|
|
10807
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10534
10808
|
try {
|
|
10535
|
-
await this.refreshExternalImportMemory(
|
|
10809
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10536
10810
|
const job = memory.getExternalMemoryImportJob(jobId);
|
|
10537
10811
|
if (!job) throw new Error("\u627E\u4E0D\u5230\u5BFC\u5165\u4EFB\u52A1");
|
|
10538
10812
|
if (job.status !== "ready" && job.status !== "awaiting_confirmation") {
|
|
@@ -10573,10 +10847,10 @@ var StrataGateRuntime = class {
|
|
|
10573
10847
|
}
|
|
10574
10848
|
}
|
|
10575
10849
|
async adminUndoExternalMemory(namespace, sourceBlockId) {
|
|
10576
|
-
const
|
|
10577
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10850
|
+
const key2 = namespace.trim();
|
|
10851
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10578
10852
|
try {
|
|
10579
|
-
await this.refreshExternalImportMemory(
|
|
10853
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10580
10854
|
const result = await memory.undoExternalMemoryImport(sourceBlockId);
|
|
10581
10855
|
const job = memory.listExternalMemoryImportJobs().find((candidate) => candidate.sourceBlockId === sourceBlockId);
|
|
10582
10856
|
if (job) await memory.markExternalMemoryImportUndone(job.id);
|
|
@@ -10658,19 +10932,19 @@ var StrataGateRuntime = class {
|
|
|
10658
10932
|
return { opened: true, path };
|
|
10659
10933
|
}
|
|
10660
10934
|
async adminExpandBlock(namespace, id, target) {
|
|
10661
|
-
const
|
|
10662
|
-
if (!
|
|
10935
|
+
const key2 = namespace.trim();
|
|
10936
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10663
10937
|
const update = this.settingsTail.catch(() => {
|
|
10664
10938
|
}).then(async () => {
|
|
10665
10939
|
await this.flush();
|
|
10666
|
-
const active = this.spaces.get(
|
|
10940
|
+
const active = this.spaces.get(key2);
|
|
10667
10941
|
if (active) return (await active).expandBlock(id, target, "user");
|
|
10668
10942
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
|
|
10669
|
-
throw new Error(`Unknown StrataGate namespace: ${
|
|
10943
|
+
throw new Error(`Unknown StrataGate namespace: ${key2}`);
|
|
10670
10944
|
}
|
|
10671
10945
|
const memory = await StrataGate.open({
|
|
10672
10946
|
database: this.config.database,
|
|
10673
|
-
namespace:
|
|
10947
|
+
namespace: key2,
|
|
10674
10948
|
blockTurnSize: this.blockTurnSize,
|
|
10675
10949
|
blockDecayLambda: this.blockDecayLambda,
|
|
10676
10950
|
summarizer: this.models.summarizer,
|
|
@@ -10690,20 +10964,20 @@ var StrataGateRuntime = class {
|
|
|
10690
10964
|
return update;
|
|
10691
10965
|
}
|
|
10692
10966
|
async adminRetryJob(namespace, kind, id) {
|
|
10693
|
-
const
|
|
10967
|
+
const key2 = namespace.trim();
|
|
10694
10968
|
const jobId = id.trim();
|
|
10695
|
-
if (!
|
|
10969
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10696
10970
|
if (!jobId) throw new TypeError("StrataGate job id must not be empty");
|
|
10697
|
-
if (kind === "topic-projection") return this.adminRetryTopicProjection(
|
|
10698
|
-
const retryKey = `${
|
|
10971
|
+
if (kind === "topic-projection") return this.adminRetryTopicProjection(key2, jobId);
|
|
10972
|
+
const retryKey = `${key2}\0${kind}\0${jobId}`;
|
|
10699
10973
|
const existing = this.adminJobRetryRuns.get(retryKey);
|
|
10700
10974
|
if (existing) return existing;
|
|
10701
10975
|
const update = this.settingsTail.catch(() => {
|
|
10702
10976
|
}).then(async () => {
|
|
10703
10977
|
await this.flush();
|
|
10704
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10978
|
+
const { memory, owned } = await this.openAdminMemory(key2, { derivation: true });
|
|
10705
10979
|
try {
|
|
10706
|
-
await this.refreshExternalImportMemory(
|
|
10980
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10707
10981
|
const graphJob = kind === "graph-projection" ? memory.listGraphProjectionJobs().find((candidate) => candidate.id === jobId) : void 0;
|
|
10708
10982
|
const blockId = kind === "graph-projection" ? graphJob?.sourceEventIds.flatMap((eventId) => memory.listAllEvents().find(({ id: eventCandidateId }) => eventCandidateId === eventId)?.sourceBlockId ?? [])[0] : jobId;
|
|
10709
10983
|
const block = blockId ? memory.listBlocks().find((candidate) => candidate.id === blockId) : void 0;
|
|
@@ -10719,12 +10993,12 @@ var StrataGateRuntime = class {
|
|
|
10719
10993
|
if (!this.models.isReady(session)) {
|
|
10720
10994
|
throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
|
|
10721
10995
|
}
|
|
10722
|
-
const result = session && this.namespaceFor(session) ===
|
|
10996
|
+
const result = session && this.namespaceFor(session) === key2 ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry);
|
|
10723
10997
|
await this.persistSuccessfulResponses(memory);
|
|
10724
10998
|
const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
|
|
10725
10999
|
if (kind !== "graph-projection" && currentBlock?.processingStatus === "ready" && currentBlock.threadId) {
|
|
10726
|
-
this.pendingSurfaceSync.set(`${
|
|
10727
|
-
if (session && this.namespaceFor(session) ===
|
|
11000
|
+
this.pendingSurfaceSync.set(`${key2}\0${currentBlock.id}`, { namespace: key2, blockId: currentBlock.id });
|
|
11001
|
+
if (session && this.namespaceFor(session) === key2) await this.syncPendingRetrySurface(session, memory);
|
|
10728
11002
|
}
|
|
10729
11003
|
const job = kind === "block-summary" ? memory.listSummaryJobs().find(({ blockId: candidateId }) => candidateId === jobId) : kind === "event-extraction" ? memory.listExtractionJobs().find(({ blockId: candidateId }) => candidateId === jobId) : memory.listGraphProjectionJobs().find(({ id: candidateId }) => candidateId === jobId);
|
|
10730
11004
|
const succeeded = kind === "graph-projection" ? job?.status === "completed" : job?.status === "succeeded" || job?.status === "skipped";
|
|
@@ -10739,7 +11013,7 @@ var StrataGateRuntime = class {
|
|
|
10739
11013
|
result,
|
|
10740
11014
|
processingStatus: currentBlock?.processingStatus ?? null,
|
|
10741
11015
|
ready: currentBlock?.processingStatus === "ready",
|
|
10742
|
-
surfaceUpdated: !blockId || !this.pendingSurfaceSync.has(`${
|
|
11016
|
+
surfaceUpdated: !blockId || !this.pendingSurfaceSync.has(`${key2}\0${blockId}`)
|
|
10743
11017
|
};
|
|
10744
11018
|
} finally {
|
|
10745
11019
|
if (owned) await memory.close();
|
|
@@ -10755,14 +11029,14 @@ var StrataGateRuntime = class {
|
|
|
10755
11029
|
return tracked;
|
|
10756
11030
|
}
|
|
10757
11031
|
async adminRetryTopicProjection(namespace, id) {
|
|
10758
|
-
const
|
|
11032
|
+
const key2 = namespace.trim();
|
|
10759
11033
|
const jobId = id.trim();
|
|
10760
|
-
if (!
|
|
11034
|
+
if (!key2 || !jobId) throw new TypeError("Topic retry requires namespace and job id");
|
|
10761
11035
|
const update = this.settingsTail.catch(() => {
|
|
10762
11036
|
}).then(async () => {
|
|
10763
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
11037
|
+
const { memory, owned } = await this.openAdminMemory(key2, { derivation: true });
|
|
10764
11038
|
try {
|
|
10765
|
-
return await this.retryTopicWrite(
|
|
11039
|
+
return await this.retryTopicWrite(key2, memory, () => memory.retryTopicProjection(jobId));
|
|
10766
11040
|
} finally {
|
|
10767
11041
|
if (owned) await memory.close();
|
|
10768
11042
|
}
|
|
@@ -10939,8 +11213,8 @@ var StrataGateRuntime = class {
|
|
|
10939
11213
|
}
|
|
10940
11214
|
scheduleBlockDerivation(session, memory) {
|
|
10941
11215
|
const threadId = String(session.id);
|
|
10942
|
-
const
|
|
10943
|
-
if (this.closed || !this.models.isReady(session) || this.derivationTimers.has(
|
|
11216
|
+
const key2 = `${this.namespaceFor(session)}\0${threadId}`;
|
|
11217
|
+
if (this.closed || !this.models.isReady(session) || this.derivationTimers.has(key2) || this.derivationRuns.has(key2)) return;
|
|
10944
11218
|
const pendingBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId && block.processingStatus === "pending").map((block) => block.id));
|
|
10945
11219
|
if (pendingBlockIds.size === 0) return;
|
|
10946
11220
|
const retryTimes = [
|
|
@@ -10950,7 +11224,7 @@ var StrataGateRuntime = class {
|
|
|
10950
11224
|
if (retryTimes.length === 0) return;
|
|
10951
11225
|
const delay = Math.max(0, Math.min(...retryTimes) - Date.now());
|
|
10952
11226
|
const timer = setTimeout(() => {
|
|
10953
|
-
this.derivationTimers.delete(
|
|
11227
|
+
this.derivationTimers.delete(key2);
|
|
10954
11228
|
if (this.closed || !this.models.isReady(session)) return;
|
|
10955
11229
|
const failedBefore = this.failedCoreJobs(memory);
|
|
10956
11230
|
const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
|
|
@@ -10977,13 +11251,13 @@ var StrataGateRuntime = class {
|
|
|
10977
11251
|
this.notePluginError(session, error);
|
|
10978
11252
|
this.onIngestError(error);
|
|
10979
11253
|
}).finally(() => {
|
|
10980
|
-
this.derivationRuns.delete(
|
|
11254
|
+
this.derivationRuns.delete(key2);
|
|
10981
11255
|
this.scheduleBlockDerivation(session, memory);
|
|
10982
11256
|
});
|
|
10983
|
-
this.derivationRuns.set(
|
|
11257
|
+
this.derivationRuns.set(key2, run);
|
|
10984
11258
|
}, delay);
|
|
10985
11259
|
timer.unref?.();
|
|
10986
|
-
this.derivationTimers.set(
|
|
11260
|
+
this.derivationTimers.set(key2, timer);
|
|
10987
11261
|
}
|
|
10988
11262
|
scheduleGraphMigration(session, memory) {
|
|
10989
11263
|
const namespace = this.namespaceFor(session);
|
|
@@ -11068,23 +11342,28 @@ var StrataGateRuntime = class {
|
|
|
11068
11342
|
if (responses.length > 0) await memory.recordSuccessfulModelResponses(responses);
|
|
11069
11343
|
}
|
|
11070
11344
|
batch(session, evidence, results, metadata = {}) {
|
|
11071
|
-
const sequence =
|
|
11345
|
+
const sequence = this.batchSequence + 1;
|
|
11072
11346
|
const id = `batch_${sequence}`;
|
|
11073
11347
|
const refs = new Map(evidence.map(({ ref, target }) => [ref, target]));
|
|
11074
|
-
const
|
|
11075
|
-
|
|
11348
|
+
const output = { batchId: id, evidenceRefs: [...refs.keys()], results, ...metadata };
|
|
11349
|
+
if (validateArgs2({ retrieval: { type: "json", required: true } }, { retrieval: output }).length) {
|
|
11350
|
+
throw new TypeError("StrataGate retrieval output is not lossless JSON");
|
|
11351
|
+
}
|
|
11352
|
+
this.batchSequence = sequence;
|
|
11353
|
+
const key2 = String(session.id);
|
|
11354
|
+
let sessionBatches = this.batches.get(key2);
|
|
11076
11355
|
if (!sessionBatches) {
|
|
11077
11356
|
sessionBatches = /* @__PURE__ */ new Map();
|
|
11078
|
-
this.batches.set(
|
|
11357
|
+
this.batches.set(key2, sessionBatches);
|
|
11079
11358
|
}
|
|
11080
11359
|
sessionBatches.set(id, { id, sequence, refs, status: "unresolved" });
|
|
11081
|
-
this.latestBatchIds.set(
|
|
11082
|
-
return
|
|
11360
|
+
this.latestBatchIds.set(key2, id);
|
|
11361
|
+
return output;
|
|
11083
11362
|
}
|
|
11084
11363
|
requireBatch(session, batchId, operation) {
|
|
11085
|
-
const
|
|
11086
|
-
const selectedId = batchId?.trim() || this.latestBatchIds.get(
|
|
11087
|
-
const sessionBatches = this.batches.get(
|
|
11364
|
+
const key2 = String(session.id);
|
|
11365
|
+
const selectedId = batchId?.trim() || this.latestBatchIds.get(key2);
|
|
11366
|
+
const sessionBatches = this.batches.get(key2);
|
|
11088
11367
|
const batch = selectedId ? sessionBatches?.get(selectedId) : void 0;
|
|
11089
11368
|
if (batch) return batch;
|
|
11090
11369
|
const availableBatchIds = [...sessionBatches?.keys() ?? []];
|
|
@@ -11092,7 +11371,7 @@ var StrataGateRuntime = class {
|
|
|
11092
11371
|
const requested = batchId?.trim() ? `Unknown retrieval batch: ${batchId.trim()}.` : "No StrataGate retrieval batch exists for this session.";
|
|
11093
11372
|
throw new Error([
|
|
11094
11373
|
`${operation} could not select a retrieval batch. ${requested}`,
|
|
11095
|
-
`Latest batch: ${this.latestBatchIds.get(
|
|
11374
|
+
`Latest batch: ${this.latestBatchIds.get(key2) ?? "none"}.`,
|
|
11096
11375
|
`Unresolved batches: ${JSON.stringify(unresolvedBatchIds)}.`,
|
|
11097
11376
|
`Available batches: ${JSON.stringify(availableBatchIds)}.`
|
|
11098
11377
|
].join(" "));
|
|
@@ -11103,10 +11382,10 @@ var StrataGateRuntime = class {
|
|
|
11103
11382
|
return [...sessionBatches.values()].filter(({ status }) => status === "unresolved").map(({ id }) => id);
|
|
11104
11383
|
}
|
|
11105
11384
|
batchError(session, batch, summary, invalidEvidenceRefs = []) {
|
|
11106
|
-
const
|
|
11385
|
+
const key2 = String(session.id);
|
|
11107
11386
|
const assessmentStatus = batch.assessment?.verdict ?? "not_assessed";
|
|
11108
|
-
const latestBatch = this.latestBatchIds.get(
|
|
11109
|
-
const latestState = latestBatch ? this.batches.get(
|
|
11387
|
+
const latestBatch = this.latestBatchIds.get(key2);
|
|
11388
|
+
const latestState = latestBatch ? this.batches.get(key2)?.get(latestBatch) : void 0;
|
|
11110
11389
|
return [
|
|
11111
11390
|
summary,
|
|
11112
11391
|
`Invalid evidence refs: ${JSON.stringify(invalidEvidenceRefs)}.`,
|
|
@@ -11350,7 +11629,7 @@ function compactEvent2(event, score) {
|
|
|
11350
11629
|
summary: compactText2(event.summary),
|
|
11351
11630
|
sourceTime: event.temporal.happenedStart ?? event.temporal.mentionedAt ?? event.createdAt,
|
|
11352
11631
|
temporal: compactTemporal(event),
|
|
11353
|
-
sourceBlockId: event.sourceBlockId,
|
|
11632
|
+
...event.sourceBlockId !== void 0 ? { sourceBlockId: event.sourceBlockId } : {},
|
|
11354
11633
|
status: event.status,
|
|
11355
11634
|
scope: event.scope,
|
|
11356
11635
|
criticality: event.criticality,
|
|
@@ -11495,9 +11774,9 @@ Events:
|
|
|
11495
11774
|
metadataEvidenceStatus: result.metadataEvidenceStatus,
|
|
11496
11775
|
matchType: result.matchType,
|
|
11497
11776
|
currentState: node.currentState,
|
|
11498
|
-
facts: node.facts.map(({ key, value, status, validFrom, validTo }) => ({ key, value, status, validFrom, validTo })),
|
|
11777
|
+
facts: node.facts.map(({ key: key2, value, status, validFrom, validTo }) => ({ key: key2, value, status, validFrom, validTo })),
|
|
11499
11778
|
currentRelations: result.currentEdges?.map(({ relation, status }) => ({ relation, status })),
|
|
11500
|
-
historicalMatches: result.historicalFacts?.map(({ key, value, status, validFrom, validTo }) => ({ key, value, status, validFrom, validTo })),
|
|
11779
|
+
historicalMatches: result.historicalFacts?.map(({ key: key2, value, status, validFrom, validTo }) => ({ key: key2, value, status, validFrom, validTo })),
|
|
11501
11780
|
historicalRelations: result.historicalEdges?.map(({ relation, status, validFrom, validTo }) => ({ relation, status, validFrom, validTo })),
|
|
11502
11781
|
timeline: result.timeline
|
|
11503
11782
|
});
|
|
@@ -11557,7 +11836,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
11557
11836
|
},
|
|
11558
11837
|
output: jsonOutput,
|
|
11559
11838
|
execute: async (args, exec) => {
|
|
11560
|
-
if (Object.keys(args).some((
|
|
11839
|
+
if (Object.keys(args).some((key2) => key2 !== "field" && key2 !== "value")) throw new TypeError("Unknown Profile update argument");
|
|
11561
11840
|
sessionOf(exec);
|
|
11562
11841
|
return runtime.updatePersistentProfileFromTool(args.field, args.value);
|
|
11563
11842
|
}
|
|
@@ -11771,9 +12050,9 @@ var LEIDEN_RESOLUTION = 0.55;
|
|
|
11771
12050
|
function pairKey(left, right) {
|
|
11772
12051
|
return left < right ? `${left}\0${right}` : `${right}\0${left}`;
|
|
11773
12052
|
}
|
|
11774
|
-
function pairIds(
|
|
11775
|
-
const separator =
|
|
11776
|
-
return [
|
|
12053
|
+
function pairIds(key2) {
|
|
12054
|
+
const separator = key2.indexOf("\0");
|
|
12055
|
+
return [key2.slice(0, separator), key2.slice(separator + 1)];
|
|
11777
12056
|
}
|
|
11778
12057
|
function semanticTags(node) {
|
|
11779
12058
|
return Array.isArray(node.tags) ? [...new Set(node.tags.map((tag) => tag.trim()).filter(Boolean))] : [];
|
|
@@ -11804,8 +12083,8 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
11804
12083
|
const structuralWeights = /* @__PURE__ */ new Map();
|
|
11805
12084
|
const addStructuralWeight = (left, right, weight) => {
|
|
11806
12085
|
if (left === right || !nodeMap.has(left) || !nodeMap.has(right) || weight <= 0) return;
|
|
11807
|
-
const
|
|
11808
|
-
structuralWeights.set(
|
|
12086
|
+
const key2 = pairKey(left, right);
|
|
12087
|
+
structuralWeights.set(key2, (structuralWeights.get(key2) ?? 0) + weight);
|
|
11809
12088
|
};
|
|
11810
12089
|
for (const edge of rawEdges.filter((edge2) => edge2.status === "active")) {
|
|
11811
12090
|
addStructuralWeight(edge.fromNodeId, edge.toNodeId, 3 * Math.max(0.25, Number(edge.confidence) || 0.8));
|
|
@@ -11825,8 +12104,8 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
11825
12104
|
const graph = createGraph({ multigraph: false });
|
|
11826
12105
|
for (const node of nodes) graph.addNode(node.id);
|
|
11827
12106
|
const weightedDegree = new Map(nodes.map((node) => [node.id, 0]));
|
|
11828
|
-
for (const [
|
|
11829
|
-
const [leftId, rightId] = pairIds(
|
|
12107
|
+
for (const [key2, structuralWeight] of structuralWeights) {
|
|
12108
|
+
const [leftId, rightId] = pairIds(key2);
|
|
11830
12109
|
const left = nodeMap.get(leftId);
|
|
11831
12110
|
const right = nodeMap.get(rightId);
|
|
11832
12111
|
const leftTags = new Set(semanticTags(left));
|
|
@@ -11851,9 +12130,9 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
11851
12130
|
const connectedGroups = [];
|
|
11852
12131
|
const isolatedIds = [];
|
|
11853
12132
|
for (const community of partition.getCommunities().values()) {
|
|
11854
|
-
const
|
|
11855
|
-
const connected =
|
|
11856
|
-
const isolated =
|
|
12133
|
+
const ids2 = community.map(String).sort();
|
|
12134
|
+
const connected = ids2.filter((id) => (weightedDegree.get(id) ?? 0) > 0);
|
|
12135
|
+
const isolated = ids2.filter((id) => (weightedDegree.get(id) ?? 0) === 0);
|
|
11857
12136
|
if (connected.length > 0) connectedGroups.push(connected);
|
|
11858
12137
|
isolatedIds.push(...isolated);
|
|
11859
12138
|
}
|
|
@@ -11885,7 +12164,7 @@ function currentPluginVersion() {
|
|
|
11885
12164
|
return "unknown";
|
|
11886
12165
|
}
|
|
11887
12166
|
var STRATAGATE_DSH_VERSION = currentPluginVersion();
|
|
11888
|
-
var TOPIC_DIRECTORY_VIEW_VERSION =
|
|
12167
|
+
var TOPIC_DIRECTORY_VIEW_VERSION = 7;
|
|
11889
12168
|
function graphProjectionIsProcessing(job) {
|
|
11890
12169
|
return derivationJobState(job, false) === "processing" || derivationJobState(job, false) === "retryable";
|
|
11891
12170
|
}
|
|
@@ -11940,7 +12219,7 @@ function redactValue(value) {
|
|
|
11940
12219
|
if (typeof value === "string") return redact(value);
|
|
11941
12220
|
if (Array.isArray(value)) return value.map(redactValue);
|
|
11942
12221
|
if (value && typeof value === "object") {
|
|
11943
|
-
return Object.fromEntries(Object.entries(value).map(([
|
|
12222
|
+
return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, redactValue(item)]));
|
|
11944
12223
|
}
|
|
11945
12224
|
return value;
|
|
11946
12225
|
}
|
|
@@ -11949,17 +12228,17 @@ function redactedMessage(message, blockId) {
|
|
|
11949
12228
|
const common = { ...base, content: redact(message.content), blockId };
|
|
11950
12229
|
return toolCalls ? { ...common, toolCalls: redactValue(toolCalls) } : common;
|
|
11951
12230
|
}
|
|
11952
|
-
function sourceMessages(snapshot,
|
|
12231
|
+
function sourceMessages(snapshot, ids2) {
|
|
11953
12232
|
const output = [];
|
|
11954
12233
|
for (const block of snapshot.blocks) {
|
|
11955
12234
|
for (const message of block.l5Raw) {
|
|
11956
|
-
if (!
|
|
12235
|
+
if (!ids2 || ids2.has(message.id)) {
|
|
11957
12236
|
output.push(redactedMessage(message, block.id));
|
|
11958
12237
|
}
|
|
11959
12238
|
}
|
|
11960
12239
|
}
|
|
11961
12240
|
for (const message of snapshot.openTail) {
|
|
11962
|
-
if (!
|
|
12241
|
+
if (!ids2 || ids2.has(message.id)) {
|
|
11963
12242
|
output.push(redactedMessage(message, null));
|
|
11964
12243
|
}
|
|
11965
12244
|
}
|
|
@@ -12205,15 +12484,15 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
12205
12484
|
const failures = state.jobs.flatMap((job) => {
|
|
12206
12485
|
if (job.superseded || job.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || job.status !== "failed" || job.attempts < TOPIC_MAX_ATTEMPTS || !Object.entries({ ...job.sourceVersions, ...job.dependencyVersions }).every(([id, version]) => versions.get(id) === version)) return [];
|
|
12207
12486
|
const diagnostics = safeTopicDiagnostics(job.diagnostics);
|
|
12208
|
-
const
|
|
12209
|
-
const
|
|
12210
|
-
if (!outstandingWork.has(
|
|
12211
|
-
failedWork.add(
|
|
12487
|
+
const ids2 = memoryTopicUnfinishedJobEventIds(job, state, versions).filter((id) => {
|
|
12488
|
+
const key2 = relationKey(job.sectionBackfillTopicId, id, job.sourceVersions[id]);
|
|
12489
|
+
if (!outstandingWork.has(key2)) return false;
|
|
12490
|
+
failedWork.add(key2);
|
|
12212
12491
|
return true;
|
|
12213
12492
|
});
|
|
12214
|
-
return
|
|
12493
|
+
return ids2.length > 0 ? [{
|
|
12215
12494
|
jobId: job.id,
|
|
12216
|
-
eventIds:
|
|
12495
|
+
eventIds: ids2,
|
|
12217
12496
|
attempts: job.attempts,
|
|
12218
12497
|
// Topic failures store reason codes, never raw model output.
|
|
12219
12498
|
diagnostics,
|
|
@@ -12224,7 +12503,7 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
12224
12503
|
const total = (/* @__PURE__ */ new Set([...history.map(([id]) => id), ...backfillRelations.map(({ id }) => id)])).size;
|
|
12225
12504
|
const failedRelations = backfillRelations.filter(({ topicId, id, version }) => failedWork.has(relationKey(topicId, id, version))).length;
|
|
12226
12505
|
const bootstrap = frozen ? {
|
|
12227
|
-
status:
|
|
12506
|
+
status: outstandingWork.size === 0 ? "completed" : outstandingWork.size === failedWork.size ? "failed" : frozen.status === "pending" ? "pending" : "running",
|
|
12228
12507
|
total,
|
|
12229
12508
|
completed: total - outstanding.size,
|
|
12230
12509
|
failedEvents: new Set(failures.flatMap(({ eventIds }) => eventIds)).size,
|
|
@@ -12293,8 +12572,8 @@ function topicEventPageNumber(url, name2, fallback) {
|
|
|
12293
12572
|
}
|
|
12294
12573
|
async function topicEvents(runtime, url) {
|
|
12295
12574
|
const allowed = /* @__PURE__ */ new Set(["namespace", "topicId", "sectionKey", "offset", "limit", "expectedRevision"]);
|
|
12296
|
-
for (const
|
|
12297
|
-
if (!allowed.has(
|
|
12575
|
+
for (const key2 of url.searchParams.keys()) {
|
|
12576
|
+
if (!allowed.has(key2) || url.searchParams.getAll(key2).length !== 1) {
|
|
12298
12577
|
throw new AdminHttpError(400, "Unknown or repeated topic Event parameter");
|
|
12299
12578
|
}
|
|
12300
12579
|
}
|
|
@@ -12321,29 +12600,29 @@ async function topicEvents(runtime, url) {
|
|
|
12321
12600
|
revision: data.revision
|
|
12322
12601
|
});
|
|
12323
12602
|
}
|
|
12324
|
-
let
|
|
12603
|
+
let ids2;
|
|
12325
12604
|
if (topicId === "pending") {
|
|
12326
|
-
if (sectionKey === null)
|
|
12605
|
+
if (sectionKey === null) ids2 = projection.pending.map(({ id }) => id);
|
|
12327
12606
|
else if (sectionKey.startsWith("failure:")) {
|
|
12328
12607
|
const failure = projection.failures.find(({ jobId }) => jobId === sectionKey.slice("failure:".length));
|
|
12329
12608
|
if (!failure) throw new AdminHttpError(404, "Unknown or unavailable Topic failure");
|
|
12330
|
-
|
|
12609
|
+
ids2 = failure.eventIds;
|
|
12331
12610
|
} else throw new AdminHttpError(404, "Unknown or unavailable pending Topic section");
|
|
12332
12611
|
} else {
|
|
12333
12612
|
const topic = projection.chapters.find(({ id }) => id === topicId);
|
|
12334
12613
|
if (!topic) throw new AdminHttpError(404, "Unknown or unavailable memory topic");
|
|
12335
|
-
if (sectionKey === null)
|
|
12614
|
+
if (sectionKey === null) ids2 = topic.sourceEventIds;
|
|
12336
12615
|
else if (sectionKey === "uncovered") {
|
|
12337
12616
|
const covered = new Set(memoryTopicSections(topic).flatMap((section) => section.sourceEventIds));
|
|
12338
|
-
|
|
12617
|
+
ids2 = topic.sourceEventIds.filter((id) => !covered.has(id));
|
|
12339
12618
|
} else {
|
|
12340
12619
|
const section = memoryTopicSections(topic).find((candidate) => candidate.key === sectionKey);
|
|
12341
12620
|
if (!section) throw new AdminHttpError(404, "Unknown or unavailable memory topic section");
|
|
12342
|
-
|
|
12621
|
+
ids2 = section.sourceEventIds;
|
|
12343
12622
|
}
|
|
12344
12623
|
}
|
|
12345
12624
|
const byId = new Map(projection.visibleEvents.map((event) => [event.id, event]));
|
|
12346
|
-
const sources2 = [...new Set(
|
|
12625
|
+
const sources2 = [...new Set(ids2)].flatMap((id) => byId.get(id) ?? []);
|
|
12347
12626
|
const page = sources2.slice(offset, offset + limit);
|
|
12348
12627
|
return {
|
|
12349
12628
|
namespace,
|
|
@@ -12358,13 +12637,13 @@ async function topicEvents(runtime, url) {
|
|
|
12358
12637
|
};
|
|
12359
12638
|
}
|
|
12360
12639
|
async function overview(runtime, cachedEntries) {
|
|
12361
|
-
const
|
|
12640
|
+
const entries2 = cachedEntries ?? await Promise.all((await runtime.adminNamespaces()).map(async (namespace) => ({
|
|
12362
12641
|
namespace,
|
|
12363
12642
|
revision: 0,
|
|
12364
12643
|
snapshot: await runtime.adminSnapshot(namespace)
|
|
12365
12644
|
})));
|
|
12366
12645
|
const rows = [];
|
|
12367
|
-
for (const { namespace, snapshot } of
|
|
12646
|
+
for (const { namespace, snapshot } of entries2) {
|
|
12368
12647
|
if (!snapshot) continue;
|
|
12369
12648
|
const recovered = recoverSnapshotView(snapshot);
|
|
12370
12649
|
const blockDetails = (blockIds) => [...new Set(blockIds)].flatMap((blockId) => {
|
|
@@ -12433,7 +12712,12 @@ async function overview(runtime, cachedEntries) {
|
|
|
12433
12712
|
sourceEventIds: job.sourceEventIds
|
|
12434
12713
|
};
|
|
12435
12714
|
};
|
|
12436
|
-
const
|
|
12715
|
+
const topicEvents2 = [...snapshot.events, ...(runtime.adminAgentMemoryRetrievalWeight?.() ?? 1) > 0 ? snapshot.agentEvents : []].filter((event) => event.status === "active" || event.status === "superseded");
|
|
12716
|
+
const topicDirectory2 = new MemoryTopicDirectory();
|
|
12717
|
+
topicDirectory2.restore(snapshot.memoryTopicState);
|
|
12718
|
+
const topicState = topicDirectory2.snapshot(topicEvents2);
|
|
12719
|
+
const topicVersions = new Map(topicEvents2.map((event) => [event.id, memoryTopicEventFingerprint(event)]));
|
|
12720
|
+
const topicJobs = topicState.jobs.filter((job) => !job.superseded && (job.status === "completed" || memoryTopicUnfinishedJobEventIds(job, topicState, topicVersions).length > 0));
|
|
12437
12721
|
const describeTopicJob = (job) => {
|
|
12438
12722
|
const diagnostics = safeTopicDiagnostics(job.diagnostics);
|
|
12439
12723
|
const lastError = diagnostics ? `${diagnostics.category}: ${diagnostics.reason}` : ["timeout", "source-changed", "invalid-output", "worker-failed"].includes(job.lastError ?? "") ? job.lastError : "worker-failed";
|
|
@@ -12448,7 +12732,7 @@ async function overview(runtime, cachedEntries) {
|
|
|
12448
12732
|
lastError,
|
|
12449
12733
|
lastErrorFull: lastError,
|
|
12450
12734
|
diagnostics,
|
|
12451
|
-
eventCount: job.sourceEventIds.length,
|
|
12735
|
+
eventCount: job.status === "completed" ? job.sourceEventIds.length : memoryTopicUnfinishedJobEventIds(job, topicState, topicVersions).length,
|
|
12452
12736
|
blockIds: [],
|
|
12453
12737
|
threadIds: [],
|
|
12454
12738
|
blockDetails: []
|
|
@@ -12752,12 +13036,12 @@ function recoverSnapshotView(snapshot) {
|
|
|
12752
13036
|
if (turn !== null) receiptTurns.set(receiptTurnKey(threadId, receipt.createdAt), turn);
|
|
12753
13037
|
const currentActivity = receiptActivity.get(threadId);
|
|
12754
13038
|
if (!currentActivity || receipt.createdAt > currentActivity) receiptActivity.set(threadId, receipt.createdAt);
|
|
12755
|
-
const
|
|
12756
|
-
const candidates = receiptCandidates.get(
|
|
13039
|
+
const key2 = timestampKey(receipt.createdAt);
|
|
13040
|
+
const candidates = receiptCandidates.get(key2) ?? /* @__PURE__ */ new Set();
|
|
12757
13041
|
candidates.add(threadId);
|
|
12758
|
-
receiptCandidates.set(
|
|
13042
|
+
receiptCandidates.set(key2, candidates);
|
|
12759
13043
|
}
|
|
12760
|
-
const exactThreadAt = new Map([...receiptCandidates].filter(([,
|
|
13044
|
+
const exactThreadAt = new Map([...receiptCandidates].filter(([, ids2]) => ids2.size === 1).map(([createdAt, ids2]) => [createdAt, [...ids2][0]]));
|
|
12761
13045
|
const recoverMessages = (messages) => {
|
|
12762
13046
|
let precedingThreadId = null;
|
|
12763
13047
|
return messages.map((message) => {
|
|
@@ -12778,11 +13062,11 @@ function recoverSnapshotView(snapshot) {
|
|
|
12778
13062
|
messages.push(item.message);
|
|
12779
13063
|
groups.set(item.threadId, messages);
|
|
12780
13064
|
}
|
|
12781
|
-
const
|
|
12782
|
-
for (const [threadId, messages] of
|
|
12783
|
-
const virtual = !source.threadId && (
|
|
13065
|
+
const entries2 = [...groups];
|
|
13066
|
+
for (const [threadId, messages] of entries2) {
|
|
13067
|
+
const virtual = !source.threadId && (entries2.length > 1 || threadId !== LEGACY_THREAD_ID);
|
|
12784
13068
|
blocks.push({
|
|
12785
|
-
id:
|
|
13069
|
+
id: entries2.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
|
|
12786
13070
|
source,
|
|
12787
13071
|
threadId,
|
|
12788
13072
|
messages,
|
|
@@ -12831,12 +13115,12 @@ function virtualBlockLayers(block) {
|
|
|
12831
13115
|
]);
|
|
12832
13116
|
}
|
|
12833
13117
|
function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
|
|
12834
|
-
const
|
|
13118
|
+
const ids2 = /* @__PURE__ */ new Set([
|
|
12835
13119
|
...view.blocks.map((block) => block.threadId),
|
|
12836
13120
|
...view.openMessages.map(({ threadId }) => threadId),
|
|
12837
13121
|
...view.receiptThreads.values()
|
|
12838
13122
|
]);
|
|
12839
|
-
return [...
|
|
13123
|
+
return [...ids2].map((id) => {
|
|
12840
13124
|
const blocks = view.blocks.filter((block) => block.threadId === id);
|
|
12841
13125
|
const messages = [
|
|
12842
13126
|
...blocks.flatMap((block) => block.messages),
|
|
@@ -13035,12 +13319,12 @@ async function sources(runtime, url) {
|
|
|
13035
13319
|
const blockId = url.searchParams.get("blockId");
|
|
13036
13320
|
let events = [];
|
|
13037
13321
|
let elements = [];
|
|
13038
|
-
let
|
|
13322
|
+
let ids2 = /* @__PURE__ */ new Set();
|
|
13039
13323
|
if (eventId) {
|
|
13040
13324
|
const event = snapshot.events.find(({ id }) => id === eventId) ?? ((runtime.adminAgentMemoryRetrievalWeight?.() ?? 1) > 0 ? snapshot.agentEvents.find(({ id, status }) => id === eventId && (status === "active" || status === "superseded")) : void 0);
|
|
13041
13325
|
if (!event) throw new AdminHttpError(404, `Unknown event: ${eventId}`);
|
|
13042
13326
|
events = [event];
|
|
13043
|
-
|
|
13327
|
+
ids2 = new Set(event.sourceMessageIds);
|
|
13044
13328
|
} else if (nodeId) {
|
|
13045
13329
|
const rawNode = snapshot.graphNodes.find(({ id }) => id === nodeId);
|
|
13046
13330
|
if (!rawNode) throw new AdminHttpError(404, `Unknown graph node: ${nodeId}`);
|
|
@@ -13062,7 +13346,7 @@ async function sources(runtime, url) {
|
|
|
13062
13346
|
...edges.flatMap(({ sourceEventIds }) => sourceEventIds)
|
|
13063
13347
|
]);
|
|
13064
13348
|
events = snapshot.events.filter(({ id }) => eventIds.has(id));
|
|
13065
|
-
|
|
13349
|
+
ids2 = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
13066
13350
|
return {
|
|
13067
13351
|
namespace,
|
|
13068
13352
|
node: view.node,
|
|
@@ -13073,28 +13357,28 @@ async function sources(runtime, url) {
|
|
|
13073
13357
|
currentEdges: view.currentEdges,
|
|
13074
13358
|
historicalEdges: view.historicalEdges,
|
|
13075
13359
|
events: events.map((event) => eventSummary(event)),
|
|
13076
|
-
messages: sourceMessages(snapshot,
|
|
13360
|
+
messages: sourceMessages(snapshot, ids2)
|
|
13077
13361
|
};
|
|
13078
13362
|
} else if (elementId) {
|
|
13079
13363
|
const element = snapshot.elements.find(({ id }) => id === elementId);
|
|
13080
13364
|
if (!element) throw new AdminHttpError(404, `Unknown element: ${elementId}`);
|
|
13081
13365
|
elements = [element];
|
|
13082
13366
|
events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
|
|
13083
|
-
|
|
13367
|
+
ids2 = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
13084
13368
|
} else if (blockId) {
|
|
13085
13369
|
const displayBlock = recoverSnapshotView(snapshot).blocks.find(({ id }) => id === blockId);
|
|
13086
13370
|
const block = displayBlock?.source ?? snapshot.blocks.find(({ id }) => id === blockId);
|
|
13087
13371
|
if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
|
|
13088
13372
|
const messages = displayBlock?.messages ?? block.l5Raw;
|
|
13089
|
-
|
|
13090
|
-
events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) =>
|
|
13373
|
+
ids2 = new Set(messages.map(({ id }) => id));
|
|
13374
|
+
events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) => ids2.has(id))));
|
|
13091
13375
|
const eventIds = new Set(events.map(({ id }) => id));
|
|
13092
13376
|
elements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
13093
13377
|
return {
|
|
13094
13378
|
namespace,
|
|
13095
13379
|
events: events.map((event) => eventSummary(event)),
|
|
13096
13380
|
elements: elements.map(elementSummary),
|
|
13097
|
-
messages: sourceMessages(snapshot,
|
|
13381
|
+
messages: sourceMessages(snapshot, ids2),
|
|
13098
13382
|
layers: displayBlock ? virtualBlockLayers(displayBlock) : blockLayers(block),
|
|
13099
13383
|
virtual: displayBlock?.virtual ?? false
|
|
13100
13384
|
};
|
|
@@ -13109,7 +13393,7 @@ async function sources(runtime, url) {
|
|
|
13109
13393
|
} : {},
|
|
13110
13394
|
elements: elements.map(elementSummary),
|
|
13111
13395
|
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 })),
|
|
13112
|
-
messages: sourceMessages(snapshot,
|
|
13396
|
+
messages: sourceMessages(snapshot, ids2)
|
|
13113
13397
|
};
|
|
13114
13398
|
}
|
|
13115
13399
|
async function expandBlock(runtime, url) {
|
|
@@ -13207,20 +13491,20 @@ async function audit(runtime, url) {
|
|
|
13207
13491
|
}
|
|
13208
13492
|
function requestHeader(req, name2) {
|
|
13209
13493
|
const headers = req.headers ?? {};
|
|
13210
|
-
const
|
|
13211
|
-
const value =
|
|
13494
|
+
const key2 = Object.keys(headers).find((candidate) => candidate.toLocaleLowerCase() === name2.toLocaleLowerCase());
|
|
13495
|
+
const value = key2 ? headers[key2] : void 0;
|
|
13212
13496
|
return Array.isArray(value) ? value.join(", ") : value ?? "";
|
|
13213
13497
|
}
|
|
13214
13498
|
async function dashboard(runtime, url, ifNoneMatch) {
|
|
13215
|
-
const
|
|
13499
|
+
const entries2 = await runtime.adminSnapshotEntries();
|
|
13216
13500
|
const requestedNamespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
13217
|
-
const selected =
|
|
13501
|
+
const selected = entries2.find(({ namespace }) => namespace === requestedNamespace) ?? entries2[0];
|
|
13218
13502
|
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
13219
|
-
const revisionKey =
|
|
13503
|
+
const revisionKey = entries2.map(({ namespace, revision }) => `${namespace}:${revision}`).join("|");
|
|
13220
13504
|
const agentMemoryWeight = runtime.adminAgentMemoryRetrievalWeight?.() ?? 1;
|
|
13221
13505
|
const etag = `"${createHash4("sha256").update(`${STRATAGATE_DSH_VERSION}\0${TOPIC_DIRECTORY_VIEW_VERSION}\0${revisionKey}\0${selected?.namespace ?? ""}\0${threadId}\0${agentMemoryWeight}`).digest("base64url").slice(0, 24)}"`;
|
|
13222
13506
|
if (ifNoneMatch.split(",").map((value) => value.trim()).includes(etag)) return { etag, notModified: true };
|
|
13223
|
-
const overviewValue = await overview(runtime,
|
|
13507
|
+
const overviewValue = await overview(runtime, entries2);
|
|
13224
13508
|
if (!selected) {
|
|
13225
13509
|
return { etag, notModified: false, body: { namespace: null, overview: overviewValue, data: null, processing: false } };
|
|
13226
13510
|
}
|
|
@@ -13358,8 +13642,8 @@ function isCompleteAnswer(data) {
|
|
|
13358
13642
|
if (!data.message.content.some((block) => block.type === "text" && block.text.trim().length > 0)) return false;
|
|
13359
13643
|
const stream = data.stream ?? [];
|
|
13360
13644
|
for (let index = stream.length - 1; index >= 0; index--) {
|
|
13361
|
-
const
|
|
13362
|
-
if (
|
|
13645
|
+
const record3 = stream[index];
|
|
13646
|
+
if (record3.type === "chunk" && record3.chunk.type === "finish") return record3.chunk.reason.kind === "stop";
|
|
13363
13647
|
}
|
|
13364
13648
|
return true;
|
|
13365
13649
|
}
|
|
@@ -13863,20 +14147,22 @@ async function apply(ctx, config) {
|
|
|
13863
14147
|
}
|
|
13864
14148
|
const session = context.agent?.session;
|
|
13865
14149
|
if (!session) return { ...assembled, contexts };
|
|
14150
|
+
let autoMemory;
|
|
13866
14151
|
try {
|
|
13867
|
-
const
|
|
13868
|
-
|
|
14152
|
+
const text3 = await runtime.buildAutoContext(session);
|
|
14153
|
+
autoMemory = { name: "stratagate:auto-memory", text: text3 };
|
|
13869
14154
|
} catch (error) {
|
|
13870
|
-
ctx.logger.warn(`stratagate-memory
|
|
14155
|
+
ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
|
|
13871
14156
|
runtime.notePluginError(session, error);
|
|
13872
14157
|
}
|
|
13873
14158
|
try {
|
|
13874
|
-
const
|
|
13875
|
-
contexts.push({ name: "stratagate:
|
|
14159
|
+
const directory = await runtime.buildMemoryDirectory(session);
|
|
14160
|
+
if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
|
|
13876
14161
|
} catch (error) {
|
|
13877
|
-
ctx.logger.warn(`stratagate-memory
|
|
14162
|
+
ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
|
|
13878
14163
|
runtime.notePluginError(session, error);
|
|
13879
14164
|
}
|
|
14165
|
+
if (autoMemory) contexts.push(autoMemory);
|
|
13880
14166
|
const feedbackSuggestion = runtime.takeFeedbackSuggestion(session);
|
|
13881
14167
|
if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
|
|
13882
14168
|
return { ...assembled, contexts };
|