stratagate-dsh 0.3.5 → 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 +12 -0
- package/README.md +111 -64
- package/README.zh-CN.md +113 -66
- package/dist/client.js +2 -2
- package/dist/plugin.js +683 -443
- package/dist/plugin.js.map +1 -1
- package/docs/DSH.md +3 -1
- package/docs/DSH.zh-CN.md +5 -1
- 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,11 +9275,11 @@ 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 (
|
|
9278
|
+
const { entries: entries2, categories: categories2 } = topicNavigation(topics, events);
|
|
9279
|
+
if (entries2.length === 0) return "";
|
|
9050
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";
|
|
9051
9281
|
const byId = new Map(topics.map((topic) => [topic.id, topic]));
|
|
9052
|
-
const chapters =
|
|
9282
|
+
const chapters = entries2.map((entry) => ({
|
|
9053
9283
|
...entry,
|
|
9054
9284
|
line: `- ${entry.id}\uFF1A${lineText(entry.title, 64)}${entry.description ? `\uFF1B${lineText(entry.description, 48)}` : ""}`,
|
|
9055
9285
|
// Reuse the book's stable section order, including summary-free and legacy sections.
|
|
@@ -9061,7 +9291,7 @@ ${selected2.flatMap(({ line, sections }) => [line, ...sections]).join("\n")}`.tr
|
|
|
9061
9291
|
if (estimateTokens(complete) <= MEMORY_DIRECTORY_TOKEN_BUDGET) return complete;
|
|
9062
9292
|
const branches = categories2.map(({ id, label, count }) => `${id}\uFF08${label} ${count}\uFF09`).join("\uFF1B");
|
|
9063
9293
|
const navigation = `${header}
|
|
9064
|
-
\u5171 ${
|
|
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`;
|
|
9065
9295
|
let selected = chapters.map((chapter) => ({ ...chapter, sections: [] }));
|
|
9066
9296
|
if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9067
9297
|
selected = [];
|
|
@@ -9077,7 +9307,7 @@ ${selected2.flatMap(({ line, sections }) => [line, ...sections]).join("\n")}`.tr
|
|
|
9077
9307
|
for (const category of categories2) admit(chapters.find((chapter) => chapter.category === category.id));
|
|
9078
9308
|
for (const chapter of chapters) if (!selected.some(({ id }) => id === chapter.id)) admit(chapter);
|
|
9079
9309
|
}
|
|
9080
|
-
const order = new Map(
|
|
9310
|
+
const order = new Map(entries2.map(({ id }, index) => [id, index]));
|
|
9081
9311
|
selected.sort((a, b) => order.get(a.id) - order.get(b.id));
|
|
9082
9312
|
const sectionLines = new Map(chapters.map(({ id, sections }) => [id, sections]));
|
|
9083
9313
|
const exhausted = /* @__PURE__ */ new Set();
|
|
@@ -9159,13 +9389,13 @@ var FEEDBACK_MARKDOWN_HEADINGS = {
|
|
|
9159
9389
|
};
|
|
9160
9390
|
function patchFeedbackMarkdown(markdown, input) {
|
|
9161
9391
|
let result = markdown.trim();
|
|
9162
|
-
for (const [
|
|
9163
|
-
if (!Object.prototype.hasOwnProperty.call(input,
|
|
9164
|
-
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];
|
|
9165
9395
|
const text3 = Array.isArray(raw) ? raw.map((item, index) => {
|
|
9166
9396
|
const value = feedbackText(item, 2e3);
|
|
9167
9397
|
return value ? `${index + 1}. ${value}` : "";
|
|
9168
|
-
}).filter(Boolean).join("\n") : feedbackText(raw,
|
|
9398
|
+
}).filter(Boolean).join("\n") : feedbackText(raw, key2 === "description" ? 2e4 : key2 === "expected" || key2 === "actual" ? 1e4 : 2e4);
|
|
9169
9399
|
const marker = `## ${heading}`;
|
|
9170
9400
|
const lines = result.split("\n");
|
|
9171
9401
|
let fence = null;
|
|
@@ -9207,9 +9437,9 @@ ${text3}`;
|
|
|
9207
9437
|
return result.trim();
|
|
9208
9438
|
}
|
|
9209
9439
|
function normalizeFeedbackDraft(input, previous) {
|
|
9210
|
-
const has = (
|
|
9440
|
+
const has = (key2) => Object.prototype.hasOwnProperty.call(input, key2);
|
|
9211
9441
|
const replacesStructuredBody = has("bodyMarkdown");
|
|
9212
|
-
const updatesStructuredBody = ["description", "reproduction", "expected", "actual", "errorContext"].some((
|
|
9442
|
+
const updatesStructuredBody = ["description", "reproduction", "expected", "actual", "errorContext"].some((key2) => has(key2));
|
|
9213
9443
|
const reproduction = replacesStructuredBody ? [] : has("reproduction") ? (Array.isArray(input.reproduction) ? input.reproduction : []).map((value) => feedbackText(value, 2e3)).filter(Boolean).slice(0, 20) : previous?.reproduction ?? [];
|
|
9214
9444
|
const bodyMarkdown = has("bodyMarkdown") ? feedbackText(input.bodyMarkdown, 5e4) : updatesStructuredBody && previous?.bodyMarkdown ? patchFeedbackMarkdown(previous.bodyMarkdown, input) : previous?.bodyMarkdown;
|
|
9215
9445
|
return {
|
|
@@ -9392,7 +9622,7 @@ var StrataGateRuntime = class {
|
|
|
9392
9622
|
...this.derivationRuns.keys(),
|
|
9393
9623
|
...this.migrationTimers.keys(),
|
|
9394
9624
|
...this.migrationRuns.keys()
|
|
9395
|
-
].some((
|
|
9625
|
+
].some((key2) => key2.startsWith(`${namespace}\0`) || key2 === namespace);
|
|
9396
9626
|
if (hasActiveSessionWork) return;
|
|
9397
9627
|
if (!this.models.isReady()) return;
|
|
9398
9628
|
const resumed = await this.models.runDetached(
|
|
@@ -9491,24 +9721,24 @@ var StrataGateRuntime = class {
|
|
|
9491
9721
|
if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
|
|
9492
9722
|
const turn = this.folder.accept(session, event);
|
|
9493
9723
|
if (!turn) return;
|
|
9494
|
-
const
|
|
9495
|
-
this.sessionsById.set(
|
|
9496
|
-
const queued = this.pendingTurns.get(
|
|
9724
|
+
const key2 = String(session.id);
|
|
9725
|
+
this.sessionsById.set(key2, session);
|
|
9726
|
+
const queued = this.pendingTurns.get(key2) ?? [];
|
|
9497
9727
|
queued.push(turn);
|
|
9498
|
-
this.pendingTurns.set(
|
|
9728
|
+
this.pendingTurns.set(key2, queued);
|
|
9499
9729
|
this.scheduleDrain(session);
|
|
9500
9730
|
}
|
|
9501
9731
|
/** Serialize drains per session while allowing unrelated sessions to settle independently. */
|
|
9502
9732
|
enqueueDrain(session) {
|
|
9503
|
-
const
|
|
9504
|
-
const prior = this.drainTails.get(
|
|
9733
|
+
const key2 = String(session.id);
|
|
9734
|
+
const prior = this.drainTails.get(key2) ?? Promise.resolve();
|
|
9505
9735
|
const next = prior.catch(() => {
|
|
9506
9736
|
}).then(() => this.drain(session));
|
|
9507
|
-
this.drainTails.set(
|
|
9737
|
+
this.drainTails.set(key2, next);
|
|
9508
9738
|
void next.then(() => {
|
|
9509
|
-
if ((this.pendingTurns.get(
|
|
9739
|
+
if ((this.pendingTurns.get(key2)?.length ?? 0) === 0) this.drainErrors.delete(key2);
|
|
9510
9740
|
}, (error) => {
|
|
9511
|
-
this.drainErrors.set(
|
|
9741
|
+
this.drainErrors.set(key2, error);
|
|
9512
9742
|
this.notePluginError(session, error);
|
|
9513
9743
|
this.onIngestError(error);
|
|
9514
9744
|
});
|
|
@@ -9516,46 +9746,46 @@ var StrataGateRuntime = class {
|
|
|
9516
9746
|
}
|
|
9517
9747
|
/** Batch short bursts and retry failed drains with bounded exponential backoff. */
|
|
9518
9748
|
scheduleDrain(session, overrideDelayMs) {
|
|
9519
|
-
const
|
|
9749
|
+
const key2 = String(session.id);
|
|
9520
9750
|
if (this.closed) return;
|
|
9521
|
-
const queued = this.pendingTurns.get(
|
|
9751
|
+
const queued = this.pendingTurns.get(key2)?.length ?? 0;
|
|
9522
9752
|
if (queued === 0) return;
|
|
9523
|
-
const delay = overrideDelayMs ?? (queued >= DRAIN_THRESHOLD ? DRAIN_EAGER_MS : Math.min(this.drainBackoffMs.get(
|
|
9524
|
-
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);
|
|
9525
9755
|
if (pending) {
|
|
9526
9756
|
if (delay >= DRAIN_BASE_BACKOFF_MS) return;
|
|
9527
9757
|
clearTimeout(pending);
|
|
9528
|
-
this.drainTimers.delete(
|
|
9758
|
+
this.drainTimers.delete(key2);
|
|
9529
9759
|
}
|
|
9530
9760
|
const timer = setTimeout(() => {
|
|
9531
|
-
this.drainTimers.delete(
|
|
9761
|
+
this.drainTimers.delete(key2);
|
|
9532
9762
|
if (this.closed) return;
|
|
9533
9763
|
void this.enqueueDrain(session).then(() => {
|
|
9534
|
-
this.drainBackoffMs.delete(
|
|
9535
|
-
if ((this.pendingTurns.get(
|
|
9764
|
+
this.drainBackoffMs.delete(key2);
|
|
9765
|
+
if ((this.pendingTurns.get(key2)?.length ?? 0) > 0) {
|
|
9536
9766
|
this.scheduleDrain(session, DRAIN_BASE_BACKOFF_MS);
|
|
9537
9767
|
}
|
|
9538
9768
|
}, () => {
|
|
9539
|
-
const backoff = this.drainBackoffMs.get(
|
|
9540
|
-
this.drainBackoffMs.set(
|
|
9541
|
-
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);
|
|
9542
9772
|
if (pendingRetry) clearTimeout(pendingRetry);
|
|
9543
|
-
this.drainTimers.delete(
|
|
9773
|
+
this.drainTimers.delete(key2);
|
|
9544
9774
|
this.scheduleDrain(session);
|
|
9545
9775
|
});
|
|
9546
9776
|
}, delay);
|
|
9547
9777
|
timer.unref?.();
|
|
9548
|
-
this.drainTimers.set(
|
|
9778
|
+
this.drainTimers.set(key2, timer);
|
|
9549
9779
|
}
|
|
9550
9780
|
/**
|
|
9551
9781
|
* Persist one detached batch. On failure, the failed and unvisited turns are
|
|
9552
9782
|
* restored ahead of turns that arrived meanwhile, preserving original order.
|
|
9553
9783
|
*/
|
|
9554
9784
|
async drain(session) {
|
|
9555
|
-
const
|
|
9556
|
-
const turns = this.pendingTurns.get(
|
|
9785
|
+
const key2 = String(session.id);
|
|
9786
|
+
const turns = this.pendingTurns.get(key2);
|
|
9557
9787
|
if (!turns || turns.length === 0) return;
|
|
9558
|
-
this.pendingTurns.delete(
|
|
9788
|
+
this.pendingTurns.delete(key2);
|
|
9559
9789
|
let completed = 0;
|
|
9560
9790
|
let memory;
|
|
9561
9791
|
try {
|
|
@@ -9568,7 +9798,7 @@ var StrataGateRuntime = class {
|
|
|
9568
9798
|
} catch (error) {
|
|
9569
9799
|
const remaining = turns.slice(completed);
|
|
9570
9800
|
if (remaining.length > 0) {
|
|
9571
|
-
this.pendingTurns.set(
|
|
9801
|
+
this.pendingTurns.set(key2, [...remaining, ...this.pendingTurns.get(key2) ?? []]);
|
|
9572
9802
|
}
|
|
9573
9803
|
throw error;
|
|
9574
9804
|
} finally {
|
|
@@ -9803,7 +10033,7 @@ var StrataGateRuntime = class {
|
|
|
9803
10033
|
};
|
|
9804
10034
|
}
|
|
9805
10035
|
async recordUse(session, receiptId, evidenceRefs, batchId) {
|
|
9806
|
-
const
|
|
10036
|
+
const key2 = String(session.id);
|
|
9807
10037
|
const batch = this.requireBatch(session, batchId, "memory_record_use");
|
|
9808
10038
|
if (batch.status !== "unresolved") {
|
|
9809
10039
|
throw new Error(this.batchError(
|
|
@@ -9882,9 +10112,9 @@ var StrataGateRuntime = class {
|
|
|
9882
10112
|
eventIds: [...eventIds],
|
|
9883
10113
|
elementIds: [...elementIds]
|
|
9884
10114
|
}, {
|
|
9885
|
-
receiptId: `dsh:${
|
|
10115
|
+
receiptId: `dsh:${key2}:tool:${receiptId}`,
|
|
9886
10116
|
audit: {
|
|
9887
|
-
sessionId:
|
|
10117
|
+
sessionId: key2,
|
|
9888
10118
|
...turn === void 0 ? {} : { turn },
|
|
9889
10119
|
batchId: batch.id,
|
|
9890
10120
|
evidenceRefs: selectedRefs,
|
|
@@ -10048,9 +10278,9 @@ var StrataGateRuntime = class {
|
|
|
10048
10278
|
const contexts = new Map(memory.getBlockContext(threadId).map((context) => [context.id, context]));
|
|
10049
10279
|
const existing = currentBlockSurfaceMessages(session);
|
|
10050
10280
|
let changed = false;
|
|
10051
|
-
for (const [
|
|
10281
|
+
for (const [key2, { blockId }] of pending) {
|
|
10052
10282
|
if (existing.has(blockId)) {
|
|
10053
|
-
this.pendingSurfaceSync.delete(
|
|
10283
|
+
this.pendingSurfaceSync.delete(key2);
|
|
10054
10284
|
continue;
|
|
10055
10285
|
}
|
|
10056
10286
|
const block = memory.listBlocks().find((candidate) => candidate.id === blockId && candidate.threadId === threadId);
|
|
@@ -10058,7 +10288,7 @@ var StrataGateRuntime = class {
|
|
|
10058
10288
|
if (!block || block.processingStatus !== "ready" || !context) continue;
|
|
10059
10289
|
try {
|
|
10060
10290
|
changed = this.replaceSealedSurface(session, block, context, dshTurnAtBlockEnd(session, block)) || changed;
|
|
10061
|
-
this.pendingSurfaceSync.delete(
|
|
10291
|
+
this.pendingSurfaceSync.delete(key2);
|
|
10062
10292
|
} catch (error) {
|
|
10063
10293
|
this.onIngestError(error);
|
|
10064
10294
|
}
|
|
@@ -10068,11 +10298,11 @@ var StrataGateRuntime = class {
|
|
|
10068
10298
|
// Keep the ingestion error for callers that explicitly require a flushed run.
|
|
10069
10299
|
async settleIngestion() {
|
|
10070
10300
|
do {
|
|
10071
|
-
for (const [
|
|
10072
|
-
if ((this.pendingTurns.get(
|
|
10073
|
-
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);
|
|
10074
10304
|
if (timer) clearTimeout(timer);
|
|
10075
|
-
this.drainTimers.delete(
|
|
10305
|
+
this.drainTimers.delete(key2);
|
|
10076
10306
|
this.enqueueDrain(session);
|
|
10077
10307
|
}
|
|
10078
10308
|
await Promise.allSettled(this.drainTails.values());
|
|
@@ -10135,16 +10365,16 @@ var StrataGateRuntime = class {
|
|
|
10135
10365
|
};
|
|
10136
10366
|
}
|
|
10137
10367
|
adminFeedbackDraft(namespace) {
|
|
10138
|
-
const
|
|
10139
|
-
if (!
|
|
10140
|
-
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) };
|
|
10141
10371
|
}
|
|
10142
10372
|
adminSaveFeedbackDraft(namespace, input) {
|
|
10143
|
-
const
|
|
10144
|
-
if (!
|
|
10145
|
-
const draft = normalizeFeedbackDraft(input, this.loadFeedbackDraft(
|
|
10146
|
-
this.saveFeedbackDraft(
|
|
10147
|
-
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 };
|
|
10148
10378
|
}
|
|
10149
10379
|
/**
|
|
10150
10380
|
* Record one agent-authored fact through the core long-term Event pipeline.
|
|
@@ -10312,7 +10542,7 @@ var StrataGateRuntime = class {
|
|
|
10312
10542
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
|
|
10313
10543
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
10314
10544
|
try {
|
|
10315
|
-
const
|
|
10545
|
+
const entries2 = [];
|
|
10316
10546
|
for (const { namespace, revision } of storage.listNamespaceRevisions()) {
|
|
10317
10547
|
const opening = this.spaces.get(namespace);
|
|
10318
10548
|
if (opening) {
|
|
@@ -10322,25 +10552,25 @@ var StrataGateRuntime = class {
|
|
|
10322
10552
|
const cached2 = this.adminSnapshotCache.get(namespace);
|
|
10323
10553
|
const entry2 = cached2?.revision === currentRevision ? cached2 : { namespace, revision: currentRevision, snapshot: memory.exportSnapshot() };
|
|
10324
10554
|
this.adminSnapshotCache.set(namespace, entry2);
|
|
10325
|
-
|
|
10555
|
+
entries2.push(entry2);
|
|
10326
10556
|
continue;
|
|
10327
10557
|
}
|
|
10328
10558
|
const cached = this.adminSnapshotCache.get(namespace);
|
|
10329
10559
|
if (cached?.revision === revision) {
|
|
10330
|
-
|
|
10560
|
+
entries2.push(cached);
|
|
10331
10561
|
continue;
|
|
10332
10562
|
}
|
|
10333
10563
|
const loaded = await storage.load(namespace);
|
|
10334
10564
|
if (!loaded) continue;
|
|
10335
10565
|
const entry = { namespace, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
10336
10566
|
this.adminSnapshotCache.set(namespace, entry);
|
|
10337
|
-
|
|
10567
|
+
entries2.push(entry);
|
|
10338
10568
|
}
|
|
10339
|
-
const activeNamespaces = new Set(
|
|
10569
|
+
const activeNamespaces = new Set(entries2.map(({ namespace }) => namespace));
|
|
10340
10570
|
for (const namespace of this.adminSnapshotCache.keys()) {
|
|
10341
10571
|
if (!activeNamespaces.has(namespace)) this.adminSnapshotCache.delete(namespace);
|
|
10342
10572
|
}
|
|
10343
|
-
return
|
|
10573
|
+
return entries2;
|
|
10344
10574
|
} finally {
|
|
10345
10575
|
await storage.close();
|
|
10346
10576
|
}
|
|
@@ -10371,30 +10601,30 @@ var StrataGateRuntime = class {
|
|
|
10371
10601
|
}
|
|
10372
10602
|
}
|
|
10373
10603
|
async adminSnapshot(namespace) {
|
|
10374
|
-
const
|
|
10375
|
-
if (!
|
|
10604
|
+
const key2 = namespace.trim();
|
|
10605
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10376
10606
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
10377
|
-
const opening = this.spaces.get(
|
|
10607
|
+
const opening = this.spaces.get(key2);
|
|
10378
10608
|
if (opening) {
|
|
10379
10609
|
const memory = await opening;
|
|
10380
|
-
await this.refreshExternalImportMemory(
|
|
10610
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10381
10611
|
const revision = memory.storageRevision;
|
|
10382
|
-
const cached = this.adminSnapshotCache.get(
|
|
10612
|
+
const cached = this.adminSnapshotCache.get(key2);
|
|
10383
10613
|
if (cached?.revision === revision) return cached.snapshot;
|
|
10384
|
-
const entry = { namespace:
|
|
10385
|
-
this.adminSnapshotCache.set(
|
|
10614
|
+
const entry = { namespace: key2, revision, snapshot: memory.exportSnapshot() };
|
|
10615
|
+
this.adminSnapshotCache.set(key2, entry);
|
|
10386
10616
|
return entry.snapshot;
|
|
10387
10617
|
}
|
|
10388
10618
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
10389
10619
|
try {
|
|
10390
|
-
const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 ===
|
|
10620
|
+
const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 === key2);
|
|
10391
10621
|
if (!head) return null;
|
|
10392
|
-
const cached = this.adminSnapshotCache.get(
|
|
10622
|
+
const cached = this.adminSnapshotCache.get(key2);
|
|
10393
10623
|
if (cached?.revision === head.revision) return cached.snapshot;
|
|
10394
|
-
const loaded = await storage.load(
|
|
10624
|
+
const loaded = await storage.load(key2);
|
|
10395
10625
|
if (!loaded) return null;
|
|
10396
|
-
const entry = { namespace:
|
|
10397
|
-
this.adminSnapshotCache.set(
|
|
10626
|
+
const entry = { namespace: key2, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
10627
|
+
this.adminSnapshotCache.set(key2, entry);
|
|
10398
10628
|
return entry.snapshot;
|
|
10399
10629
|
} finally {
|
|
10400
10630
|
await storage.close();
|
|
@@ -10523,44 +10753,44 @@ var StrataGateRuntime = class {
|
|
|
10523
10753
|
}
|
|
10524
10754
|
/** Create a durable analysis job and return before model-backed work begins. */
|
|
10525
10755
|
async adminPreviewExternalMemory(namespace, text3) {
|
|
10526
|
-
const
|
|
10527
|
-
if (!
|
|
10756
|
+
const key2 = namespace.trim();
|
|
10757
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10528
10758
|
if (!text3.trim()) throw new TypeError("External memory text must not be empty");
|
|
10529
10759
|
await this.flush();
|
|
10530
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10760
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10531
10761
|
try {
|
|
10532
|
-
await this.refreshExternalImportMemory(
|
|
10762
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10533
10763
|
const job = await memory.createExternalMemoryImportJob(text3);
|
|
10534
|
-
this.scheduleExternalMemoryImport(
|
|
10764
|
+
this.scheduleExternalMemoryImport(key2, job.id);
|
|
10535
10765
|
return this.externalImportView(job);
|
|
10536
10766
|
} finally {
|
|
10537
10767
|
if (owned) await memory.close();
|
|
10538
10768
|
}
|
|
10539
10769
|
}
|
|
10540
10770
|
async adminExternalMemoryStatus(namespace, jobId) {
|
|
10541
|
-
const
|
|
10542
|
-
if (!
|
|
10543
|
-
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);
|
|
10544
10774
|
try {
|
|
10545
|
-
await this.refreshExternalImportMemory(
|
|
10775
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10546
10776
|
const jobs = memory.listExternalMemoryImportJobs();
|
|
10547
10777
|
const job = jobId ? jobs.find(({ id }) => id === jobId) : [...jobs].sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
|
|
10548
10778
|
if (!job) return { job: null };
|
|
10549
|
-
if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(
|
|
10779
|
+
if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(key2, job.id);
|
|
10550
10780
|
return this.externalImportView(job);
|
|
10551
10781
|
} finally {
|
|
10552
10782
|
if (owned) await memory.close();
|
|
10553
10783
|
}
|
|
10554
10784
|
}
|
|
10555
10785
|
async adminRetryExternalMemory(namespace, jobId) {
|
|
10556
|
-
const
|
|
10786
|
+
const key2 = namespace.trim();
|
|
10557
10787
|
let lastConflict;
|
|
10558
10788
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
10559
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10789
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10560
10790
|
try {
|
|
10561
|
-
await this.refreshExternalImportMemory(
|
|
10791
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10562
10792
|
const job = await memory.retryExternalMemoryImportJob(jobId);
|
|
10563
|
-
this.scheduleExternalMemoryImport(
|
|
10793
|
+
this.scheduleExternalMemoryImport(key2, job.id);
|
|
10564
10794
|
return this.externalImportView(job);
|
|
10565
10795
|
} catch (error) {
|
|
10566
10796
|
if (!(error instanceof StorageConflictError)) throw error;
|
|
@@ -10573,10 +10803,10 @@ var StrataGateRuntime = class {
|
|
|
10573
10803
|
throw lastConflict;
|
|
10574
10804
|
}
|
|
10575
10805
|
async adminCommitExternalMemory(namespace, jobId, choices) {
|
|
10576
|
-
const
|
|
10577
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10806
|
+
const key2 = namespace.trim();
|
|
10807
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10578
10808
|
try {
|
|
10579
|
-
await this.refreshExternalImportMemory(
|
|
10809
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10580
10810
|
const job = memory.getExternalMemoryImportJob(jobId);
|
|
10581
10811
|
if (!job) throw new Error("\u627E\u4E0D\u5230\u5BFC\u5165\u4EFB\u52A1");
|
|
10582
10812
|
if (job.status !== "ready" && job.status !== "awaiting_confirmation") {
|
|
@@ -10617,10 +10847,10 @@ var StrataGateRuntime = class {
|
|
|
10617
10847
|
}
|
|
10618
10848
|
}
|
|
10619
10849
|
async adminUndoExternalMemory(namespace, sourceBlockId) {
|
|
10620
|
-
const
|
|
10621
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10850
|
+
const key2 = namespace.trim();
|
|
10851
|
+
const { memory, owned } = await this.openAdminMemory(key2);
|
|
10622
10852
|
try {
|
|
10623
|
-
await this.refreshExternalImportMemory(
|
|
10853
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10624
10854
|
const result = await memory.undoExternalMemoryImport(sourceBlockId);
|
|
10625
10855
|
const job = memory.listExternalMemoryImportJobs().find((candidate) => candidate.sourceBlockId === sourceBlockId);
|
|
10626
10856
|
if (job) await memory.markExternalMemoryImportUndone(job.id);
|
|
@@ -10702,19 +10932,19 @@ var StrataGateRuntime = class {
|
|
|
10702
10932
|
return { opened: true, path };
|
|
10703
10933
|
}
|
|
10704
10934
|
async adminExpandBlock(namespace, id, target) {
|
|
10705
|
-
const
|
|
10706
|
-
if (!
|
|
10935
|
+
const key2 = namespace.trim();
|
|
10936
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10707
10937
|
const update = this.settingsTail.catch(() => {
|
|
10708
10938
|
}).then(async () => {
|
|
10709
10939
|
await this.flush();
|
|
10710
|
-
const active = this.spaces.get(
|
|
10940
|
+
const active = this.spaces.get(key2);
|
|
10711
10941
|
if (active) return (await active).expandBlock(id, target, "user");
|
|
10712
10942
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
|
|
10713
|
-
throw new Error(`Unknown StrataGate namespace: ${
|
|
10943
|
+
throw new Error(`Unknown StrataGate namespace: ${key2}`);
|
|
10714
10944
|
}
|
|
10715
10945
|
const memory = await StrataGate.open({
|
|
10716
10946
|
database: this.config.database,
|
|
10717
|
-
namespace:
|
|
10947
|
+
namespace: key2,
|
|
10718
10948
|
blockTurnSize: this.blockTurnSize,
|
|
10719
10949
|
blockDecayLambda: this.blockDecayLambda,
|
|
10720
10950
|
summarizer: this.models.summarizer,
|
|
@@ -10734,20 +10964,20 @@ var StrataGateRuntime = class {
|
|
|
10734
10964
|
return update;
|
|
10735
10965
|
}
|
|
10736
10966
|
async adminRetryJob(namespace, kind, id) {
|
|
10737
|
-
const
|
|
10967
|
+
const key2 = namespace.trim();
|
|
10738
10968
|
const jobId = id.trim();
|
|
10739
|
-
if (!
|
|
10969
|
+
if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10740
10970
|
if (!jobId) throw new TypeError("StrataGate job id must not be empty");
|
|
10741
|
-
if (kind === "topic-projection") return this.adminRetryTopicProjection(
|
|
10742
|
-
const retryKey = `${
|
|
10971
|
+
if (kind === "topic-projection") return this.adminRetryTopicProjection(key2, jobId);
|
|
10972
|
+
const retryKey = `${key2}\0${kind}\0${jobId}`;
|
|
10743
10973
|
const existing = this.adminJobRetryRuns.get(retryKey);
|
|
10744
10974
|
if (existing) return existing;
|
|
10745
10975
|
const update = this.settingsTail.catch(() => {
|
|
10746
10976
|
}).then(async () => {
|
|
10747
10977
|
await this.flush();
|
|
10748
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
10978
|
+
const { memory, owned } = await this.openAdminMemory(key2, { derivation: true });
|
|
10749
10979
|
try {
|
|
10750
|
-
await this.refreshExternalImportMemory(
|
|
10980
|
+
await this.refreshExternalImportMemory(key2, memory);
|
|
10751
10981
|
const graphJob = kind === "graph-projection" ? memory.listGraphProjectionJobs().find((candidate) => candidate.id === jobId) : void 0;
|
|
10752
10982
|
const blockId = kind === "graph-projection" ? graphJob?.sourceEventIds.flatMap((eventId) => memory.listAllEvents().find(({ id: eventCandidateId }) => eventCandidateId === eventId)?.sourceBlockId ?? [])[0] : jobId;
|
|
10753
10983
|
const block = blockId ? memory.listBlocks().find((candidate) => candidate.id === blockId) : void 0;
|
|
@@ -10763,12 +10993,12 @@ var StrataGateRuntime = class {
|
|
|
10763
10993
|
if (!this.models.isReady(session)) {
|
|
10764
10994
|
throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
|
|
10765
10995
|
}
|
|
10766
|
-
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);
|
|
10767
10997
|
await this.persistSuccessfulResponses(memory);
|
|
10768
10998
|
const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
|
|
10769
10999
|
if (kind !== "graph-projection" && currentBlock?.processingStatus === "ready" && currentBlock.threadId) {
|
|
10770
|
-
this.pendingSurfaceSync.set(`${
|
|
10771
|
-
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);
|
|
10772
11002
|
}
|
|
10773
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);
|
|
10774
11004
|
const succeeded = kind === "graph-projection" ? job?.status === "completed" : job?.status === "succeeded" || job?.status === "skipped";
|
|
@@ -10783,7 +11013,7 @@ var StrataGateRuntime = class {
|
|
|
10783
11013
|
result,
|
|
10784
11014
|
processingStatus: currentBlock?.processingStatus ?? null,
|
|
10785
11015
|
ready: currentBlock?.processingStatus === "ready",
|
|
10786
|
-
surfaceUpdated: !blockId || !this.pendingSurfaceSync.has(`${
|
|
11016
|
+
surfaceUpdated: !blockId || !this.pendingSurfaceSync.has(`${key2}\0${blockId}`)
|
|
10787
11017
|
};
|
|
10788
11018
|
} finally {
|
|
10789
11019
|
if (owned) await memory.close();
|
|
@@ -10799,14 +11029,14 @@ var StrataGateRuntime = class {
|
|
|
10799
11029
|
return tracked;
|
|
10800
11030
|
}
|
|
10801
11031
|
async adminRetryTopicProjection(namespace, id) {
|
|
10802
|
-
const
|
|
11032
|
+
const key2 = namespace.trim();
|
|
10803
11033
|
const jobId = id.trim();
|
|
10804
|
-
if (!
|
|
11034
|
+
if (!key2 || !jobId) throw new TypeError("Topic retry requires namespace and job id");
|
|
10805
11035
|
const update = this.settingsTail.catch(() => {
|
|
10806
11036
|
}).then(async () => {
|
|
10807
|
-
const { memory, owned } = await this.openAdminMemory(
|
|
11037
|
+
const { memory, owned } = await this.openAdminMemory(key2, { derivation: true });
|
|
10808
11038
|
try {
|
|
10809
|
-
return await this.retryTopicWrite(
|
|
11039
|
+
return await this.retryTopicWrite(key2, memory, () => memory.retryTopicProjection(jobId));
|
|
10810
11040
|
} finally {
|
|
10811
11041
|
if (owned) await memory.close();
|
|
10812
11042
|
}
|
|
@@ -10983,8 +11213,8 @@ var StrataGateRuntime = class {
|
|
|
10983
11213
|
}
|
|
10984
11214
|
scheduleBlockDerivation(session, memory) {
|
|
10985
11215
|
const threadId = String(session.id);
|
|
10986
|
-
const
|
|
10987
|
-
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;
|
|
10988
11218
|
const pendingBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId && block.processingStatus === "pending").map((block) => block.id));
|
|
10989
11219
|
if (pendingBlockIds.size === 0) return;
|
|
10990
11220
|
const retryTimes = [
|
|
@@ -10994,7 +11224,7 @@ var StrataGateRuntime = class {
|
|
|
10994
11224
|
if (retryTimes.length === 0) return;
|
|
10995
11225
|
const delay = Math.max(0, Math.min(...retryTimes) - Date.now());
|
|
10996
11226
|
const timer = setTimeout(() => {
|
|
10997
|
-
this.derivationTimers.delete(
|
|
11227
|
+
this.derivationTimers.delete(key2);
|
|
10998
11228
|
if (this.closed || !this.models.isReady(session)) return;
|
|
10999
11229
|
const failedBefore = this.failedCoreJobs(memory);
|
|
11000
11230
|
const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
|
|
@@ -11021,13 +11251,13 @@ var StrataGateRuntime = class {
|
|
|
11021
11251
|
this.notePluginError(session, error);
|
|
11022
11252
|
this.onIngestError(error);
|
|
11023
11253
|
}).finally(() => {
|
|
11024
|
-
this.derivationRuns.delete(
|
|
11254
|
+
this.derivationRuns.delete(key2);
|
|
11025
11255
|
this.scheduleBlockDerivation(session, memory);
|
|
11026
11256
|
});
|
|
11027
|
-
this.derivationRuns.set(
|
|
11257
|
+
this.derivationRuns.set(key2, run);
|
|
11028
11258
|
}, delay);
|
|
11029
11259
|
timer.unref?.();
|
|
11030
|
-
this.derivationTimers.set(
|
|
11260
|
+
this.derivationTimers.set(key2, timer);
|
|
11031
11261
|
}
|
|
11032
11262
|
scheduleGraphMigration(session, memory) {
|
|
11033
11263
|
const namespace = this.namespaceFor(session);
|
|
@@ -11112,23 +11342,28 @@ var StrataGateRuntime = class {
|
|
|
11112
11342
|
if (responses.length > 0) await memory.recordSuccessfulModelResponses(responses);
|
|
11113
11343
|
}
|
|
11114
11344
|
batch(session, evidence, results, metadata = {}) {
|
|
11115
|
-
const sequence =
|
|
11345
|
+
const sequence = this.batchSequence + 1;
|
|
11116
11346
|
const id = `batch_${sequence}`;
|
|
11117
11347
|
const refs = new Map(evidence.map(({ ref, target }) => [ref, target]));
|
|
11118
|
-
const
|
|
11119
|
-
|
|
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);
|
|
11120
11355
|
if (!sessionBatches) {
|
|
11121
11356
|
sessionBatches = /* @__PURE__ */ new Map();
|
|
11122
|
-
this.batches.set(
|
|
11357
|
+
this.batches.set(key2, sessionBatches);
|
|
11123
11358
|
}
|
|
11124
11359
|
sessionBatches.set(id, { id, sequence, refs, status: "unresolved" });
|
|
11125
|
-
this.latestBatchIds.set(
|
|
11126
|
-
return
|
|
11360
|
+
this.latestBatchIds.set(key2, id);
|
|
11361
|
+
return output;
|
|
11127
11362
|
}
|
|
11128
11363
|
requireBatch(session, batchId, operation) {
|
|
11129
|
-
const
|
|
11130
|
-
const selectedId = batchId?.trim() || this.latestBatchIds.get(
|
|
11131
|
-
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);
|
|
11132
11367
|
const batch = selectedId ? sessionBatches?.get(selectedId) : void 0;
|
|
11133
11368
|
if (batch) return batch;
|
|
11134
11369
|
const availableBatchIds = [...sessionBatches?.keys() ?? []];
|
|
@@ -11136,7 +11371,7 @@ var StrataGateRuntime = class {
|
|
|
11136
11371
|
const requested = batchId?.trim() ? `Unknown retrieval batch: ${batchId.trim()}.` : "No StrataGate retrieval batch exists for this session.";
|
|
11137
11372
|
throw new Error([
|
|
11138
11373
|
`${operation} could not select a retrieval batch. ${requested}`,
|
|
11139
|
-
`Latest batch: ${this.latestBatchIds.get(
|
|
11374
|
+
`Latest batch: ${this.latestBatchIds.get(key2) ?? "none"}.`,
|
|
11140
11375
|
`Unresolved batches: ${JSON.stringify(unresolvedBatchIds)}.`,
|
|
11141
11376
|
`Available batches: ${JSON.stringify(availableBatchIds)}.`
|
|
11142
11377
|
].join(" "));
|
|
@@ -11147,10 +11382,10 @@ var StrataGateRuntime = class {
|
|
|
11147
11382
|
return [...sessionBatches.values()].filter(({ status }) => status === "unresolved").map(({ id }) => id);
|
|
11148
11383
|
}
|
|
11149
11384
|
batchError(session, batch, summary, invalidEvidenceRefs = []) {
|
|
11150
|
-
const
|
|
11385
|
+
const key2 = String(session.id);
|
|
11151
11386
|
const assessmentStatus = batch.assessment?.verdict ?? "not_assessed";
|
|
11152
|
-
const latestBatch = this.latestBatchIds.get(
|
|
11153
|
-
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;
|
|
11154
11389
|
return [
|
|
11155
11390
|
summary,
|
|
11156
11391
|
`Invalid evidence refs: ${JSON.stringify(invalidEvidenceRefs)}.`,
|
|
@@ -11394,7 +11629,7 @@ function compactEvent2(event, score) {
|
|
|
11394
11629
|
summary: compactText2(event.summary),
|
|
11395
11630
|
sourceTime: event.temporal.happenedStart ?? event.temporal.mentionedAt ?? event.createdAt,
|
|
11396
11631
|
temporal: compactTemporal(event),
|
|
11397
|
-
sourceBlockId: event.sourceBlockId,
|
|
11632
|
+
...event.sourceBlockId !== void 0 ? { sourceBlockId: event.sourceBlockId } : {},
|
|
11398
11633
|
status: event.status,
|
|
11399
11634
|
scope: event.scope,
|
|
11400
11635
|
criticality: event.criticality,
|
|
@@ -11539,9 +11774,9 @@ Events:
|
|
|
11539
11774
|
metadataEvidenceStatus: result.metadataEvidenceStatus,
|
|
11540
11775
|
matchType: result.matchType,
|
|
11541
11776
|
currentState: node.currentState,
|
|
11542
|
-
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 })),
|
|
11543
11778
|
currentRelations: result.currentEdges?.map(({ relation, status }) => ({ relation, status })),
|
|
11544
|
-
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 })),
|
|
11545
11780
|
historicalRelations: result.historicalEdges?.map(({ relation, status, validFrom, validTo }) => ({ relation, status, validFrom, validTo })),
|
|
11546
11781
|
timeline: result.timeline
|
|
11547
11782
|
});
|
|
@@ -11601,7 +11836,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
11601
11836
|
},
|
|
11602
11837
|
output: jsonOutput,
|
|
11603
11838
|
execute: async (args, exec) => {
|
|
11604
|
-
if (Object.keys(args).some((
|
|
11839
|
+
if (Object.keys(args).some((key2) => key2 !== "field" && key2 !== "value")) throw new TypeError("Unknown Profile update argument");
|
|
11605
11840
|
sessionOf(exec);
|
|
11606
11841
|
return runtime.updatePersistentProfileFromTool(args.field, args.value);
|
|
11607
11842
|
}
|
|
@@ -11815,9 +12050,9 @@ var LEIDEN_RESOLUTION = 0.55;
|
|
|
11815
12050
|
function pairKey(left, right) {
|
|
11816
12051
|
return left < right ? `${left}\0${right}` : `${right}\0${left}`;
|
|
11817
12052
|
}
|
|
11818
|
-
function pairIds(
|
|
11819
|
-
const separator =
|
|
11820
|
-
return [
|
|
12053
|
+
function pairIds(key2) {
|
|
12054
|
+
const separator = key2.indexOf("\0");
|
|
12055
|
+
return [key2.slice(0, separator), key2.slice(separator + 1)];
|
|
11821
12056
|
}
|
|
11822
12057
|
function semanticTags(node) {
|
|
11823
12058
|
return Array.isArray(node.tags) ? [...new Set(node.tags.map((tag) => tag.trim()).filter(Boolean))] : [];
|
|
@@ -11848,8 +12083,8 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
11848
12083
|
const structuralWeights = /* @__PURE__ */ new Map();
|
|
11849
12084
|
const addStructuralWeight = (left, right, weight) => {
|
|
11850
12085
|
if (left === right || !nodeMap.has(left) || !nodeMap.has(right) || weight <= 0) return;
|
|
11851
|
-
const
|
|
11852
|
-
structuralWeights.set(
|
|
12086
|
+
const key2 = pairKey(left, right);
|
|
12087
|
+
structuralWeights.set(key2, (structuralWeights.get(key2) ?? 0) + weight);
|
|
11853
12088
|
};
|
|
11854
12089
|
for (const edge of rawEdges.filter((edge2) => edge2.status === "active")) {
|
|
11855
12090
|
addStructuralWeight(edge.fromNodeId, edge.toNodeId, 3 * Math.max(0.25, Number(edge.confidence) || 0.8));
|
|
@@ -11869,8 +12104,8 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
11869
12104
|
const graph = createGraph({ multigraph: false });
|
|
11870
12105
|
for (const node of nodes) graph.addNode(node.id);
|
|
11871
12106
|
const weightedDegree = new Map(nodes.map((node) => [node.id, 0]));
|
|
11872
|
-
for (const [
|
|
11873
|
-
const [leftId, rightId] = pairIds(
|
|
12107
|
+
for (const [key2, structuralWeight] of structuralWeights) {
|
|
12108
|
+
const [leftId, rightId] = pairIds(key2);
|
|
11874
12109
|
const left = nodeMap.get(leftId);
|
|
11875
12110
|
const right = nodeMap.get(rightId);
|
|
11876
12111
|
const leftTags = new Set(semanticTags(left));
|
|
@@ -11895,9 +12130,9 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
11895
12130
|
const connectedGroups = [];
|
|
11896
12131
|
const isolatedIds = [];
|
|
11897
12132
|
for (const community of partition.getCommunities().values()) {
|
|
11898
|
-
const
|
|
11899
|
-
const connected =
|
|
11900
|
-
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);
|
|
11901
12136
|
if (connected.length > 0) connectedGroups.push(connected);
|
|
11902
12137
|
isolatedIds.push(...isolated);
|
|
11903
12138
|
}
|
|
@@ -11929,7 +12164,7 @@ function currentPluginVersion() {
|
|
|
11929
12164
|
return "unknown";
|
|
11930
12165
|
}
|
|
11931
12166
|
var STRATAGATE_DSH_VERSION = currentPluginVersion();
|
|
11932
|
-
var TOPIC_DIRECTORY_VIEW_VERSION =
|
|
12167
|
+
var TOPIC_DIRECTORY_VIEW_VERSION = 7;
|
|
11933
12168
|
function graphProjectionIsProcessing(job) {
|
|
11934
12169
|
return derivationJobState(job, false) === "processing" || derivationJobState(job, false) === "retryable";
|
|
11935
12170
|
}
|
|
@@ -11984,7 +12219,7 @@ function redactValue(value) {
|
|
|
11984
12219
|
if (typeof value === "string") return redact(value);
|
|
11985
12220
|
if (Array.isArray(value)) return value.map(redactValue);
|
|
11986
12221
|
if (value && typeof value === "object") {
|
|
11987
|
-
return Object.fromEntries(Object.entries(value).map(([
|
|
12222
|
+
return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, redactValue(item)]));
|
|
11988
12223
|
}
|
|
11989
12224
|
return value;
|
|
11990
12225
|
}
|
|
@@ -11993,17 +12228,17 @@ function redactedMessage(message, blockId) {
|
|
|
11993
12228
|
const common = { ...base, content: redact(message.content), blockId };
|
|
11994
12229
|
return toolCalls ? { ...common, toolCalls: redactValue(toolCalls) } : common;
|
|
11995
12230
|
}
|
|
11996
|
-
function sourceMessages(snapshot,
|
|
12231
|
+
function sourceMessages(snapshot, ids2) {
|
|
11997
12232
|
const output = [];
|
|
11998
12233
|
for (const block of snapshot.blocks) {
|
|
11999
12234
|
for (const message of block.l5Raw) {
|
|
12000
|
-
if (!
|
|
12235
|
+
if (!ids2 || ids2.has(message.id)) {
|
|
12001
12236
|
output.push(redactedMessage(message, block.id));
|
|
12002
12237
|
}
|
|
12003
12238
|
}
|
|
12004
12239
|
}
|
|
12005
12240
|
for (const message of snapshot.openTail) {
|
|
12006
|
-
if (!
|
|
12241
|
+
if (!ids2 || ids2.has(message.id)) {
|
|
12007
12242
|
output.push(redactedMessage(message, null));
|
|
12008
12243
|
}
|
|
12009
12244
|
}
|
|
@@ -12249,15 +12484,15 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
12249
12484
|
const failures = state.jobs.flatMap((job) => {
|
|
12250
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 [];
|
|
12251
12486
|
const diagnostics = safeTopicDiagnostics(job.diagnostics);
|
|
12252
|
-
const
|
|
12253
|
-
const
|
|
12254
|
-
if (!outstandingWork.has(
|
|
12255
|
-
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);
|
|
12256
12491
|
return true;
|
|
12257
12492
|
});
|
|
12258
|
-
return
|
|
12493
|
+
return ids2.length > 0 ? [{
|
|
12259
12494
|
jobId: job.id,
|
|
12260
|
-
eventIds:
|
|
12495
|
+
eventIds: ids2,
|
|
12261
12496
|
attempts: job.attempts,
|
|
12262
12497
|
// Topic failures store reason codes, never raw model output.
|
|
12263
12498
|
diagnostics,
|
|
@@ -12268,7 +12503,7 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
12268
12503
|
const total = (/* @__PURE__ */ new Set([...history.map(([id]) => id), ...backfillRelations.map(({ id }) => id)])).size;
|
|
12269
12504
|
const failedRelations = backfillRelations.filter(({ topicId, id, version }) => failedWork.has(relationKey(topicId, id, version))).length;
|
|
12270
12505
|
const bootstrap = frozen ? {
|
|
12271
|
-
status:
|
|
12506
|
+
status: outstandingWork.size === 0 ? "completed" : outstandingWork.size === failedWork.size ? "failed" : frozen.status === "pending" ? "pending" : "running",
|
|
12272
12507
|
total,
|
|
12273
12508
|
completed: total - outstanding.size,
|
|
12274
12509
|
failedEvents: new Set(failures.flatMap(({ eventIds }) => eventIds)).size,
|
|
@@ -12337,8 +12572,8 @@ function topicEventPageNumber(url, name2, fallback) {
|
|
|
12337
12572
|
}
|
|
12338
12573
|
async function topicEvents(runtime, url) {
|
|
12339
12574
|
const allowed = /* @__PURE__ */ new Set(["namespace", "topicId", "sectionKey", "offset", "limit", "expectedRevision"]);
|
|
12340
|
-
for (const
|
|
12341
|
-
if (!allowed.has(
|
|
12575
|
+
for (const key2 of url.searchParams.keys()) {
|
|
12576
|
+
if (!allowed.has(key2) || url.searchParams.getAll(key2).length !== 1) {
|
|
12342
12577
|
throw new AdminHttpError(400, "Unknown or repeated topic Event parameter");
|
|
12343
12578
|
}
|
|
12344
12579
|
}
|
|
@@ -12365,29 +12600,29 @@ async function topicEvents(runtime, url) {
|
|
|
12365
12600
|
revision: data.revision
|
|
12366
12601
|
});
|
|
12367
12602
|
}
|
|
12368
|
-
let
|
|
12603
|
+
let ids2;
|
|
12369
12604
|
if (topicId === "pending") {
|
|
12370
|
-
if (sectionKey === null)
|
|
12605
|
+
if (sectionKey === null) ids2 = projection.pending.map(({ id }) => id);
|
|
12371
12606
|
else if (sectionKey.startsWith("failure:")) {
|
|
12372
12607
|
const failure = projection.failures.find(({ jobId }) => jobId === sectionKey.slice("failure:".length));
|
|
12373
12608
|
if (!failure) throw new AdminHttpError(404, "Unknown or unavailable Topic failure");
|
|
12374
|
-
|
|
12609
|
+
ids2 = failure.eventIds;
|
|
12375
12610
|
} else throw new AdminHttpError(404, "Unknown or unavailable pending Topic section");
|
|
12376
12611
|
} else {
|
|
12377
12612
|
const topic = projection.chapters.find(({ id }) => id === topicId);
|
|
12378
12613
|
if (!topic) throw new AdminHttpError(404, "Unknown or unavailable memory topic");
|
|
12379
|
-
if (sectionKey === null)
|
|
12614
|
+
if (sectionKey === null) ids2 = topic.sourceEventIds;
|
|
12380
12615
|
else if (sectionKey === "uncovered") {
|
|
12381
12616
|
const covered = new Set(memoryTopicSections(topic).flatMap((section) => section.sourceEventIds));
|
|
12382
|
-
|
|
12617
|
+
ids2 = topic.sourceEventIds.filter((id) => !covered.has(id));
|
|
12383
12618
|
} else {
|
|
12384
12619
|
const section = memoryTopicSections(topic).find((candidate) => candidate.key === sectionKey);
|
|
12385
12620
|
if (!section) throw new AdminHttpError(404, "Unknown or unavailable memory topic section");
|
|
12386
|
-
|
|
12621
|
+
ids2 = section.sourceEventIds;
|
|
12387
12622
|
}
|
|
12388
12623
|
}
|
|
12389
12624
|
const byId = new Map(projection.visibleEvents.map((event) => [event.id, event]));
|
|
12390
|
-
const sources2 = [...new Set(
|
|
12625
|
+
const sources2 = [...new Set(ids2)].flatMap((id) => byId.get(id) ?? []);
|
|
12391
12626
|
const page = sources2.slice(offset, offset + limit);
|
|
12392
12627
|
return {
|
|
12393
12628
|
namespace,
|
|
@@ -12402,13 +12637,13 @@ async function topicEvents(runtime, url) {
|
|
|
12402
12637
|
};
|
|
12403
12638
|
}
|
|
12404
12639
|
async function overview(runtime, cachedEntries) {
|
|
12405
|
-
const
|
|
12640
|
+
const entries2 = cachedEntries ?? await Promise.all((await runtime.adminNamespaces()).map(async (namespace) => ({
|
|
12406
12641
|
namespace,
|
|
12407
12642
|
revision: 0,
|
|
12408
12643
|
snapshot: await runtime.adminSnapshot(namespace)
|
|
12409
12644
|
})));
|
|
12410
12645
|
const rows = [];
|
|
12411
|
-
for (const { namespace, snapshot } of
|
|
12646
|
+
for (const { namespace, snapshot } of entries2) {
|
|
12412
12647
|
if (!snapshot) continue;
|
|
12413
12648
|
const recovered = recoverSnapshotView(snapshot);
|
|
12414
12649
|
const blockDetails = (blockIds) => [...new Set(blockIds)].flatMap((blockId) => {
|
|
@@ -12477,7 +12712,12 @@ async function overview(runtime, cachedEntries) {
|
|
|
12477
12712
|
sourceEventIds: job.sourceEventIds
|
|
12478
12713
|
};
|
|
12479
12714
|
};
|
|
12480
|
-
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));
|
|
12481
12721
|
const describeTopicJob = (job) => {
|
|
12482
12722
|
const diagnostics = safeTopicDiagnostics(job.diagnostics);
|
|
12483
12723
|
const lastError = diagnostics ? `${diagnostics.category}: ${diagnostics.reason}` : ["timeout", "source-changed", "invalid-output", "worker-failed"].includes(job.lastError ?? "") ? job.lastError : "worker-failed";
|
|
@@ -12492,7 +12732,7 @@ async function overview(runtime, cachedEntries) {
|
|
|
12492
12732
|
lastError,
|
|
12493
12733
|
lastErrorFull: lastError,
|
|
12494
12734
|
diagnostics,
|
|
12495
|
-
eventCount: job.sourceEventIds.length,
|
|
12735
|
+
eventCount: job.status === "completed" ? job.sourceEventIds.length : memoryTopicUnfinishedJobEventIds(job, topicState, topicVersions).length,
|
|
12496
12736
|
blockIds: [],
|
|
12497
12737
|
threadIds: [],
|
|
12498
12738
|
blockDetails: []
|
|
@@ -12796,12 +13036,12 @@ function recoverSnapshotView(snapshot) {
|
|
|
12796
13036
|
if (turn !== null) receiptTurns.set(receiptTurnKey(threadId, receipt.createdAt), turn);
|
|
12797
13037
|
const currentActivity = receiptActivity.get(threadId);
|
|
12798
13038
|
if (!currentActivity || receipt.createdAt > currentActivity) receiptActivity.set(threadId, receipt.createdAt);
|
|
12799
|
-
const
|
|
12800
|
-
const candidates = receiptCandidates.get(
|
|
13039
|
+
const key2 = timestampKey(receipt.createdAt);
|
|
13040
|
+
const candidates = receiptCandidates.get(key2) ?? /* @__PURE__ */ new Set();
|
|
12801
13041
|
candidates.add(threadId);
|
|
12802
|
-
receiptCandidates.set(
|
|
13042
|
+
receiptCandidates.set(key2, candidates);
|
|
12803
13043
|
}
|
|
12804
|
-
const exactThreadAt = new Map([...receiptCandidates].filter(([,
|
|
13044
|
+
const exactThreadAt = new Map([...receiptCandidates].filter(([, ids2]) => ids2.size === 1).map(([createdAt, ids2]) => [createdAt, [...ids2][0]]));
|
|
12805
13045
|
const recoverMessages = (messages) => {
|
|
12806
13046
|
let precedingThreadId = null;
|
|
12807
13047
|
return messages.map((message) => {
|
|
@@ -12822,11 +13062,11 @@ function recoverSnapshotView(snapshot) {
|
|
|
12822
13062
|
messages.push(item.message);
|
|
12823
13063
|
groups.set(item.threadId, messages);
|
|
12824
13064
|
}
|
|
12825
|
-
const
|
|
12826
|
-
for (const [threadId, messages] of
|
|
12827
|
-
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);
|
|
12828
13068
|
blocks.push({
|
|
12829
|
-
id:
|
|
13069
|
+
id: entries2.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
|
|
12830
13070
|
source,
|
|
12831
13071
|
threadId,
|
|
12832
13072
|
messages,
|
|
@@ -12875,12 +13115,12 @@ function virtualBlockLayers(block) {
|
|
|
12875
13115
|
]);
|
|
12876
13116
|
}
|
|
12877
13117
|
function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
|
|
12878
|
-
const
|
|
13118
|
+
const ids2 = /* @__PURE__ */ new Set([
|
|
12879
13119
|
...view.blocks.map((block) => block.threadId),
|
|
12880
13120
|
...view.openMessages.map(({ threadId }) => threadId),
|
|
12881
13121
|
...view.receiptThreads.values()
|
|
12882
13122
|
]);
|
|
12883
|
-
return [...
|
|
13123
|
+
return [...ids2].map((id) => {
|
|
12884
13124
|
const blocks = view.blocks.filter((block) => block.threadId === id);
|
|
12885
13125
|
const messages = [
|
|
12886
13126
|
...blocks.flatMap((block) => block.messages),
|
|
@@ -13079,12 +13319,12 @@ async function sources(runtime, url) {
|
|
|
13079
13319
|
const blockId = url.searchParams.get("blockId");
|
|
13080
13320
|
let events = [];
|
|
13081
13321
|
let elements = [];
|
|
13082
|
-
let
|
|
13322
|
+
let ids2 = /* @__PURE__ */ new Set();
|
|
13083
13323
|
if (eventId) {
|
|
13084
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);
|
|
13085
13325
|
if (!event) throw new AdminHttpError(404, `Unknown event: ${eventId}`);
|
|
13086
13326
|
events = [event];
|
|
13087
|
-
|
|
13327
|
+
ids2 = new Set(event.sourceMessageIds);
|
|
13088
13328
|
} else if (nodeId) {
|
|
13089
13329
|
const rawNode = snapshot.graphNodes.find(({ id }) => id === nodeId);
|
|
13090
13330
|
if (!rawNode) throw new AdminHttpError(404, `Unknown graph node: ${nodeId}`);
|
|
@@ -13106,7 +13346,7 @@ async function sources(runtime, url) {
|
|
|
13106
13346
|
...edges.flatMap(({ sourceEventIds }) => sourceEventIds)
|
|
13107
13347
|
]);
|
|
13108
13348
|
events = snapshot.events.filter(({ id }) => eventIds.has(id));
|
|
13109
|
-
|
|
13349
|
+
ids2 = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
13110
13350
|
return {
|
|
13111
13351
|
namespace,
|
|
13112
13352
|
node: view.node,
|
|
@@ -13117,28 +13357,28 @@ async function sources(runtime, url) {
|
|
|
13117
13357
|
currentEdges: view.currentEdges,
|
|
13118
13358
|
historicalEdges: view.historicalEdges,
|
|
13119
13359
|
events: events.map((event) => eventSummary(event)),
|
|
13120
|
-
messages: sourceMessages(snapshot,
|
|
13360
|
+
messages: sourceMessages(snapshot, ids2)
|
|
13121
13361
|
};
|
|
13122
13362
|
} else if (elementId) {
|
|
13123
13363
|
const element = snapshot.elements.find(({ id }) => id === elementId);
|
|
13124
13364
|
if (!element) throw new AdminHttpError(404, `Unknown element: ${elementId}`);
|
|
13125
13365
|
elements = [element];
|
|
13126
13366
|
events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
|
|
13127
|
-
|
|
13367
|
+
ids2 = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
13128
13368
|
} else if (blockId) {
|
|
13129
13369
|
const displayBlock = recoverSnapshotView(snapshot).blocks.find(({ id }) => id === blockId);
|
|
13130
13370
|
const block = displayBlock?.source ?? snapshot.blocks.find(({ id }) => id === blockId);
|
|
13131
13371
|
if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
|
|
13132
13372
|
const messages = displayBlock?.messages ?? block.l5Raw;
|
|
13133
|
-
|
|
13134
|
-
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))));
|
|
13135
13375
|
const eventIds = new Set(events.map(({ id }) => id));
|
|
13136
13376
|
elements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
13137
13377
|
return {
|
|
13138
13378
|
namespace,
|
|
13139
13379
|
events: events.map((event) => eventSummary(event)),
|
|
13140
13380
|
elements: elements.map(elementSummary),
|
|
13141
|
-
messages: sourceMessages(snapshot,
|
|
13381
|
+
messages: sourceMessages(snapshot, ids2),
|
|
13142
13382
|
layers: displayBlock ? virtualBlockLayers(displayBlock) : blockLayers(block),
|
|
13143
13383
|
virtual: displayBlock?.virtual ?? false
|
|
13144
13384
|
};
|
|
@@ -13153,7 +13393,7 @@ async function sources(runtime, url) {
|
|
|
13153
13393
|
} : {},
|
|
13154
13394
|
elements: elements.map(elementSummary),
|
|
13155
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 })),
|
|
13156
|
-
messages: sourceMessages(snapshot,
|
|
13396
|
+
messages: sourceMessages(snapshot, ids2)
|
|
13157
13397
|
};
|
|
13158
13398
|
}
|
|
13159
13399
|
async function expandBlock(runtime, url) {
|
|
@@ -13251,20 +13491,20 @@ async function audit(runtime, url) {
|
|
|
13251
13491
|
}
|
|
13252
13492
|
function requestHeader(req, name2) {
|
|
13253
13493
|
const headers = req.headers ?? {};
|
|
13254
|
-
const
|
|
13255
|
-
const value =
|
|
13494
|
+
const key2 = Object.keys(headers).find((candidate) => candidate.toLocaleLowerCase() === name2.toLocaleLowerCase());
|
|
13495
|
+
const value = key2 ? headers[key2] : void 0;
|
|
13256
13496
|
return Array.isArray(value) ? value.join(", ") : value ?? "";
|
|
13257
13497
|
}
|
|
13258
13498
|
async function dashboard(runtime, url, ifNoneMatch) {
|
|
13259
|
-
const
|
|
13499
|
+
const entries2 = await runtime.adminSnapshotEntries();
|
|
13260
13500
|
const requestedNamespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
13261
|
-
const selected =
|
|
13501
|
+
const selected = entries2.find(({ namespace }) => namespace === requestedNamespace) ?? entries2[0];
|
|
13262
13502
|
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
13263
|
-
const revisionKey =
|
|
13503
|
+
const revisionKey = entries2.map(({ namespace, revision }) => `${namespace}:${revision}`).join("|");
|
|
13264
13504
|
const agentMemoryWeight = runtime.adminAgentMemoryRetrievalWeight?.() ?? 1;
|
|
13265
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)}"`;
|
|
13266
13506
|
if (ifNoneMatch.split(",").map((value) => value.trim()).includes(etag)) return { etag, notModified: true };
|
|
13267
|
-
const overviewValue = await overview(runtime,
|
|
13507
|
+
const overviewValue = await overview(runtime, entries2);
|
|
13268
13508
|
if (!selected) {
|
|
13269
13509
|
return { etag, notModified: false, body: { namespace: null, overview: overviewValue, data: null, processing: false } };
|
|
13270
13510
|
}
|
|
@@ -13402,8 +13642,8 @@ function isCompleteAnswer(data) {
|
|
|
13402
13642
|
if (!data.message.content.some((block) => block.type === "text" && block.text.trim().length > 0)) return false;
|
|
13403
13643
|
const stream = data.stream ?? [];
|
|
13404
13644
|
for (let index = stream.length - 1; index >= 0; index--) {
|
|
13405
|
-
const
|
|
13406
|
-
if (
|
|
13645
|
+
const record3 = stream[index];
|
|
13646
|
+
if (record3.type === "chunk" && record3.chunk.type === "finish") return record3.chunk.reason.kind === "stop";
|
|
13407
13647
|
}
|
|
13408
13648
|
return true;
|
|
13409
13649
|
}
|